import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CAN_REDO_COMMAND, CAN_UNDO_COMMAND, REDO_COMMAND, UNDO_COMMAND, SELECTION_CHANGE_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $getSelection, $isElementNode, $isRangeSelection, $createParagraphNode, $getNodeByKey, } from 'lexical'; import { $isLinkNode, $toggleLink, TOGGLE_LINK_COMMAND } from '@lexical/link'; import { $getSelectionStyleValueForProperty, $isParentElementRTL, $patchStyleText, $setBlocksType, // $wrapNodes, $isAtNodeEnd, } from '@lexical/selection'; import { $findMatchingParent, $getNearestNodeOfType, mergeRegister } from '@lexical/utils'; import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, REMOVE_LIST_COMMAND, $isListNode, ListNode } from '@lexical/list'; import { createPortal } from 'react-dom'; import { $createHeadingNode, $createQuoteNode, $isHeadingNode } from '@lexical/rich-text'; import { $createCodeNode, $isCodeNode, getDefaultCodeLanguage, getCodeLanguages } from '@lexical/code'; import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode'; import DropDown, { DropDownItem } from './../ui/DropDown'; import DropdownColorPicker from '../ui/DropdownColorPicker'; const LowPriority = 1; const supportedBlockTypes = new Set(['paragraph', 'quote', 'code', 'h1', 'h2', 'h3', 'ul', 'ol']); const blockTypeToBlockName = { code: 'Code Block', h1: 'Large Heading', h2: 'Small Heading', h3: 'Heading', h4: 'Heading', h5: 'Heading', ol: 'Numbered List', paragraph: 'Normal', quote: 'Quote', ul: 'Bulleted List', }; const FONT_FAMILY_OPTIONS = [ ['Arial', 'Arial'], ['Courier New', 'Courier New'], ['Georgia', 'Georgia'], ['Times New Roman', 'Times New Roman'], ['Trebuchet MS', 'Trebuchet MS'], ['Verdana', 'Verdana'], ]; const FONT_SIZE_OPTIONS = [ ['10px', '10px'], ['11px', '11px'], ['12px', '12px'], ['13px', '13px'], ['14px', '14px'], ['15px', '15px'], ['16px', '16px'], ['17px', '17px'], ['18px', '18px'], ['19px', '19px'], ['20px', '20px'], ]; const ELEMENT_FORMAT_OPTIONS = { center: { icon: 'center-align', iconRTL: 'center-align', name: 'Center Align' }, end: { icon: 'right-align', iconRTL: 'left-align', name: 'End Align' }, justify: { icon: 'justify-align', iconRTL: 'justify-align', name: 'Justify Align' }, left: { icon: 'left-align', iconRTL: 'left-align', name: 'Left Align' }, right: { icon: 'right-align', iconRTL: 'right-align', name: 'Right Align' }, start: { icon: 'left-align', iconRTL: 'right-align', name: 'Start Align' }, }; function dropDownActiveClass(active) { if (active) { return 'active dropdown-item-active'; } else { return ''; } } function Divider() { return
; } function positionEditorElement(editor, rect) { if (rect === null) { editor.style.opacity = '0'; editor.style.top = '-1000px'; editor.style.left = '-1000px'; } else { editor.style.opacity = '1'; editor.style.top = `${rect.top + rect.height + window.pageYOffset + 10}px`; editor.style.left = `${rect.left + window.pageXOffset - editor.offsetWidth / 2 + rect.width / 2}px`; } } function FloatingLinkEditor({ editor }) { const editorRef = useRef(null); const inputRef = useRef(null); const mouseDownRef = useRef(false); const [linkUrl, setLinkUrl] = useState(''); const [isEditMode, setEditMode] = useState(false); const [lastSelection, setLastSelection] = useState(null); 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.isCollapsed && rootElement !== null && rootElement.contains(nativeSelection.anchorNode)) { 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(); } if (!mouseDownRef.current) { positionEditorElement(editorElem, rect); } setLastSelection(selection); } else if (!activeElement || activeElement.className !== 'link-input') { positionEditorElement(editorElem, null); setLastSelection(null); setEditMode(false); setLinkUrl(''); } return true; }, [editor]); useEffect(() => { return mergeRegister( editor.registerUpdateListener(({ editorState }) => { editorState.read(() => { updateLinkEditor(); }); }), editor.registerCommand( SELECTION_CHANGE_COMMAND, () => { updateLinkEditor(); return true; }, LowPriority ) ); }, [editor, updateLinkEditor]); useEffect(() => { editor.getEditorState().read(() => { updateLinkEditor(); }); }, [editor, updateLinkEditor]); useEffect(() => { if (isEditMode && inputRef.current) { inputRef.current.focus(); } }, [isEditMode]); return (