Lexical: Cleanup, allow ctrl+enter to submit

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2023-04-19 00:22:22 +02:00
parent e76a9ec8aa
commit 461a002be9
6 changed files with 57 additions and 30 deletions

View file

@ -275,6 +275,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
eventDiscussion={!!event}
autoFocus={shouldAutoFocus}
hasPoll={hasPoll}
handleSubmit={handleSubmit}
onFocus={handleComposeFocus}
onPaste={onPaste}
/>

View file

@ -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<HTMLDivElement>
onPaste?: (files: FileList) => void
placeholder?: JSX.Element | string
@ -66,6 +53,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
eventDiscussion,
hasPoll,
autoFocus,
handleSubmit,
onFocus,
onPaste,
placeholder,
@ -154,7 +142,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
<div className={clsx('lexical relative', className)} data-markup>
<RichTextPlugin
contentEditable={
<div className='editor' ref={onRef} onFocus={onFocus} onPaste={handlePaste}>
<div className='editor' ref={onRef} onFocus={onFocus} onPaste={handlePaste} onSubmit={() => alert('xd')}>
<ContentEditable
className={clsx('mr-4 outline-none transition-[min-height] motion-reduce:transition-none', {
'min-fh-[40px]': condensed,
@ -193,7 +181,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
<FloatingLinkEditorPlugin anchorElem={floatingAnchorElem} />
</>
)}
<StatePlugin composeId={composeId} />
<StatePlugin composeId={composeId} handleSubmit={handleSubmit} />
</div>
</LexicalComposer>
);

View file

@ -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<HTMLElement | null>,
) => 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;

View file

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

View file

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

View file

@ -242,6 +242,7 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ 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}
/>
</FormGroup>
<FormGroup