Lexical: Fix autofocus

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2023-07-22 17:20:47 +02:00
parent 176a3b5ece
commit b3f9edd41e
4 changed files with 58 additions and 23 deletions

View File

@ -6,7 +6,6 @@ Copyright (c) Meta Platforms, Inc. and affiliates.
This source code is licensed under the MIT license found in the This source code is licensed under the MIT license found in the
LICENSE file in the /app/soapbox/features/compose/editor directory. LICENSE file in the /app/soapbox/features/compose/editor directory.
*/ */
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';
import { AutoLinkPlugin, createLinkMatcherWithRegExp } from '@lexical/react/LexicalAutoLinkPlugin'; import { AutoLinkPlugin, createLinkMatcherWithRegExp } from '@lexical/react/LexicalAutoLinkPlugin';
import { LexicalComposer, InitialConfigType } from '@lexical/react/LexicalComposer'; import { LexicalComposer, InitialConfigType } from '@lexical/react/LexicalComposer';
import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { ContentEditable } from '@lexical/react/LexicalContentEditable';
@ -25,6 +24,15 @@ import { FormattedMessage } from 'react-intl';
import { useAppDispatch, useFeatures } from 'soapbox/hooks'; import { useAppDispatch, useFeatures } from 'soapbox/hooks';
import { useNodes } from './nodes';
import AutosuggestPlugin from './plugins/autosuggest-plugin';
import FloatingBlockTypeToolbarPlugin from './plugins/floating-block-type-toolbar-plugin';
import FloatingLinkEditorPlugin from './plugins/floating-link-editor-plugin';
import FloatingTextFormatToolbarPlugin from './plugins/floating-text-format-toolbar-plugin';
import FocusPlugin from './plugins/focus-plugin';
import MentionPlugin from './plugins/mention-plugin';
import StatePlugin from './plugins/state-plugin';
const LINK_MATCHERS = [ const LINK_MATCHERS = [
createLinkMatcherWithRegExp( createLinkMatcherWithRegExp(
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/,
@ -32,14 +40,6 @@ const LINK_MATCHERS = [
), ),
]; ];
import { useNodes } from './nodes';
import AutosuggestPlugin from './plugins/autosuggest-plugin';
import FloatingBlockTypeToolbarPlugin from './plugins/floating-block-type-toolbar-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 StatePlugin from './plugins/state-plugin';
interface IComposeEditor { interface IComposeEditor {
className?: string className?: string
placeholderClassName?: string placeholderClassName?: string
@ -104,7 +104,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
return compose.editorState; return compose.editorState;
} }
return function() { return () => {
if (compose.content_type === 'text/markdown') { if (compose.content_type === 'text/markdown') {
$createRemarkImport({})(compose.text); $createRemarkImport({})(compose.text);
} else { } else {
@ -152,11 +152,10 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
contentEditable={ contentEditable={
<div className='editor' ref={onRef} onFocus={onFocus} onPaste={handlePaste}> <div className='editor' ref={onRef} onFocus={onFocus} onPaste={handlePaste}>
<ContentEditable <ContentEditable
className={clsx('mr-4 pb-8 outline-none transition-[min-height] motion-reduce:transition-none', { className={clsx('outline-none transition-[min-height] motion-reduce:transition-none', {
'min-h-[40px]': condensed, 'min-h-[40px] pb-4': condensed,
'min-h-[100px]': !condensed, 'min-h-[100px] pb-8': !condensed,
})} })}
autoFocus={autoFocus}
/> />
</div> </div>
} }
@ -172,7 +171,6 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
)} )}
ErrorBoundary={LexicalErrorBoundary} ErrorBoundary={LexicalErrorBoundary}
/> />
{autoFocus && <AutoFocusPlugin />}
<OnChangePlugin onChange={(_, editor) => { <OnChangePlugin onChange={(_, editor) => {
if (editorStateRef) (editorStateRef as any).current = editor.getEditorState().read($createRemarkExport()); if (editorStateRef) (editorStateRef as any).current = editor.getEditorState().read($createRemarkExport());
}} }}
@ -192,6 +190,7 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
</> </>
)} )}
<StatePlugin composeId={composeId} handleSubmit={handleSubmit} /> <StatePlugin composeId={composeId} handleSubmit={handleSubmit} />
<FocusPlugin autoFocus={autoFocus} />
</div> </div>
</LexicalComposer> </LexicalComposer>
); );

View File

@ -42,7 +42,7 @@ import type {
const imageCache = new Set(); const imageCache = new Set();
function useSuspenseImage(src: string) { const useSuspenseImage = (src: string) => {
if (!imageCache.has(src)) { if (!imageCache.has(src)) {
throw new Promise((resolve) => { throw new Promise((resolve) => {
const img = new Image(); const img = new Image();
@ -53,9 +53,9 @@ function useSuspenseImage(src: string) {
}; };
}); });
} }
} };
function LazyImage({ const LazyImage = ({
altText, altText,
className, className,
imageRef, imageRef,
@ -65,7 +65,7 @@ function LazyImage({
className: string | null className: string | null
imageRef: {current: null | HTMLImageElement} imageRef: {current: null | HTMLImageElement}
src: string src: string
}): JSX.Element { }): JSX.Element => {
useSuspenseImage(src); useSuspenseImage(src);
return ( return (
<img <img
@ -76,9 +76,9 @@ function LazyImage({
draggable='false' draggable='false'
/> />
); );
} };
export default function ImageComponent({ const ImageComponent = ({
src, src,
altText, altText,
nodeKey, nodeKey,
@ -86,7 +86,7 @@ export default function ImageComponent({
altText: string altText: string
nodeKey: NodeKey nodeKey: NodeKey
src: string src: string
}): JSX.Element { }): JSX.Element => {
const imageRef = useRef<null | HTMLImageElement>(null); const imageRef = useRef<null | HTMLImageElement>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null); const buttonRef = useRef<HTMLButtonElement | null>(null);
const [isSelected, setSelected, clearSelection] = const [isSelected, setSelected, clearSelection] =
@ -270,4 +270,6 @@ export default function ImageComponent({
</> </>
</Suspense> </Suspense>
); );
} };
export default ImageComponent;

View File

@ -0,0 +1,33 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
interface IFocusPlugin {
autoFocus?: boolean
}
const FocusPlugin: React.FC<IFocusPlugin> = ({ autoFocus }) => {
const [editor] = useLexicalComposerContext();
const focus = () => {
editor.focus(
() => {
const activeElement = document.activeElement;
const rootElement = editor.getRootElement();
if (
rootElement !== null &&
(activeElement === null || !rootElement.contains(activeElement))
) {
rootElement.focus({ preventScroll: true });
}
}, { defaultSelection: 'rootEnd' },
);
};
useEffect(() => {
if (autoFocus) focus();
}, []);
return null;
};
export default FocusPlugin;

View File

@ -86,6 +86,7 @@ const ComposeModal: React.FC<IComposeModal> = ({ onClose, composeId = 'compose-m
<ComposeForm <ComposeForm
id={composeId} id={composeId}
extra={<ComposeFormGroupToggle composeId={composeId} groupId={groupId} />} extra={<ComposeFormGroupToggle composeId={composeId} groupId={groupId} />}
autoFocus
/> />
</Modal> </Modal>
); );