Make headers work properly when typed normally. Fixes #1
All checks were successful
/ Push Docker image to local registry (push) Successful in 3m8s

This commit is contained in:
kalle 2025-04-16 17:22:07 +02:00
parent db9cd4f91e
commit d4f2f301d8

View file

@ -1,8 +1,9 @@
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { $createParagraphNode, $isParagraphNode, $isTextNode, SELECTION_CHANGE_COMMAND, TextNode } from "lexical"; import { $createParagraphNode, $isParagraphNode, $isTextNode, SELECTION_CHANGE_COMMAND, TextNode, type LexicalEditor, type LexicalNode } from "lexical";
import { useEffect } from "react"; import { useEffect } from "react";
import { $createHeaderMarkerNode, $createHeaderNode, $isHeaderMarkerNode, $isHeaderNode, HeaderMarkerNode, HeaderNode } from "../nodes/header_node"; import { $createHeaderMarkerNode, $createHeaderNode, $isHeaderMarkerNode, $isHeaderNode, HeaderMarkerNode, HeaderNode } from "../nodes/header_node";
import { mergeRegister } from "@lexical/utils"; import { mergeRegister } from "@lexical/utils";
import { findBlockNode } from "../editor_utils";
const HEADER_REGEX = /^#+ /; const HEADER_REGEX = /^#+ /;
@ -11,7 +12,25 @@ export function HeaderPlugin() {
useEffect(() => mergeRegister( useEffect(() => mergeRegister(
// Create a header node if the text node matches the HEADER_REGEX // Create a header node if the text node matches the HEADER_REGEX
editor.registerNodeTransform(TextNode, (textNode) => { editor.registerNodeTransform(TextNode, createHeaderTransform(editor)),
editor.registerNodeTransform(TextNode, ensureHeaderValidTransform),
editor.registerNodeTransform(HeaderNode, ensureHeaderValidTransform),
// Remove header markers if they aren't in a header
editor.registerNodeTransform(HeaderMarkerNode, (node) => {
const headerNode = node.getParent();
if ($isHeaderNode(headerNode)) {
return;
}
node.getChildren().reverse().forEach(child => node.insertAfter(child));
node.remove();
}),
));
return null;
}
const createHeaderTransform = (editor: LexicalEditor) => (textNode: TextNode) => {
const prevNode = textNode.getPreviousSibling(); const prevNode = textNode.getPreviousSibling();
if (prevNode) return; if (prevNode) return;
const paragraphNode = textNode.getParent(); const paragraphNode = textNode.getParent();
@ -44,35 +63,21 @@ export function HeaderPlugin() {
paragraphNode.replace(headerNode, true); paragraphNode.replace(headerNode, true);
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined); editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
}), }
// Remove headers if they don't match the HEADER_REGEX
editor.registerNodeTransform(HeaderNode, (headerNode) => { const ensureHeaderValidTransform = (node: LexicalNode) => {
const content = headerNode.getTextContent(); const headerNode = findBlockNode(node);
if (content.match(HEADER_REGEX)) return; if (!$isHeaderNode(headerNode)) {
headerNode.replace($createParagraphNode(), true);
}),
// Remove header markers if they don't match the HEADER_REGEX
editor.registerNodeTransform(HeaderMarkerNode, (node) => {
const headerNode = node.getParent();
const content = node.getTextContent();
if ($isHeaderNode(headerNode) &&
content.match(HEADER_REGEX) &&
content.length - 1 == headerNode.getLevel()) {
return; return;
} }
node.getChildren().reverse().forEach(child => node.insertAfter(child)); const markerNode = headerNode.getFirstChild();
node.remove(); const markerContent = markerNode?.getTextContent();
}), if ($isHeaderMarkerNode(markerNode)
// Remove header nodes without a header marker && markerContent?.match(HEADER_REGEX)
editor.registerNodeTransform(HeaderNode, (node) => { && markerContent.length == headerNode.getLevel() + 1) {
const children = node.getChildren(); return
const headerMarker = children[0]; }
if ($isHeaderMarkerNode(headerMarker)) return;
node.replace($createParagraphNode(), true); headerNode.replace($createParagraphNode(), true);
}),
));
return null;
} }