Merge, store Lexical editorState
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
@ -6,47 +6,89 @@ Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
This source code is licensed under the MIT license found in the
|
||||
LICENSE file in the /app/soapbox/features/compose/editor directory.
|
||||
*/
|
||||
|
||||
import {
|
||||
$convertToMarkdownString,
|
||||
TRANSFORMERS,
|
||||
} from '@lexical/markdown';
|
||||
import { $convertFromMarkdownString, $convertToMarkdownString, TRANSFORMERS } from '@lexical/markdown';
|
||||
import { LexicalComposer, InitialConfigType } from '@lexical/react/LexicalComposer';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
||||
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
|
||||
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
|
||||
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
|
||||
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
|
||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
||||
import classNames from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { $createParagraphNode, $createTextNode, $getRoot } from 'lexical';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useFeatures } from 'soapbox/hooks';
|
||||
import { setEditorState } from 'soapbox/actions/compose';
|
||||
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||
|
||||
import nodes from './nodes';
|
||||
import FloatingLinkEditorPlugin from './plugins/floating-link-editor-plugin';
|
||||
import FloatingTextFormatToolbarPlugin from './plugins/floating-text-format-toolbar-plugin';
|
||||
|
||||
const initialConfig: InitialConfigType = {
|
||||
namespace: 'ComposeForm',
|
||||
onError: console.error,
|
||||
nodes,
|
||||
theme: {
|
||||
text: {
|
||||
bold: 'font-bold',
|
||||
code: 'font-mono',
|
||||
italic: 'italic',
|
||||
strikethrough: 'line-through',
|
||||
underline: 'underline',
|
||||
underlineStrikethrough: 'underline-line-through',
|
||||
},
|
||||
},
|
||||
const StatePlugin = ({ composeId, autoFocus }: { composeId: string, autoFocus: boolean }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocus) editor.focus();
|
||||
|
||||
editor.registerUpdateListener(({ editorState }) => {
|
||||
dispatch(setEditorState(composeId, editorState.isEmpty() ? null : JSON.stringify(editorState.toJSON())));
|
||||
});
|
||||
}, [editor]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const ComposeEditor = React.forwardRef<string, any>(({ condensed, onFocus }, editorStateRef) => {
|
||||
const ComposeEditor = React.forwardRef<string, any>(({ composeId, condensed, onFocus, autoFocus }, editorStateRef) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
|
||||
const initialConfig: InitialConfigType = useMemo(function() {
|
||||
return {
|
||||
namespace: 'ComposeForm',
|
||||
onError: console.error,
|
||||
nodes,
|
||||
theme: {
|
||||
text: {
|
||||
bold: 'font-bold',
|
||||
code: 'font-mono',
|
||||
italic: 'italic',
|
||||
strikethrough: 'line-through',
|
||||
underline: 'underline',
|
||||
underlineStrikethrough: 'underline-line-through',
|
||||
},
|
||||
},
|
||||
editorState: dispatch((_, getState) => {
|
||||
const state = getState();
|
||||
const compose = state.compose.get(composeId);
|
||||
|
||||
if (!compose) return;
|
||||
|
||||
if (compose.editorState) {
|
||||
return compose.editorState;
|
||||
}
|
||||
|
||||
return function() {
|
||||
if (compose.content_type === 'text/markdown') {
|
||||
$convertFromMarkdownString(compose.text, TRANSFORMERS);
|
||||
} else {
|
||||
const paragraph = $createParagraphNode();
|
||||
const textNode = $createTextNode(compose.text);
|
||||
|
||||
paragraph.append(textNode);
|
||||
|
||||
$getRoot()
|
||||
.clear()
|
||||
.append(paragraph);
|
||||
}
|
||||
};
|
||||
}),
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [floatingAnchorElem, setFloatingAnchorElem] =
|
||||
useState<HTMLDivElement | null>(null);
|
||||
|
||||
@ -63,7 +105,7 @@ const ComposeEditor = React.forwardRef<string, any>(({ condensed, onFocus }, edi
|
||||
contentEditable={
|
||||
<div className='editor' ref={onRef} onFocus={onFocus}>
|
||||
<ContentEditable
|
||||
className={classNames('outline-none py-2 transition-[min-height] motion-reduce:transition-none', {
|
||||
className={clsx('py-2 outline-none transition-[min-height] motion-reduce:transition-none', {
|
||||
'min-h-[40px]': condensed,
|
||||
'min-h-[100px]': !condensed,
|
||||
})}
|
||||
@ -71,7 +113,7 @@ const ComposeEditor = React.forwardRef<string, any>(({ condensed, onFocus }, edi
|
||||
</div>
|
||||
}
|
||||
placeholder={(
|
||||
<div className='absolute top-2 text-gray-600 dark:placeholder:text-gray-600 pointer-events-none select-none'>
|
||||
<div className='pointer-events-none absolute top-2 select-none text-gray-600 dark:placeholder:text-gray-600'>
|
||||
<FormattedMessage id='compose_form.placeholder' defaultMessage="What's on your mind" />
|
||||
</div>
|
||||
)}
|
||||
@ -91,6 +133,7 @@ const ComposeEditor = React.forwardRef<string, any>(({ condensed, onFocus }, edi
|
||||
<FloatingLinkEditorPlugin anchorElem={floatingAnchorElem} />
|
||||
</>
|
||||
)}
|
||||
<StatePlugin composeId={composeId} autoFocus={autoFocus} />
|
||||
</div>
|
||||
</LexicalComposer>
|
||||
);
|
||||
|
||||
@ -35,8 +35,8 @@ const FloatingLinkEditor = ({
|
||||
editor,
|
||||
anchorElem,
|
||||
}: {
|
||||
editor: LexicalEditor;
|
||||
anchorElem: HTMLElement;
|
||||
editor: LexicalEditor
|
||||
anchorElem: HTMLElement
|
||||
}): JSX.Element => {
|
||||
const editorRef = useRef<HTMLDivElement | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@ -79,7 +79,7 @@ const FloatingLinkEditor = ({
|
||||
let rect;
|
||||
if (nativeSelection.anchorNode === rootElement) {
|
||||
let inner = rootElement;
|
||||
while (inner.firstElementChild != null) {
|
||||
while (inner.firstElementChild !== null) {
|
||||
inner = inner.firstElementChild as HTMLElement;
|
||||
}
|
||||
rect = inner.getBoundingClientRect();
|
||||
@ -237,7 +237,7 @@ const useFloatingLinkEditorToolbar = (
|
||||
const autoLinkParent = $findMatchingParent(node, $isAutoLinkNode);
|
||||
|
||||
// We don't want this menu to open for auto links.
|
||||
if (linkParent != null && autoLinkParent == null) {
|
||||
if (linkParent !== null && autoLinkParent === null) {
|
||||
setIsLink(true);
|
||||
} else {
|
||||
setIsLink(false);
|
||||
@ -268,7 +268,7 @@ const useFloatingLinkEditorToolbar = (
|
||||
const FloatingLinkEditorPlugin = ({
|
||||
anchorElem = document.body,
|
||||
}: {
|
||||
anchorElem?: HTMLElement;
|
||||
anchorElem?: HTMLElement
|
||||
}): JSX.Element | null => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
return useFloatingLinkEditorToolbar(editor, anchorElem);
|
||||
|
||||
@ -40,14 +40,14 @@ const TextFormatFloatingToolbar = ({
|
||||
isCode,
|
||||
isStrikethrough,
|
||||
}: {
|
||||
editor: LexicalEditor;
|
||||
anchorElem: HTMLElement;
|
||||
isBold: boolean;
|
||||
isCode: boolean;
|
||||
isItalic: boolean;
|
||||
isLink: boolean;
|
||||
isStrikethrough: boolean;
|
||||
isUnderline: boolean;
|
||||
editor: LexicalEditor
|
||||
anchorElem: HTMLElement
|
||||
isBold: boolean
|
||||
isCode: boolean
|
||||
isItalic: boolean
|
||||
isLink: boolean
|
||||
isStrikethrough: boolean
|
||||
isUnderline: boolean
|
||||
}): JSX.Element => {
|
||||
const popupCharStylesEditorRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@ -301,7 +301,7 @@ const useFloatingTextFormatToolbar = (
|
||||
const FloatingTextFormatToolbarPlugin = ({
|
||||
anchorElem = document.body,
|
||||
}: {
|
||||
anchorElem?: HTMLElement;
|
||||
anchorElem?: HTMLElement
|
||||
}): JSX.Element | null => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
return useFloatingTextFormatToolbar(editor, anchorElem);
|
||||
|
||||
Reference in New Issue
Block a user