diff --git a/app/components/ContentEditable.tsx b/app/components/ContentEditable.tsx index 9d4f849d6..21ad646d3 100644 --- a/app/components/ContentEditable.tsx +++ b/app/components/ContentEditable.tsx @@ -20,78 +20,85 @@ type Props = Omit, "ref" | "onChange"> & { * Defines a content editable component with the same interface as a native * HTMLInputElement (or, as close as we can get). */ -function ContentEditable({ - disabled, - onChange, - onInput, - onBlur, - onKeyDown, - value, - children, - className, - maxLength, - autoFocus, - placeholder, - readOnly, - ...rest -}: Props) { - const ref = React.useRef(null); - const [innerHTML, setInnerHTML] = React.useState(value); - const lastValue = React.useRef(""); +const ContentEditable = React.forwardRef( + ( + { + disabled, + onChange, + onInput, + onBlur, + onKeyDown, + value, + children, + className, + maxLength, + autoFocus, + placeholder, + readOnly, + dir, + ...rest + }: Props, + forwardedRef: React.RefObject + ) => { + const innerRef = React.useRef(null); + const ref = forwardedRef || innerRef; + const [innerHTML, setInnerHTML] = React.useState(value); + const lastValue = React.useRef(""); - const wrappedEvent = ( - callback: - | React.FocusEventHandler - | React.FormEventHandler - | React.KeyboardEventHandler - | undefined - ) => (event: any) => { - const text = ref.current?.innerText || ""; + const wrappedEvent = ( + callback: + | React.FocusEventHandler + | React.FormEventHandler + | React.KeyboardEventHandler + | undefined + ) => (event: any) => { + const text = ref.current?.innerText || ""; - if (maxLength && isPrintableKeyEvent(event) && text.length >= maxLength) { - event?.preventDefault(); - return; - } + if (maxLength && isPrintableKeyEvent(event) && text.length >= maxLength) { + event?.preventDefault(); + return; + } - if (text !== lastValue.current) { - lastValue.current = text; - onChange && onChange(text); - } + if (text !== lastValue.current) { + lastValue.current = text; + onChange && onChange(text); + } - callback?.(event); - }; + callback?.(event); + }; - React.useLayoutEffect(() => { - if (autoFocus) { - ref.current?.focus(); - } - }); + React.useLayoutEffect(() => { + if (autoFocus) { + ref.current?.focus(); + } + }); - React.useEffect(() => { - if (value !== ref.current?.innerText) { - setInnerHTML(value); - } - }, [value]); + React.useEffect(() => { + if (value !== ref.current?.innerText) { + setInnerHTML(value); + } + }, [value, ref]); - return ( -
- - {children} -
- ); -} + return ( +
+ + {children} +
+ ); + } +); const Content = styled.span` &:empty { @@ -108,4 +115,4 @@ const Content = styled.span` } `; -export default React.memo(ContentEditable); +export default ContentEditable; diff --git a/app/scenes/Document/components/EditableTitle.tsx b/app/scenes/Document/components/EditableTitle.tsx index 113e33a55..a511032ac 100644 --- a/app/scenes/Document/components/EditableTitle.tsx +++ b/app/scenes/Document/components/EditableTitle.tsx @@ -27,86 +27,92 @@ type Props = { onSave?: (options: { publish?: boolean; done?: boolean }) => void; }; -function EditableTitle({ - value, - document, - readOnly, - onChange, - onSave, - onGoToNextInput, - starrable, -}: Props) { - const { policies } = useStores(); - const { t } = useTranslation(); - const can = policies.abilities(document.id); - const { emoji } = parseTitle(value); - const startsWithEmojiAndSpace = !!(emoji && value.startsWith(`${emoji} `)); - const normalizedTitle = - !value && readOnly ? document.titleWithDefault : value; +const EditableTitle = React.forwardRef( + ( + { + value, + document, + readOnly, + onChange, + onSave, + onGoToNextInput, + starrable, + }: Props, + ref: React.RefObject + ) => { + const { policies } = useStores(); + const { t } = useTranslation(); + const can = policies.abilities(document.id); + const { emoji } = parseTitle(value); + const startsWithEmojiAndSpace = !!(emoji && value.startsWith(`${emoji} `)); + const normalizedTitle = + !value && readOnly ? document.titleWithDefault : value; - const handleKeyDown = React.useCallback( - (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); - if (isModKey(event)) { + if (isModKey(event)) { + onSave?.({ + done: true, + }); + return; + } + + onGoToNextInput(true); + return; + } + + if (event.key === "Tab" || event.key === "ArrowDown") { + event.preventDefault(); + onGoToNextInput(); + return; + } + + if (event.key === "p" && isModKey(event) && event.shiftKey) { + event.preventDefault(); onSave?.({ + publish: true, done: true, }); return; } - onGoToNextInput(true); - return; - } + if (event.key === "s" && isModKey(event)) { + event.preventDefault(); + onSave?.({}); + return; + } + }, + [onGoToNextInput, onSave] + ); - if (event.key === "Tab" || event.key === "ArrowDown") { - event.preventDefault(); - onGoToNextInput(); - return; - } - - if (event.key === "p" && isModKey(event) && event.shiftKey) { - event.preventDefault(); - onSave?.({ - publish: true, - done: true, - }); - return; - } - - if (event.key === "s" && isModKey(event)) { - event.preventDefault(); - onSave?.({}); - return; - } - }, - [onGoToNextInput, onSave] - ); - - return ( - - {(can.star || can.unstar) && starrable !== false && ( - <StarButton document={document} size={32} /> - )} - - ); -} + return ( + + {(can.star || can.unstar) && starrable !== false && ( + <StarButton document={document} size={32} /> + )} + + ); + } +); const StarButton = styled(Star)` position: relative; diff --git a/app/scenes/Document/components/Editor.tsx b/app/scenes/Document/components/Editor.tsx index 7fc61b7b1..93e2390cc 100644 --- a/app/scenes/Document/components/Editor.tsx +++ b/app/scenes/Document/components/Editor.tsx @@ -37,6 +37,7 @@ class DocumentEditor extends React.Component { activeLinkEvent: MouseEvent | null | undefined; ref = React.createRef(); + titleRef = React.createRef(); focusAtStart = () => { if (this.props.innerRef.current) { @@ -94,6 +95,7 @@ class DocumentEditor extends React.Component { return ( { document={document} to={documentHistoryUrl(document)} rtl={ - this.ref.current - ? window.getComputedStyle(this.ref.current).direction === "rtl" + this.titleRef.current + ? window.getComputedStyle(this.titleRef.current).direction === + "rtl" : false } />