From 929642195d3fabbe4c0cb01dea07868c096e00c4 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Sun, 2 Jul 2023 14:45:49 +0530 Subject: [PATCH] feat: Link and formatting toolbars for super are now unified on web/desktop to allow formatting existing links and updated link editor on mobile (#2342) --- .../Utils/getAdjustedStylesForNonPortal.ts | 8 +- .../Lexical/Theme/IconComponent.tsx | 2 +- .../SuperEditor/Lexical/UI/LinkPreview.css | 69 ------ .../SuperEditor/Lexical/UI/LinkPreview.tsx | 106 --------- .../FloatingLinkEditorPlugin/LinkEditor.tsx | 120 +++++++++++ .../FloatingLinkEditorPlugin/index.tsx | 128 +++++------ .../FloatingTextFormatToolbarPlugin/index.css | 128 ----------- .../FloatingTextFormatToolbarPlugin/index.tsx | 202 ++++++++++++------ 8 files changed, 315 insertions(+), 448 deletions(-) delete mode 100644 packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/LinkPreview.css delete mode 100644 packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/LinkPreview.tsx create mode 100644 packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingLinkEditorPlugin/LinkEditor.tsx delete mode 100644 packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingTextFormatToolbarPlugin/index.css diff --git a/packages/web/src/javascripts/Components/Popover/Utils/getAdjustedStylesForNonPortal.ts b/packages/web/src/javascripts/Components/Popover/Utils/getAdjustedStylesForNonPortal.ts index b867e4ef7..948d88285 100644 --- a/packages/web/src/javascripts/Components/Popover/Utils/getAdjustedStylesForNonPortal.ts +++ b/packages/web/src/javascripts/Components/Popover/Utils/getAdjustedStylesForNonPortal.ts @@ -1,8 +1,12 @@ import { PopoverCSSProperties } from '../GetPositionedPopoverStyles' import { getAbsolutePositionedParent } from './getAbsolutePositionedParent' -export const getAdjustedStylesForNonPortalPopover = (popoverElement: HTMLElement, styles: PopoverCSSProperties) => { - const absoluteParent = getAbsolutePositionedParent(popoverElement) +export const getAdjustedStylesForNonPortalPopover = ( + popoverElement: HTMLElement, + styles: PopoverCSSProperties, + parent?: HTMLElement, +) => { + const absoluteParent = parent || getAbsolutePositionedParent(popoverElement) const translateXProperty = styles?.['--translate-x'] const translateYProperty = styles?.['--translate-y'] diff --git a/packages/web/src/javascripts/Components/SuperEditor/Lexical/Theme/IconComponent.tsx b/packages/web/src/javascripts/Components/SuperEditor/Lexical/Theme/IconComponent.tsx index a5123de42..6c2eb6f82 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Lexical/Theme/IconComponent.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Lexical/Theme/IconComponent.tsx @@ -8,7 +8,7 @@ export const IconComponent = ({ paddingTop?: number }) => { return ( - + {children} ) diff --git a/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/LinkPreview.css b/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/LinkPreview.css deleted file mode 100644 index 4633a8ca2..000000000 --- a/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/LinkPreview.css +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * - */ - -@keyframes glimmer-animation { - 0% { - background: #f9f9f9; - } - .50% { - background: #eeeeee; - } - .100% { - background: #f9f9f9; - } -} - -.LinkPreview__container { - padding-bottom: 12px; -} - -.LinkPreview__imageWrapper { - text-align: center; -} - -.LinkPreview__image { - max-width: 100%; - max-height: 250px; - margin: auto; -} - -.LinkPreview__title { - margin-left: 12px; - margin-right: 12px; - margin-top: 4px; -} - -.LinkPreview__description { - color: #999; - font-size: 90%; - margin-left: 12px; - margin-right: 12px; - margin-top: 4px; -} - -.LinkPreview__domain { - color: #999; - font-size: 90%; - margin-left: 12px; - margin-right: 12px; - margin-top: 4px; -} - -.LinkPreview__glimmer { - background: #f9f9f9; - border-radius: 8px; - height: 18px; - margin-bottom: 8px; - margin-left: 12px; - margin-right: 12px; - animation-duration: 3s; - animation-iteration-count: infinite; - animation-timing-function: linear; - animation-name: glimmer-animation; -} diff --git a/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/LinkPreview.tsx b/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/LinkPreview.tsx deleted file mode 100644 index 54ad7716d..000000000 --- a/packages/web/src/javascripts/Components/SuperEditor/Lexical/UI/LinkPreview.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import './LinkPreview.css' - -import { CSSProperties, Suspense } from 'react' - -type Preview = { - title: string - description: string - img: string - domain: string -} | null - -// Cached responses or running request promises -const PREVIEW_CACHE: Record | { preview: Preview }> = {} - -const URL_MATCHER = - /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/ - -function useSuspenseRequest(url: string) { - let cached = PREVIEW_CACHE[url] - - if (!url.match(URL_MATCHER)) { - return { preview: null } - } - - if (!cached) { - cached = PREVIEW_CACHE[url] = fetch(`/api/link-preview?url=${encodeURI(url)}`) - .then((response) => response.json()) - .then((preview) => { - PREVIEW_CACHE[url] = preview - return preview - }) - .catch(() => { - PREVIEW_CACHE[url] = { preview: null } - }) - } - - if (cached instanceof Promise) { - throw cached - } - - return cached -} - -function LinkPreviewContent({ - url, -}: Readonly<{ - url: string -}>): JSX.Element | null { - const { preview } = useSuspenseRequest(url) - if (preview === null) { - return null - } - return ( -
- {preview.img && ( -
- {preview.title} -
- )} - {preview.domain &&
{preview.domain}
} - {preview.title &&
{preview.title}
} - {preview.description &&
{preview.description}
} -
- ) -} - -function Glimmer(props: { style: CSSProperties; index: number }): JSX.Element { - return ( -
- ) -} - -export default function LinkPreview({ - url, -}: Readonly<{ - url: string -}>): JSX.Element { - return ( - - - - - - } - > - - - ) -} diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingLinkEditorPlugin/LinkEditor.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingLinkEditorPlugin/LinkEditor.tsx new file mode 100644 index 000000000..a3d37c45d --- /dev/null +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingLinkEditorPlugin/LinkEditor.tsx @@ -0,0 +1,120 @@ +import Icon from '@/Components/Icon/Icon' +import { CloseIcon, CheckIcon, PencilFilledIcon, TrashFilledIcon } from '@standardnotes/icons' +import { KeyboardKey } from '@standardnotes/ui-services' +import { IconComponent } from '../../Lexical/Theme/IconComponent' +import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl' +import { TOGGLE_LINK_COMMAND } from '@lexical/link' +import { useCallback, useState } from 'react' +import { GridSelection, LexicalEditor, NodeSelection, RangeSelection } from 'lexical' + +type Props = { + linkUrl: string + isEditMode: boolean + setEditMode: (isEditMode: boolean) => void + editor: LexicalEditor + lastSelection: RangeSelection | GridSelection | NodeSelection | null +} + +const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, lastSelection }: Props) => { + const [editedLinkUrl, setEditedLinkUrl] = useState('') + + const handleLinkSubmission = () => { + if (lastSelection !== null) { + if (linkUrl !== '') { + editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl)) + } + setEditMode(false) + } + } + + const focusInput = useCallback((input: HTMLInputElement | null) => { + if (input) { + input.focus() + } + }, []) + + return isEditMode ? ( +
+ { + setEditedLinkUrl(event.target.value) + }} + onKeyDown={(event) => { + if (event.key === KeyboardKey.Enter) { + event.preventDefault() + handleLinkSubmission() + } else if (event.key === KeyboardKey.Escape) { + event.preventDefault() + setEditMode(false) + } + }} + className="flex-grow rounded-sm bg-contrast p-1 text-text sm:min-w-[40ch]" + /> + + +
+ ) : ( +
+ + +
{linkUrl}
+
+ + +
+ ) +} + +export default LinkEditor diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingLinkEditorPlugin/index.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingLinkEditorPlugin/index.tsx index 097ffd757..fa7476dc2 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingLinkEditorPlugin/index.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingLinkEditorPlugin/index.tsx @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * */ -import { $isAutoLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link' +import { $isAutoLinkNode, $isLinkNode } from '@lexical/link' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { $findMatchingParent, mergeRegister } from '@lexical/utils' import { @@ -22,18 +22,14 @@ import { import { useCallback, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' -import LinkPreview from '../../Lexical/UI/LinkPreview' import { getSelectedNode } from '../../Lexical/Utils/getSelectedNode' -import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl' -import { setFloatingElemPosition } from '../../Lexical/Utils/setFloatingElemPosition' -import { LexicalPencilFill } from '@standardnotes/icons' -import { IconComponent } from '../../Lexical/../Lexical/Theme/IconComponent' import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect' -import { KeyboardKey } from '@standardnotes/ui-services' +import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPopoverStyles' +import { getAdjustedStylesForNonPortalPopover } from '@/Components/Popover/Utils/getAdjustedStylesForNonPortal' +import LinkEditor from './LinkEditor' function FloatingLinkEditor({ editor, anchorElem }: { editor: LexicalEditor; anchorElem: HTMLElement }): JSX.Element { const editorRef = useRef(null) - const inputRef = useRef(null) const [linkUrl, setLinkUrl] = useState('') const [isEditMode, setEditMode] = useState(false) const [lastSelection, setLastSelection] = useState(null) @@ -67,21 +63,36 @@ function FloatingLinkEditor({ editor, anchorElem }: { editor: LexicalEditor; anc rootElement !== null && rootElement.contains(nativeSelection.anchorNode) ) { + setLastSelection(selection) + const rect = getDOMRangeRect(nativeSelection, rootElement) - setFloatingElemPosition(rect, editorElem, anchorElem) - setLastSelection(selection) - } else if (!activeElement || activeElement.className !== 'link-input') { - if (rootElement !== null) { - setFloatingElemPosition(null, editorElem, anchorElem) + const editorRect = editorElem.getBoundingClientRect() + const rootElementRect = rootElement.getBoundingClientRect() + + const calculatedStyles = getPositionedPopoverStyles({ + align: 'start', + side: 'top', + anchorRect: rect, + popoverRect: editorRect, + documentRect: rootElementRect, + offset: 8, + disableMobileFullscreenTakeover: true, + }) + + if (calculatedStyles) { + Object.assign(editorElem.style, calculatedStyles) + const adjustedStyles = getAdjustedStylesForNonPortalPopover(editorElem, calculatedStyles, rootElement) + editorElem.style.setProperty('--translate-x', adjustedStyles['--translate-x']) + editorElem.style.setProperty('--translate-y', adjustedStyles['--translate-y']) } + } else if (!activeElement || activeElement.id !== 'link-input') { setLastSelection(null) setEditMode(false) - setLinkUrl('') } return true - }, [anchorElem, editor]) + }, [editor]) useEffect(() => { const scrollerElem = anchorElem.parentElement @@ -132,60 +143,18 @@ function FloatingLinkEditor({ editor, anchorElem }: { editor: LexicalEditor; anc }) }, [editor, updateLinkEditor]) - useEffect(() => { - if (isEditMode && inputRef.current) { - inputRef.current.focus() - } - }, [isEditMode]) - return ( -
- {isEditMode ? ( - { - setLinkUrl(event.target.value) - }} - onKeyDown={(event) => { - if (event.key === KeyboardKey.Enter) { - event.preventDefault() - if (lastSelection !== null) { - if (linkUrl !== '') { - editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(linkUrl)) - } - setEditMode(false) - } - } else if (event.key === KeyboardKey.Escape) { - event.preventDefault() - setEditMode(false) - } - }} - /> - ) : ( - <> -
- - {linkUrl} - -
event.preventDefault()} - onClick={() => { - setEditMode(true) - }} - > - - - -
-
- - - )} +
+
) } @@ -210,14 +179,21 @@ function useFloatingLinkEditorToolbar(editor: LexicalEditor, anchorElem: HTMLEle }, []) useEffect(() => { - return editor.registerCommand( - SELECTION_CHANGE_COMMAND, - (_payload, newEditor) => { - updateToolbar() - setActiveEditor(newEditor) - return false - }, - COMMAND_PRIORITY_CRITICAL, + return mergeRegister( + editor.registerUpdateListener(({ editorState }) => { + editorState.read(() => { + updateToolbar() + }) + }), + editor.registerCommand( + SELECTION_CHANGE_COMMAND, + (_payload, newEditor) => { + updateToolbar() + setActiveEditor(newEditor) + return false + }, + COMMAND_PRIORITY_CRITICAL, + ), ) }, [editor, updateToolbar]) diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingTextFormatToolbarPlugin/index.css b/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingTextFormatToolbarPlugin/index.css deleted file mode 100644 index c45b46279..000000000 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingTextFormatToolbarPlugin/index.css +++ /dev/null @@ -1,128 +0,0 @@ -.floating-text-format-popup { - display: flex; - vertical-align: middle; - position: absolute; - top: 0; - left: 0; - z-index: 10; - opacity: 0; - background-color: var(--sn-stylekit-contrast-background-color); - box-shadow: 0px 5px 10px var(--sn-stylekit-shadow-color); - border-radius: 8px; - transition: opacity 0.5s; - will-change: transform; -} - -.floating-text-format-popup button.popup-item { - border: 0; - display: flex; - background: none; - border-radius: 10px; - padding: 12px; - cursor: pointer; - vertical-align: middle; -} - -.floating-text-format-popup button.popup-item:disabled { - cursor: not-allowed; -} - -.floating-text-format-popup button.popup-item.spaced { - margin-right: 2px; -} - -.floating-text-format-popup button.popup-item i.format { - background-size: contain; - display: inline-block; - height: 18px; - width: 18px; - margin-top: 2px; - vertical-align: -0.25em; - display: flex; - opacity: 0.6; -} - -.floating-text-format-popup button.popup-item:disabled i.format { - opacity: 0.2; -} - -.floating-text-format-popup button.popup-item.active { - background-color: rgba(223, 232, 250, 0.3); -} - -.floating-text-format-popup button.popup-item.active i { - opacity: 1; -} - -.floating-text-format-popup .popup-item:hover:not([disabled]) { - background-color: var(--sn-stylekit-info-color); - color: var(--sn-stylekit-info-contrast-color); -} - -.floating-text-format-popup select.popup-item { - border: 0; - display: flex; - background: none; - border-radius: 10px; - padding: 8px; - vertical-align: middle; - -webkit-appearance: none; - -moz-appearance: none; - width: 70px; - font-size: 14px; - color: #777; - text-overflow: ellipsis; -} - -.floating-text-format-popup select.code-language { - text-transform: capitalize; - width: 130px; -} - -.floating-text-format-popup .popup-item .text { - display: flex; - line-height: 20px; - width: 200px; - vertical-align: middle; - font-size: 14px; - color: #777; - text-overflow: ellipsis; - width: 70px; - overflow: hidden; - height: 20px; - text-align: left; -} - -.floating-text-format-popup .popup-item .icon { - display: flex; - width: 20px; - height: 20px; - user-select: none; - margin-right: 8px; - line-height: 16px; - background-size: contain; -} - -.floating-text-format-popup i.chevron-down { - margin-top: 3px; - width: 16px; - height: 16px; - display: flex; - user-select: none; -} - -.floating-text-format-popup i.chevron-down.inside { - width: 16px; - height: 16px; - display: flex; - margin-left: -25px; - margin-top: 11px; - margin-right: 10px; - pointer-events: none; -} - -.floating-text-format-popup .divider { - width: 1px; - background-color: #eee; - margin: 0 4px; -} diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingTextFormatToolbarPlugin/index.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingTextFormatToolbarPlugin/index.tsx index 19292e6dc..c707d3a8f 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingTextFormatToolbarPlugin/index.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/FloatingTextFormatToolbarPlugin/index.tsx @@ -6,8 +6,6 @@ * */ -import './index.css' - import { $isCodeHighlightNode } from '@lexical/code' import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' @@ -16,12 +14,15 @@ import { $getSelection, $isRangeSelection, $isTextNode, - COMMAND_PRIORITY_LOW, FORMAT_TEXT_COMMAND, LexicalEditor, SELECTION_CHANGE_COMMAND, $isRootOrShadowRoot, COMMAND_PRIORITY_CRITICAL, + COMMAND_PRIORITY_LOW, + RangeSelection, + GridSelection, + NodeSelection, } from 'lexical' import { $isHeadingNode } from '@lexical/rich-text' import { @@ -31,12 +32,10 @@ import { ListNode, INSERT_ORDERED_LIST_COMMAND, } from '@lexical/list' -import { useCallback, useEffect, useRef, useState } from 'react' +import { ComponentPropsWithoutRef, useCallback, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' -import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect' import { getSelectedNode } from '../../Lexical/Utils/getSelectedNode' -import { setFloatingElemPosition } from '../../Lexical/Utils/setFloatingElemPosition' import { BoldIcon, ItalicIcon, @@ -51,6 +50,11 @@ import { } from '@standardnotes/icons' import { IconComponent } from '../../Lexical/Theme/IconComponent' import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl' +import { classNames } from '@standardnotes/snjs' +import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect' +import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPopoverStyles' +import { getAdjustedStylesForNonPortalPopover } from '@/Components/Popover/Utils/getAdjustedStylesForNonPortal' +import LinkEditor from '../FloatingLinkEditorPlugin/LinkEditor' const blockTypeToBlockName = { bullet: 'Bulleted List', @@ -69,9 +73,24 @@ const blockTypeToBlockName = { const IconSize = 15 +const ToolbarButton = ({ active, ...props }: { active?: boolean } & ComponentPropsWithoutRef<'button'>) => { + return ( + + ) +} + function TextFormatFloatingToolbar({ editor, anchorElem, + isText, isLink, isBold, isItalic, @@ -85,6 +104,7 @@ function TextFormatFloatingToolbar({ }: { editor: LexicalEditor anchorElem: HTMLElement + isText: boolean isBold: boolean isCode: boolean isItalic: boolean @@ -95,8 +115,12 @@ function TextFormatFloatingToolbar({ isUnderline: boolean isBulletedList: boolean isNumberedList: boolean -}): JSX.Element { - const popupCharStylesEditorRef = useRef(null) +}) { + const toolbarRef = useRef(null) + + const [linkUrl, setLinkUrl] = useState('') + const [isLinkEditMode, setIsLinkEditMode] = useState(false) + const [lastSelection, setLastSelection] = useState(null) const insertLink = useCallback(() => { if (!isLink) { @@ -130,36 +154,72 @@ function TextFormatFloatingToolbar({ } }, [editor, isNumberedList]) - const updateTextFormatFloatingToolbar = useCallback(() => { + const updateToolbar = useCallback(() => { const selection = $getSelection() + if ($isRangeSelection(selection)) { + const node = getSelectedNode(selection) + const parent = node.getParent() + if ($isLinkNode(parent)) { + setLinkUrl(parent.getURL()) + } else if ($isLinkNode(node)) { + setLinkUrl(node.getURL()) + } else { + setLinkUrl('') + } + } - const popupCharStylesEditorElem = popupCharStylesEditorRef.current - const nativeSelection = window.getSelection() + const toolbarElement = toolbarRef.current - if (popupCharStylesEditorElem === null) { + if (!toolbarElement) { return } + const nativeSelection = window.getSelection() + const activeElement = document.activeElement const rootElement = editor.getRootElement() + if ( selection !== null && nativeSelection !== null && - !nativeSelection.isCollapsed && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) ) { - const rangeRect = getDOMRangeRect(nativeSelection, rootElement) + setLastSelection(selection) - setFloatingElemPosition(rangeRect, popupCharStylesEditorElem, anchorElem) + const rangeRect = getDOMRangeRect(nativeSelection, rootElement) + const toolbarRect = toolbarElement.getBoundingClientRect() + const rootElementRect = rootElement.getBoundingClientRect() + + const calculatedStyles = getPositionedPopoverStyles({ + align: 'start', + side: 'top', + anchorRect: rangeRect, + popoverRect: toolbarRect, + documentRect: rootElementRect, + offset: 8, + }) + + if (calculatedStyles) { + Object.assign(toolbarElement.style, calculatedStyles) + const adjustedStyles = getAdjustedStylesForNonPortalPopover(toolbarElement, calculatedStyles, rootElement) + toolbarElement.style.setProperty('--translate-x', adjustedStyles['--translate-x']) + toolbarElement.style.setProperty('--translate-y', adjustedStyles['--translate-y']) + } + } else if (!activeElement || activeElement.id !== 'link-input') { + setLastSelection(null) + setIsLinkEditMode(false) + setLinkUrl('') } - }, [editor, anchorElem]) + + return true + }, [editor]) useEffect(() => { const scrollerElem = anchorElem.parentElement const update = () => { editor.getEditorState().read(() => { - updateTextFormatFloatingToolbar() + updateToolbar() }) } @@ -174,141 +234,150 @@ function TextFormatFloatingToolbar({ scrollerElem.removeEventListener('scroll', update) } } - }, [editor, updateTextFormatFloatingToolbar, anchorElem]) + }, [editor, anchorElem, updateToolbar]) useEffect(() => { editor.getEditorState().read(() => { - updateTextFormatFloatingToolbar() + updateToolbar() }) return mergeRegister( editor.registerUpdateListener(({ editorState }) => { editorState.read(() => { - updateTextFormatFloatingToolbar() + updateToolbar() }) }), editor.registerCommand( SELECTION_CHANGE_COMMAND, () => { - updateTextFormatFloatingToolbar() + updateToolbar() return false }, COMMAND_PRIORITY_LOW, ), ) - }, [editor, updateTextFormatFloatingToolbar]) + }, [editor, updateToolbar]) + + useEffect(() => { + editor.getEditorState().read(() => updateToolbar()) + }, [editor, isLink, isText, updateToolbar]) + + if (!editor.isEditable()) { + return null + } return ( -
- {editor.isEditable() && ( - <> - - - - - - - - - - - + +
)}
) @@ -436,7 +505,7 @@ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLEle ) }, [editor, updatePopup]) - if (!isText || isLink) { + if (!isText && !isLink) { return null } @@ -444,6 +513,7 @@ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLEle