83 lines
3.1 KiB
TypeScript
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;
|
|
}
|