Make headers work properly when typed normally. Fixes #1
All checks were successful
/ Push Docker image to local registry (push) Successful in 3m8s
All checks were successful
/ Push Docker image to local registry (push) Successful in 3m8s
This commit is contained in:
parent
db9cd4f91e
commit
d4f2f301d8
1 changed files with 59 additions and 54 deletions
|
@ -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,68 +12,72 @@ 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)),
|
||||||
const prevNode = textNode.getPreviousSibling();
|
editor.registerNodeTransform(TextNode, ensureHeaderValidTransform),
|
||||||
if (prevNode) return;
|
editor.registerNodeTransform(HeaderNode, ensureHeaderValidTransform),
|
||||||
const paragraphNode = textNode.getParent();
|
// Remove header markers if they aren't in a header
|
||||||
if (!$isParagraphNode(paragraphNode)) return;
|
|
||||||
|
|
||||||
const content = textNode.getTextContent();
|
|
||||||
const regexMatch = content.match(HEADER_REGEX);
|
|
||||||
if (!regexMatch) return;
|
|
||||||
|
|
||||||
const children = paragraphNode.getChildren();
|
|
||||||
|
|
||||||
const firstTextNode = children[0];
|
|
||||||
if (!$isTextNode(firstTextNode)) return;
|
|
||||||
|
|
||||||
const markerLength = regexMatch[0].length;
|
|
||||||
const textNodes = firstTextNode.splitText(markerLength);
|
|
||||||
|
|
||||||
const headerMarkerContent = textNodes[0];
|
|
||||||
if (!headerMarkerContent) return;
|
|
||||||
|
|
||||||
const headerNode = $createHeaderNode(markerLength - 1);
|
|
||||||
const headerMarkerNode = $createHeaderMarkerNode();
|
|
||||||
|
|
||||||
headerMarkerNode.append(headerMarkerContent);
|
|
||||||
|
|
||||||
headerNode.append(headerMarkerNode);
|
|
||||||
headerNode.append(...textNodes.slice(1));
|
|
||||||
headerNode.append(...children.slice(1));
|
|
||||||
|
|
||||||
paragraphNode.replace(headerNode, true);
|
|
||||||
|
|
||||||
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
|
||||||
}),
|
|
||||||
// Remove headers if they don't match the HEADER_REGEX
|
|
||||||
editor.registerNodeTransform(HeaderNode, (headerNode) => {
|
|
||||||
const content = headerNode.getTextContent();
|
|
||||||
if (content.match(HEADER_REGEX)) return;
|
|
||||||
headerNode.replace($createParagraphNode(), true);
|
|
||||||
}),
|
|
||||||
// Remove header markers if they don't match the HEADER_REGEX
|
|
||||||
editor.registerNodeTransform(HeaderMarkerNode, (node) => {
|
editor.registerNodeTransform(HeaderMarkerNode, (node) => {
|
||||||
const headerNode = node.getParent();
|
const headerNode = node.getParent();
|
||||||
const content = node.getTextContent();
|
if ($isHeaderNode(headerNode)) {
|
||||||
if ($isHeaderNode(headerNode) &&
|
|
||||||
content.match(HEADER_REGEX) &&
|
|
||||||
content.length - 1 == headerNode.getLevel()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.getChildren().reverse().forEach(child => node.insertAfter(child));
|
node.getChildren().reverse().forEach(child => node.insertAfter(child));
|
||||||
node.remove();
|
node.remove();
|
||||||
}),
|
}),
|
||||||
// Remove header nodes without a header marker
|
|
||||||
editor.registerNodeTransform(HeaderNode, (node) => {
|
|
||||||
const children = node.getChildren();
|
|
||||||
const headerMarker = children[0];
|
|
||||||
if ($isHeaderMarkerNode(headerMarker)) return;
|
|
||||||
|
|
||||||
node.replace($createParagraphNode(), true);
|
|
||||||
}),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createHeaderTransform = (editor: LexicalEditor) => (textNode: TextNode) => {
|
||||||
|
const prevNode = textNode.getPreviousSibling();
|
||||||
|
if (prevNode) return;
|
||||||
|
const paragraphNode = textNode.getParent();
|
||||||
|
if (!$isParagraphNode(paragraphNode)) return;
|
||||||
|
|
||||||
|
const content = textNode.getTextContent();
|
||||||
|
const regexMatch = content.match(HEADER_REGEX);
|
||||||
|
if (!regexMatch) return;
|
||||||
|
|
||||||
|
const children = paragraphNode.getChildren();
|
||||||
|
|
||||||
|
const firstTextNode = children[0];
|
||||||
|
if (!$isTextNode(firstTextNode)) return;
|
||||||
|
|
||||||
|
const markerLength = regexMatch[0].length;
|
||||||
|
const textNodes = firstTextNode.splitText(markerLength);
|
||||||
|
|
||||||
|
const headerMarkerContent = textNodes[0];
|
||||||
|
if (!headerMarkerContent) return;
|
||||||
|
|
||||||
|
const headerNode = $createHeaderNode(markerLength - 1);
|
||||||
|
const headerMarkerNode = $createHeaderMarkerNode();
|
||||||
|
|
||||||
|
headerMarkerNode.append(headerMarkerContent);
|
||||||
|
|
||||||
|
headerNode.append(headerMarkerNode);
|
||||||
|
headerNode.append(...textNodes.slice(1));
|
||||||
|
headerNode.append(...children.slice(1));
|
||||||
|
|
||||||
|
paragraphNode.replace(headerNode, true);
|
||||||
|
|
||||||
|
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureHeaderValidTransform = (node: LexicalNode) => {
|
||||||
|
const headerNode = findBlockNode(node);
|
||||||
|
if (!$isHeaderNode(headerNode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const markerNode = headerNode.getFirstChild();
|
||||||
|
const markerContent = markerNode?.getTextContent();
|
||||||
|
if ($isHeaderMarkerNode(markerNode)
|
||||||
|
&& markerContent?.match(HEADER_REGEX)
|
||||||
|
&& markerContent.length == headerNode.getLevel() + 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
headerNode.replace($createParagraphNode(), true);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue