v3/frontend/src/editor/plugins/term_plugin.tsx

83 lines
3.1 KiB
TypeScript

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;
}