/* MIT License 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 { $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 { HashtagPlugin } from '@lexical/react/LexicalHashtagPlugin'; 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 clsx from 'clsx'; import { $createParagraphNode, $createTextNode, $getRoot } from 'lexical'; import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; 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'; import NewMentionsPlugin from './plugins/mention-plugin'; 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(({ composeId, condensed, onFocus, autoFocus }, editorStateRef) => { const dispatch = useAppDispatch(); const features = useFeatures(); const initialConfig: InitialConfigType = useMemo(function() { return { namespace: 'ComposeForm', onError: console.error, nodes, theme: { hashtag: 'hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue', 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(null); const onRef = (_floatingAnchorElem: HTMLDivElement) => { if (_floatingAnchorElem !== null) { setFloatingAnchorElem(_floatingAnchorElem); } }; return (
} placeholder={(
)} ErrorBoundary={LexicalErrorBoundary} /> { editor.update(() => { if (editorStateRef) (editorStateRef as any).current = $convertToMarkdownString(TRANSFORMERS); }); }} /> {features.richText && } {features.richText && floatingAnchorElem && ( <> )}
); }); export default ComposeEditor;