import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; import { mergeRegister } from "@lexical/utils"; import { SELECTION_CHANGE_COMMAND, TextNode } from "lexical"; import { useEffect } from "react"; import { $createTermIconNode, $createTermMarkerNode, $createTermNode, $isTermIconNode, $isTermMarkerNode, $isTermNode, TermIconNode, TermMarkerNode, TermNode } from "../nodes/term_node"; const TERM_REGEX = /\[\[(.+)\]\]/; export function TermPlugin() { const [editor] = useLexicalComposerContext(); useEffect(() => mergeRegister( editor.registerNodeTransform(TextNode, (node) => { const content = node.getTextContent(); const matches = content.match(TERM_REGEX); if (!matches) return; const term = matches[1]!; const start = content.indexOf(matches[0]); const end = start + matches[0].length; const startIndex = start > 0 ? 1 : 0; const contentIndex = startIndex + 1; const endIndex = contentIndex + 1; const textNodes = node.splitText(start, start + 2, end - 2, end); const termNode = $createTermNode(term); textNodes[startIndex]!.insertBefore(termNode); const iconNode = $createTermIconNode(term); termNode.append(iconNode); const startMarkerNode = $createTermMarkerNode(); startMarkerNode.append(textNodes[startIndex]!); const endMarkerNode = $createTermMarkerNode(); endMarkerNode.append(textNodes[endIndex]!); termNode.append(startMarkerNode, textNodes[contentIndex]!, endMarkerNode); editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined); }), editor.registerNodeTransform(TermMarkerNode, (node) => { const termNode = node.getParent(); if ($isTermNode(termNode)) return; node.getChildren().reverse().forEach((child) => node.insertAfter(child)); node.remove(); }), editor.registerNodeTransform(TermIconNode, (node) => { const termNode = node.getParent(); if ($isTermNode(termNode)) return; node.remove(); }), editor.registerNodeTransform(TermNode, (node) => { const children = node.getChildren(); const content = node.getTextContent(); const term = node.getTerm(); const iconNode = children.at(0); const startMarkerNode = children.at(1); const endMarkerNode = children.at(-1); if ($isTermIconNode(iconNode) && $isTermMarkerNode(startMarkerNode) && $isTermMarkerNode(endMarkerNode) && iconNode.getTerm() === term && startMarkerNode.getTextContent() === "[[" && endMarkerNode.getTextContent() === "]]" && content === `[[${term}]]` ) { return } node.getChildren().reverse().forEach((child) => node.insertAfter(child)); node.remove(); }), )); return null; }