From 461a002be9b1e5cc87c32313b3d2399d1335a6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 19 Apr 2023 00:22:22 +0200 Subject: [PATCH] Lexical: Cleanup, allow ctrl+enter to submit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../compose/components/compose-form.tsx | 1 + app/soapbox/features/compose/editor/index.tsx | 28 +++++----------- .../editor/plugins/autosuggest-plugin.tsx | 16 +++++---- .../compose/editor/plugins/mention-plugin.tsx | 8 +++-- .../compose/editor/plugins/state-plugin.tsx | 33 +++++++++++++++++++ .../compose-event-modal.tsx | 1 + 6 files changed, 57 insertions(+), 30 deletions(-) create mode 100644 app/soapbox/features/compose/editor/plugins/state-plugin.tsx diff --git a/app/soapbox/features/compose/components/compose-form.tsx b/app/soapbox/features/compose/components/compose-form.tsx index 57bbd76641..96669ff28a 100644 --- a/app/soapbox/features/compose/components/compose-form.tsx +++ b/app/soapbox/features/compose/components/compose-form.tsx @@ -275,6 +275,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab eventDiscussion={!!event} autoFocus={shouldAutoFocus} hasPoll={hasPoll} + handleSubmit={handleSubmit} onFocus={handleComposeFocus} onPaste={onPaste} /> diff --git a/app/soapbox/features/compose/editor/index.tsx b/app/soapbox/features/compose/editor/index.tsx index 4949d977e4..ef1a99d25f 100644 --- a/app/soapbox/features/compose/editor/index.tsx +++ b/app/soapbox/features/compose/editor/index.tsx @@ -9,7 +9,6 @@ LICENSE file in the /app/soapbox/features/compose/editor directory. import { $convertFromMarkdownString, $convertToMarkdownString } from '@lexical/markdown'; import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; 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'; @@ -20,33 +19,20 @@ 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 React, { 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 { AutosuggestPlugin } from './plugins/autosuggest-plugin'; +import AutosuggestPlugin from './plugins/autosuggest-plugin'; import DraggableBlockPlugin from './plugins/draggable-block-plugin'; import FloatingLinkEditorPlugin from './plugins/floating-link-editor-plugin'; import FloatingTextFormatToolbarPlugin from './plugins/floating-text-format-toolbar-plugin'; -import { MentionPlugin } from './plugins/mention-plugin'; +import MentionPlugin from './plugins/mention-plugin'; +import StatePlugin from './plugins/state-plugin'; import { TO_WYSIWYG_TRANSFORMERS } from './transformers'; -const StatePlugin = ({ composeId }: { composeId: string }) => { - const dispatch = useAppDispatch(); - const [editor] = useLexicalComposerContext(); - - useEffect(() => { - editor.registerUpdateListener(({ editorState }) => { - dispatch(setEditorState(composeId, editorState.isEmpty() ? null : JSON.stringify(editorState.toJSON()))); - }); - }, [editor]); - - return null; -}; - interface IComposeEditor { className?: string composeId: string @@ -54,6 +40,7 @@ interface IComposeEditor { eventDiscussion?: boolean hasPoll?: boolean autoFocus?: boolean + handleSubmit?: () => void onFocus?: React.FocusEventHandler onPaste?: (files: FileList) => void placeholder?: JSX.Element | string @@ -66,6 +53,7 @@ const ComposeEditor = React.forwardRef(({ eventDiscussion, hasPoll, autoFocus, + handleSubmit, onFocus, onPaste, placeholder, @@ -154,7 +142,7 @@ const ComposeEditor = React.forwardRef(({
+
alert('xd')}> (({ )} - +
); diff --git a/app/soapbox/features/compose/editor/plugins/autosuggest-plugin.tsx b/app/soapbox/features/compose/editor/plugins/autosuggest-plugin.tsx index 17ac6aa2e8..1c77fc8b3b 100644 --- a/app/soapbox/features/compose/editor/plugins/autosuggest-plugin.tsx +++ b/app/soapbox/features/compose/editor/plugins/autosuggest-plugin.tsx @@ -37,17 +37,17 @@ import { $createEmojiNode } from '../nodes/emoji-node'; import type { AutoSuggestion } from 'soapbox/components/autosuggest-input'; -export type QueryMatch = { +type QueryMatch = { leadOffset: number matchingString: string }; -export type Resolution = { +type Resolution = { match: QueryMatch getRect: () => DOMRect }; -export type MenuRenderFn = ( +type MenuRenderFn = ( anchorElementRef: MutableRefObject, ) => ReactPortal | JSX.Element | null; @@ -102,7 +102,7 @@ const startTransition = (callback: () => void) => { }; // Got from https://stackoverflow.com/a/42543908/2013580 -export const getScrollParent = ( +const getScrollParent = ( element: HTMLElement, includeHidden: boolean, ): HTMLElement | HTMLBodyElement => { @@ -142,7 +142,7 @@ const isTriggerVisibleInNearestScrollContainer = ( }; // Reposition the menu on scroll, window resize, and element resize. -export const useDynamicPositioning = ( +const useDynamicPositioning = ( resolution: Resolution | null, targetElement: HTMLElement | null, onReposition: () => void, @@ -269,13 +269,13 @@ const useMenuAnchorRef = ( return anchorElementRef; }; -export type AutosuggestPluginProps = { +type AutosuggestPluginProps = { composeId: string suggestionsHidden: boolean setSuggestionsHidden: (value: boolean) => void }; -export const AutosuggestPlugin = ({ +const AutosuggestPlugin = ({ composeId, suggestionsHidden, setSuggestionsHidden, @@ -469,3 +469,5 @@ export const AutosuggestPlugin = ({ /> ); }; + +export default AutosuggestPlugin; diff --git a/app/soapbox/features/compose/editor/plugins/mention-plugin.tsx b/app/soapbox/features/compose/editor/plugins/mention-plugin.tsx index 1bfbaabd49..f2746e3afb 100644 --- a/app/soapbox/features/compose/editor/plugins/mention-plugin.tsx +++ b/app/soapbox/features/compose/editor/plugins/mention-plugin.tsx @@ -14,16 +14,16 @@ import { $createMentionNode, MentionNode } from '../nodes/mention-node'; import type { TextNode } from 'lexical'; -export const MENTION_REGEX = new RegExp('(^|$|(?:^|\\s))([@])([a-z\\d_-]+(?:@[^@\\s]+)?)', 'i'); +const MENTION_REGEX = new RegExp('(^|$|(?:^|\\s))([@])([a-z\\d_-]+(?:@[^@\\s]+)?)', 'i'); -export const getMentionMatch = (text: string) => { +const getMentionMatch = (text: string) => { const matchArr = MENTION_REGEX.exec(text); if (!matchArr) return null; return matchArr; }; -export const MentionPlugin = (): JSX.Element | null => { +const MentionPlugin = (): JSX.Element | null => { const [editor] = useLexicalComposerContext(); useEffect(() => { @@ -58,3 +58,5 @@ export const MentionPlugin = (): JSX.Element | null => { return null; }; + +export default MentionPlugin; diff --git a/app/soapbox/features/compose/editor/plugins/state-plugin.tsx b/app/soapbox/features/compose/editor/plugins/state-plugin.tsx new file mode 100644 index 0000000000..031b6da5b6 --- /dev/null +++ b/app/soapbox/features/compose/editor/plugins/state-plugin.tsx @@ -0,0 +1,33 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { KEY_ENTER_COMMAND } from 'lexical'; +import { useEffect } from 'react'; + +import { setEditorState } from 'soapbox/actions/compose'; +import { useAppDispatch } from 'soapbox/hooks'; + +interface IStatePlugin { + composeId: string + handleSubmit?: () => void +} + +const StatePlugin = ({ composeId, handleSubmit }: IStatePlugin) => { + const dispatch = useAppDispatch(); + const [editor] = useLexicalComposerContext(); + + useEffect(() => { + if (handleSubmit) editor.registerCommand(KEY_ENTER_COMMAND, (event) => { + if (event?.ctrlKey) { + handleSubmit(); + return true; + } + return false; + }, 1); + editor.registerUpdateListener(({ editorState }) => { + dispatch(setEditorState(composeId, editorState.isEmpty() ? null : JSON.stringify(editorState.toJSON()))); + }); + }, [editor]); + + return null; +}; + +export default StatePlugin; diff --git a/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx b/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx index ff2e7a4da4..253921f48a 100644 --- a/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx +++ b/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx @@ -242,6 +242,7 @@ const ComposeEventModal: React.FC = ({ onClose }) => { className='block w-full rounded-md border-gray-400 bg-white px-3 py-2 text-base text-gray-900 placeholder:text-gray-600 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus:border-primary-500 dark:focus:ring-primary-500 sm:text-sm' composeId='compose-event-modal' placeholder={intl.formatMessage(messages.eventDescriptionPlaceholder)} + handleSubmit={handleSubmit} />