Merge, store Lexical editorState

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2023-03-02 19:42:31 +01:00
777 changed files with 25645 additions and 29568 deletions

View File

@ -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>
);

View File

@ -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);

View File

@ -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);