diff --git a/packages/web/package.json b/packages/web/package.json index e0cb4aaa5..131b0a86a 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -110,6 +110,7 @@ "dependencies": { "@ariakit/react": "^0.3.9", "@lexical/headless": "0.13.1", + "@lexical/link": "0.13.1", "@lexical/list": "0.13.1", "@lexical/rich-text": "0.13.1", "@lexical/utils": "0.13.1", diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/FloatingLinkEditor.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/FloatingLinkEditor.tsx deleted file mode 100644 index d7108e962..000000000 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/FloatingLinkEditor.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPopoverStyles' -import { - $getSelection, - $isRangeSelection, - COMMAND_PRIORITY_LOW, - LexicalEditor, - SELECTION_CHANGE_COMMAND, -} from 'lexical' -import { useCallback, useEffect, useRef, useState } from 'react' -import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect' -import { classNames } from '@standardnotes/snjs' -import Icon from '@/Components/Icon/Icon' -import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip' -import { TOGGLE_LINK_COMMAND } from '@lexical/link' -import { mergeRegister } from '@lexical/utils' -import { KeyboardKey } from '@standardnotes/ui-services' -import Button from '@/Components/Button/Button' -import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl' -import { getSelectedNode } from '../../Lexical/Utils/getSelectedNode' -import { $isLinkTextNode } from './ToolbarLinkTextEditor' -import { useElementResize } from '@/Hooks/useElementRect' -import { createPortal } from 'react-dom' -import { ElementIds } from '@/Constants/ElementIDs' -import { getAdjustedStylesForNonPortalPopover } from '@/Components/Popover/Utils/getAdjustedStylesForNonPortal' - -const FloatingLinkEditor = ({ - linkUrl, - linkText, - isEditMode, - setEditMode, - editor, - isAutoLink, - isLinkText, - isMobile, -}: { - linkUrl: string - linkText: string - isEditMode: boolean - setEditMode: (isEditMode: boolean) => void - editor: LexicalEditor - isLinkText: boolean - isAutoLink: boolean - isMobile: boolean -}) => { - const [editedLinkUrl, setEditedLinkUrl] = useState(() => linkUrl) - useEffect(() => { - setEditedLinkUrl(linkUrl) - }, [linkUrl]) - const [editedLinkText, setEditedLinkText] = useState(() => linkText) - useEffect(() => { - setEditedLinkText(linkText) - }, [linkText]) - - const linkEditorRef = useRef(null) - const rangeRect = useRef() - - const updateLinkEditorPosition = useCallback(() => { - if (isMobile) { - return - } - - const nativeSelection = window.getSelection() - const rootElement = editor.getRootElement() - - if (nativeSelection !== null && rootElement !== null) { - if (rootElement.contains(nativeSelection.anchorNode)) { - rangeRect.current = getDOMRangeRect(nativeSelection, rootElement) - } - } - - const linkEditorElement = linkEditorRef.current - - if (!linkEditorElement) { - setTimeout(updateLinkEditorPosition) - return - } - - if (!rootElement) { - return - } - - const linkEditorRect = linkEditorElement.getBoundingClientRect() - const rootElementRect = rootElement.getBoundingClientRect() - - const calculatedStyles = getPositionedPopoverStyles({ - align: 'center', - side: 'top', - anchorRect: rangeRect.current, - popoverRect: linkEditorRect, - documentRect: rootElementRect, - offset: 12, - maxHeightFunction: () => 'none', - }) - if (calculatedStyles) { - const adjustedStyles = getAdjustedStylesForNonPortalPopover(linkEditorElement, calculatedStyles) - Object.entries(adjustedStyles).forEach(([key, value]) => { - linkEditorElement.style.setProperty(key, value) - }) - linkEditorElement.style.opacity = '1' - } - }, [editor, isMobile]) - - useElementResize(linkEditorRef.current, updateLinkEditorPosition) - - useEffect(() => { - updateLinkEditorPosition() - - return mergeRegister( - editor.registerUpdateListener(() => { - updateLinkEditorPosition() - }), - editor.registerCommand( - SELECTION_CHANGE_COMMAND, - (_payload) => { - updateLinkEditorPosition() - return false - }, - COMMAND_PRIORITY_LOW, - ), - ) - }, [editor, updateLinkEditorPosition]) - - const focusInput = useCallback((input: HTMLInputElement | null) => { - if (input) { - input.focus() - } - }, []) - - const handleSubmission = () => { - if (editedLinkUrl !== '') { - editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl)) - } - editor.update(() => { - const selection = $getSelection() - if (!$isRangeSelection(selection)) { - return - } - const node = getSelectedNode(selection) - if (!$isLinkTextNode(node, selection)) { - return - } - node.setTextContent(editedLinkText) - }) - setEditMode(false) - } - - useEffect(() => { - setTimeout(updateLinkEditorPosition) - }, [isEditMode, updateLinkEditorPosition]) - - return createPortal( - , - document.getElementById(ElementIds.SuperEditor) ?? document.body, - ) -} - -export default FloatingLinkEditor diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/LinkEditor.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/LinkEditor.tsx new file mode 100644 index 000000000..d062249d3 --- /dev/null +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/LinkEditor.tsx @@ -0,0 +1,261 @@ +import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPopoverStyles' +import { + $isTextNode, + COMMAND_PRIORITY_LOW, + LexicalEditor, + RangeSelection, + SELECTION_CHANGE_COMMAND, + TextNode, +} from 'lexical' +import { useCallback, useEffect, useRef, useState } from 'react' +import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect' +import { classNames } from '@standardnotes/snjs' +import Icon from '@/Components/Icon/Icon' +import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip' +import { $isLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link' +import { mergeRegister } from '@lexical/utils' +import { KeyboardKey } from '@standardnotes/ui-services' +import Button from '@/Components/Button/Button' +import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl' +import { getSelectedNode } from '../../Lexical/Utils/getSelectedNode' +import { useElementResize } from '@/Hooks/useElementRect' +import { createPortal } from 'react-dom' +import { ElementIds } from '@/Constants/ElementIDs' +import { getAdjustedStylesForNonPortalPopover } from '@/Components/Popover/Utils/getAdjustedStylesForNonPortal' + +export const $isLinkTextNode = ( + node: ReturnType, + selection: RangeSelection, +): node is TextNode => { + const parent = node.getParent() + return ( + $isLinkNode(parent) && + parent.getChildrenSize() === 1 && + $isTextNode(node) && + parent.getFirstChild() === node && + selection.anchor.getNode() === selection.focus.getNode() + ) +} + +const LinkEditor = ({ + editor, + setIsEditingLink, + isMobile, + linkNode, + linkTextNode, +}: { + editor: LexicalEditor + setIsEditingLink: (isEditMode: boolean) => void + isMobile: boolean + linkNode: LinkNode | null + linkTextNode: TextNode | null +}) => { + const [url, setURL] = useState('') + const [text, setText] = useState('') + useEffect(() => { + editor.getEditorState().read(() => { + if (linkNode) { + setURL(linkNode.getURL()) + } + if (linkTextNode) { + setText(linkTextNode.getTextContent()) + } + }) + }, [editor, linkNode, linkTextNode]) + + const linkInputRef = useRef(null) + const linkEditorRef = useRef(null) + const rangeRect = useRef() + const positionUpdateRAF = useRef() + + const updateLinkEditorPosition = useCallback(() => { + if (positionUpdateRAF.current) { + cancelAnimationFrame(positionUpdateRAF.current) + } + + positionUpdateRAF.current = requestAnimationFrame(() => { + if (isMobile) { + linkInputRef.current?.focus() + return + } + + const nativeSelection = window.getSelection() + const rootElement = editor.getRootElement() + + if (nativeSelection !== null && rootElement !== null) { + if (rootElement.contains(nativeSelection.anchorNode)) { + rangeRect.current = getDOMRangeRect(nativeSelection, rootElement) + } + } + + const linkEditorElement = linkEditorRef.current + + if (!linkEditorElement) { + setTimeout(updateLinkEditorPosition) + return + } + + if (!rootElement) { + return + } + + if (!rangeRect.current) { + return + } + + const linkEditorRect = linkEditorElement.getBoundingClientRect() + const rootElementRect = rootElement.getBoundingClientRect() + + const calculatedStyles = getPositionedPopoverStyles({ + align: 'center', + side: 'top', + anchorRect: rangeRect.current, + popoverRect: linkEditorRect, + documentRect: rootElementRect, + offset: 12, + maxHeightFunction: () => 'none', + }) + if (calculatedStyles) { + const adjustedStyles = getAdjustedStylesForNonPortalPopover(linkEditorElement, calculatedStyles) + Object.entries(adjustedStyles).forEach(([key, value]) => { + linkEditorElement.style.setProperty(key, value) + }) + linkEditorElement.style.display = 'block' + linkInputRef.current?.focus() + } + }) + }, [editor, isMobile]) + + useElementResize(linkEditorRef.current, updateLinkEditorPosition) + + useEffect(() => { + updateLinkEditorPosition() + + return mergeRegister( + editor.registerUpdateListener(() => { + updateLinkEditorPosition() + }), + editor.registerCommand( + SELECTION_CHANGE_COMMAND, + (_payload) => { + updateLinkEditorPosition() + return false + }, + COMMAND_PRIORITY_LOW, + ), + ) + }, [editor, updateLinkEditorPosition]) + + const handleSubmission = () => { + if (url !== '') { + editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(url)) + } + if (linkTextNode !== null && text !== '') { + editor.update( + () => { + linkTextNode.setTextContent(text) + }, + { + discrete: true, + }, + ) + } + setIsEditingLink(false) + } + + useEffect(() => { + const linkEditor = linkEditorRef.current + if (!linkEditor) { + return + } + + const handleFocusOut = (event: FocusEvent) => { + if (!linkEditor.contains(event.relatedTarget as Node)) { + setIsEditingLink(false) + } + } + + linkEditor.addEventListener('focusout', handleFocusOut) + + return () => { + linkEditor.removeEventListener('focusout', handleFocusOut) + } + }, [setIsEditingLink]) + + return createPortal( + , + document.getElementById(ElementIds.SuperEditor) ?? document.body, + ) +} + +export default LinkEditor diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/LinkViewer.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/LinkViewer.tsx new file mode 100644 index 000000000..9f298182e --- /dev/null +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/LinkViewer.tsx @@ -0,0 +1,172 @@ +import Icon from '@/Components/Icon/Icon' +import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPopoverStyles' +import { getAdjustedStylesForNonPortalPopover } from '@/Components/Popover/Utils/getAdjustedStylesForNonPortal' +import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip' +import { useElementResize } from '@/Hooks/useElementRect' +import { $isAutoLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link' +import { mergeRegister } from '@lexical/utils' +import { classNames } from '@standardnotes/snjs' +import { COMMAND_PRIORITY_LOW, LexicalEditor, SELECTION_CHANGE_COMMAND } from 'lexical' +import { useCallback, useEffect, useMemo, useRef } from 'react' +import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect' +import { ElementIds } from '@/Constants/ElementIDs' +import { createPortal } from 'react-dom' + +type Props = { + linkNode: LinkNode + editor: LexicalEditor + isMobile: boolean + setIsEditingLink: (isEditingLink: boolean) => void +} + +const LinkViewer = ({ isMobile, editor, linkNode, setIsEditingLink }: Props) => { + const linkViewerRef = useRef(null) + + const [linkUrl, isAutoLink] = useMemo(() => { + let linkUrl = '' + let isAutoLink = false + editor.getEditorState().read(() => { + linkUrl = linkNode.getURL() + isAutoLink = $isAutoLinkNode(linkNode) + }) + return [linkUrl, isAutoLink] + }, [editor, linkNode]) + + const rangeRect = useRef() + const updateLinkEditorPosition = useCallback(() => { + if (isMobile) { + return + } + + const nativeSelection = window.getSelection() + const rootElement = editor.getRootElement() + + if (nativeSelection !== null && rootElement !== null) { + if (rootElement.contains(nativeSelection.anchorNode)) { + rangeRect.current = getDOMRangeRect(nativeSelection, rootElement) + } + } + + const linkEditorElement = linkViewerRef.current + + if (!linkEditorElement) { + setTimeout(updateLinkEditorPosition) + return + } + + if (!rootElement) { + return + } + + const linkEditorRect = linkEditorElement.getBoundingClientRect() + const rootElementRect = rootElement.getBoundingClientRect() + + const calculatedStyles = getPositionedPopoverStyles({ + align: 'center', + side: 'top', + anchorRect: rangeRect.current, + popoverRect: linkEditorRect, + documentRect: rootElementRect, + offset: 12, + maxHeightFunction: () => 'none', + }) + if (calculatedStyles) { + const adjustedStyles = getAdjustedStylesForNonPortalPopover(linkEditorElement, calculatedStyles) + Object.entries(adjustedStyles).forEach(([key, value]) => { + linkEditorElement.style.setProperty(key, value) + }) + linkEditorElement.style.opacity = '1' + } + }, [editor, isMobile]) + + useElementResize(linkViewerRef.current, updateLinkEditorPosition) + + useEffect(() => { + updateLinkEditorPosition() + + return mergeRegister( + editor.registerUpdateListener(() => { + updateLinkEditorPosition() + }), + editor.registerCommand( + SELECTION_CHANGE_COMMAND, + (_payload) => { + updateLinkEditorPosition() + return false + }, + COMMAND_PRIORITY_LOW, + ), + ) + }, [editor, updateLinkEditorPosition]) + + if (!linkUrl) { + return null + } + + return createPortal( +
+
+ + +
{linkUrl}
+
+ + + + {!isAutoLink && ( + <> + + + + + + + + )} +
+
, + document.getElementById(ElementIds.SuperEditor) ?? document.body, + ) +} + +export default LinkViewer diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarLinkEditor.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarLinkEditor.tsx deleted file mode 100644 index babac09e0..000000000 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarLinkEditor.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import Icon from '@/Components/Icon/Icon' -import { KeyboardKey } from '@standardnotes/ui-services' -import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl' -import { TOGGLE_LINK_COMMAND } from '@lexical/link' -import { useCallback, useState, useRef, useEffect } from 'react' -import { LexicalEditor } from 'lexical' -import { classNames } from '@standardnotes/snjs' -import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip' - -type Props = { - linkUrl: string - isEditMode: boolean - setEditMode: (isEditMode: boolean) => void - editor: LexicalEditor - isAutoLink: boolean -} - -const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, isAutoLink }: Props) => { - const [editedLinkUrl, setEditedLinkUrl] = useState('') - const editModeContainer = useRef(null) - - const handleLinkSubmission = () => { - if (editedLinkUrl !== '') { - editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl)) - } - setEditMode(false) - } - - const focusInput = useCallback((input: HTMLInputElement | null) => { - if (input) { - input.focus() - } - }, []) - - useEffect(() => { - setEditedLinkUrl(linkUrl) - }, [linkUrl]) - - 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) - } - }} - onBlur={(event) => { - if (!editModeContainer.current?.contains(event.relatedTarget as Node)) { - setEditMode(false) - } - }} - className="flex-grow rounded-sm bg-contrast p-1 text-text sm:min-w-[40ch]" - /> - - - - - - -
- ) : ( -
- - -
{linkUrl}
-
- {!isAutoLink && ( - <> - - - - - - - - )} -
- ) -} - -export default LinkEditor diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarLinkTextEditor.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarLinkTextEditor.tsx deleted file mode 100644 index 3c5ce1fc3..000000000 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarLinkTextEditor.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import Icon from '@/Components/Icon/Icon' -import { KeyboardKey } from '@standardnotes/ui-services' -import { $getSelection, $isRangeSelection, $isTextNode, LexicalEditor, RangeSelection, TextNode } from 'lexical' -import { useCallback, useEffect, useRef, useState } from 'react' -import { VisuallyHidden } from '@ariakit/react' -import { getSelectedNode } from '../../Lexical/Utils/getSelectedNode' -import { $isLinkNode } from '@lexical/link' -import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip' - -type Props = { - linkText: string - editor: LexicalEditor - isEditMode: boolean - setEditMode: (isEditMode: boolean) => void -} - -export const $isLinkTextNode = ( - node: ReturnType, - selection: RangeSelection, -): node is TextNode => { - const parent = node.getParent() - return $isLinkNode(parent) && $isTextNode(node) && selection.anchor.getNode() === selection.focus.getNode() -} - -const LinkTextEditor = ({ linkText, editor, isEditMode, setEditMode }: Props) => { - const [editedLinkText, setEditedLinkText] = useState(() => linkText) - const editModeContainer = useRef(null) - - useEffect(() => { - setEditedLinkText(linkText) - }, [linkText]) - - const focusInput = useCallback((input: HTMLInputElement | null) => { - if (input) { - input.focus() - } - }, []) - - const handleLinkTextSubmission = () => { - editor.update(() => { - const selection = $getSelection() - if (!$isRangeSelection(selection)) { - return - } - const node = getSelectedNode(selection) - if (!$isLinkTextNode(node, selection)) { - return - } - node.setTextContent(editedLinkText) - }) - setEditMode(false) - } - - return isEditMode ? ( -
- { - setEditedLinkText(event.target.value) - }} - onKeyDown={(event) => { - if (event.key === KeyboardKey.Enter) { - event.preventDefault() - handleLinkTextSubmission() - } else if (event.key === KeyboardKey.Escape) { - event.preventDefault() - setEditMode(false) - } - }} - onBlur={(event) => { - if (!editModeContainer.current?.contains(event.relatedTarget as Node)) { - setEditMode(false) - } - }} - className="flex-grow rounded-sm bg-contrast p-1 text-text sm:min-w-[20ch]" - /> - - - - - - -
- ) : ( -
- -
- Link text: - {linkText} -
- - - -
- ) -} - -export default LinkTextEditor diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx index 0e49c2639..f1e29d18d 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx @@ -23,6 +23,7 @@ import { $createParagraphNode, $isTextNode, $getNodeByKey, + TextNode, } from 'lexical' import { mergeRegister, @@ -30,7 +31,7 @@ import { $getNearestNodeOfType, $getNearestBlockElementAncestorOrThrow, } from '@lexical/utils' -import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link' +import { $isLinkNode, TOGGLE_LINK_COMMAND, LinkNode } from '@lexical/link' import { $isListNode, ListNode } from '@lexical/list' import { $isHeadingNode, $isQuoteNode } from '@lexical/rich-text' import { @@ -61,13 +62,12 @@ import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip' import { Toolbar, ToolbarItem, useToolbarStore } from '@ariakit/react' import { PasswordBlock } from '../Blocks/Password' import { URL_REGEX } from '@/Constants/Constants' -import { $isLinkTextNode } from './ToolbarLinkTextEditor' import Popover from '@/Components/Popover/Popover' import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents' import Menu from '@/Components/Menu/Menu' import MenuItem, { MenuItemProps } from '@/Components/Menu/MenuItem' import { debounce, remToPx } from '@/Utils' -import FloatingLinkEditor from './FloatingLinkEditor' +import LinkEditor, { $isLinkTextNode } from './LinkEditor' import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator' import { useStateRef } from '@/Hooks/useStateRef' import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect' @@ -75,6 +75,7 @@ import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPo import usePreference from '@/Hooks/usePreference' import { ElementIds } from '@/Constants/ElementIDs' import { $isDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode' +import LinkViewer from './LinkViewer' const TOGGLE_LINK_AND_EDIT_COMMAND = createCommand('TOGGLE_LINK_AND_EDIT_COMMAND') @@ -220,12 +221,9 @@ const ToolbarPlugin = () => { const [isCode, setIsCode] = useState(false) const [isHighlight, setIsHighlight] = useState(false) - const [isLink, setIsLink] = useState(false) - const [isAutoLink, setIsAutoLink] = useState(false) - const [isLinkText, setIsLinkText] = useState(false) - const [isLinkEditMode, setIsLinkEditMode] = useState(false) - const [linkText, setLinkText] = useState('') - const [linkUrl, setLinkUrl] = useState('') + const [linkNode, setLinkNode] = useState(null) + const [linkTextNode, setLinkTextNode] = useState(null) + const [isEditingLink, setIsEditingLink] = useState(false) const [isTOCOpen, setIsTOCOpen] = useState(false) const tocAnchorRef = useRef(null) @@ -342,23 +340,18 @@ const ToolbarPlugin = () => { // Update links const node = getSelectedNode(selection) const parent = node.getParent() - if ($isLinkNode(parent) || $isLinkNode(node)) { - setIsLink(true) + setIsEditingLink(false) + if ($isLinkNode(node)) { + setLinkNode(node) + } else if ($isLinkNode(parent)) { + setLinkNode(parent) } else { - setIsLink(false) - } - setLinkUrl($isLinkNode(parent) ? parent.getURL() : $isLinkNode(node) ? node.getURL() : '') - if ($isAutoLinkNode(parent) || $isAutoLinkNode(node)) { - setIsAutoLink(true) - } else { - setIsAutoLink(false) + setLinkNode(null) } if ($isLinkTextNode(node, selection)) { - setIsLinkText(true) - setLinkText(node.getTextContent()) + setLinkTextNode(node) } else { - setIsLinkText(false) - setLinkText('') + setLinkTextNode(null) } if (elementDOM !== null) { @@ -505,13 +498,11 @@ const ToolbarPlugin = () => { TOGGLE_LINK_AND_EDIT_COMMAND, (payload) => { if (payload === null) { + setIsEditingLink(false) return activeEditor.dispatchCommand(TOGGLE_LINK_COMMAND, null) - } else if (typeof payload === 'string') { - const dispatched = activeEditor.dispatchCommand(TOGGLE_LINK_COMMAND, payload) - setIsLink(true) - setLinkUrl(payload) - setIsLinkEditMode(true) - return dispatched + } else { + setIsEditingLink(true) + return true } return false }, @@ -542,11 +533,9 @@ const ToolbarPlugin = () => { .catch((error) => { console.error(error) activeEditor.dispatchCommand(TOGGLE_LINK_AND_EDIT_COMMAND, '') - setIsLinkEditMode(true) }) } else { activeEditor.dispatchCommand(TOGGLE_LINK_AND_EDIT_COMMAND, '') - setIsLinkEditMode(true) } return true } @@ -555,7 +544,7 @@ const ToolbarPlugin = () => { }, COMMAND_PRIORITY_NORMAL, ) - }, [activeEditor, isLink]) + }, [activeEditor]) const dismissButtonRef = useRef(null) @@ -580,7 +569,7 @@ const ToolbarPlugin = () => { const elementToBeFocused = event.relatedTarget as Node const containerContainsElementToFocus = container?.contains(elementToBeFocused) const linkEditorContainsElementToFocus = document - .getElementById('super-link-editor') + .getElementById(ElementIds.SuperEditor) ?.contains(elementToBeFocused) const willFocusDismissButton = dismissButtonRef.current === elementToBeFocused if ((containerContainsElementToFocus || linkEditorContainsElementToFocus) && !willFocusDismissButton) { @@ -664,16 +653,22 @@ const ToolbarPlugin = () => { id="super-mobile-toolbar" ref={containerRef} > - {isLink && ( - + )} + {isEditingLink && ( + )}
@@ -742,7 +737,7 @@ const ToolbarPlugin = () => { { editor.dispatchCommand(TOGGLE_LINK_AND_EDIT_COMMAND, '') }} diff --git a/yarn.lock b/yarn.lock index afa5e8a61..a84e8b1ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7682,6 +7682,7 @@ __metadata: "@babel/preset-env": "*" "@babel/preset-typescript": ^7.21.5 "@lexical/headless": 0.13.1 + "@lexical/link": 0.13.1 "@lexical/list": 0.13.1 "@lexical/react": 0.13.1 "@lexical/rich-text": 0.13.1