Lexical: Cleanup, allow ctrl+enter to submit
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
e76a9ec8aa
commit
461a002be9
6 changed files with 57 additions and 30 deletions
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
33
app/soapbox/features/compose/editor/plugins/state-plugin.tsx
Normal file
33
app/soapbox/features/compose/editor/plugins/state-plugin.tsx
Normal 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;
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue