import { $isAutoLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { $findMatchingParent, mergeRegister } from '@lexical/utils' import styles from '../styles.module.css' import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND } from 'lexical' import { useCallback, useEffect, useRef, useState } from 'react' import * as React from 'react' import { getSelectedNode } from '../utils/selected-node' import { setTooltipPosition } from '../utils/tooltip-position' import { useLinkInsert } from './link-insert' import { getLinkFromSelection } from '../utils/link-from-selection' function FloatingLinkEditor ({ editor, isLink, setIsLink, anchorElem }) { const { setLink } = useLinkInsert() const editorRef = useRef(null) const inputRef = useRef(null) const [linkUrl, setLinkUrl] = useState('') const [isEditMode, setEditMode] = useState(false) const updateLinkEditor = 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 editorElem = editorRef.current const nativeSelection = window.getSelection() const activeElement = document.activeElement if (editorElem === null) { return } const rootElement = editor.getRootElement() if ( selection !== null && nativeSelection !== null && rootElement !== null && rootElement.contains(nativeSelection.anchorNode) && editor.isEditable() ) { const domRange = nativeSelection.getRangeAt(0) let rect if (nativeSelection.anchorNode === rootElement) { let inner = rootElement while (inner.firstElementChild != null) { inner = inner.firstElementChild } rect = inner.getBoundingClientRect() } else { rect = domRange.getBoundingClientRect() } setTooltipPosition(rect, editorElem, anchorElem) } else if (!activeElement) { if (rootElement !== null) { setTooltipPosition(null, editorElem, anchorElem) } setEditMode(false) setLinkUrl('') } return true }, [anchorElem, editor]) useEffect(() => { const scrollerElem = anchorElem.parentElement const update = () => { editor.getEditorState().read(() => { updateLinkEditor() }) } window.addEventListener('resize', update) if (scrollerElem) { scrollerElem.addEventListener('scroll', update) } return () => { window.removeEventListener('resize', update) if (scrollerElem) { scrollerElem.removeEventListener('scroll', update) } } }, [anchorElem.parentElement, editor, updateLinkEditor]) useEffect(() => { return mergeRegister( editor.registerUpdateListener(({ editorState }) => { editorState.read(() => { updateLinkEditor() }) }), editor.registerCommand( SELECTION_CHANGE_COMMAND, () => { updateLinkEditor() return true }, COMMAND_PRIORITY_LOW ), editor.registerCommand( KEY_ESCAPE_COMMAND, () => { if (isLink) { setIsLink(false) return true } return false }, COMMAND_PRIORITY_HIGH ) ) }, [editor, updateLinkEditor, setIsLink, isLink]) useEffect(() => { editor.getEditorState().read(() => { updateLinkEditor() }) }, [editor, updateLinkEditor]) useEffect(() => { if (isEditMode && inputRef.current) { inputRef.current.focus() } }, [isEditMode]) return ( linkUrl &&
{linkUrl.replace('https://', '').replace('http://', '')} \ { editor.update(() => { // we need to replace the link // their playground simple 'TOGGLE's it with a new url // but we need to potentiallyr replace the text setLink(getLinkFromSelection()) }) }} >edit \ { editor.dispatchCommand(TOGGLE_LINK_COMMAND, null) }} >remove
) } function useFloatingLinkEditorToolbar ({ editor, anchorElem }) { const [activeEditor, setActiveEditor] = useState(editor) const [isLink, setIsLink] = useState(false) const updateToolbar = useCallback(() => { const selection = $getSelection() if ($isRangeSelection(selection)) { const node = getSelectedNode(selection) const linkParent = $findMatchingParent(node, $isLinkNode) const autoLinkParent = $findMatchingParent(node, $isAutoLinkNode) // We don't want this menu to open for auto links. if (linkParent != null && autoLinkParent == null) { setIsLink(true) } else { setIsLink(false) } } }, []) useEffect(() => { return editor.registerCommand( SELECTION_CHANGE_COMMAND, (_payload, newEditor) => { updateToolbar() setActiveEditor(newEditor) return false }, COMMAND_PRIORITY_CRITICAL ) }, [editor, updateToolbar]) return isLink ? : null } export default function LinkTooltipPlugin ({ anchorElem = document.body }) { const [editor] = useLexicalComposerContext() return useFloatingLinkEditorToolbar({ editor, anchorElem }) }