mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-04-27 13:35:44 +00:00

Also added extra lifecycle handling for decorators to things can be properly cleaned up after node destruction.
117 lines
No EOL
3.4 KiB
TypeScript
117 lines
No EOL
3.4 KiB
TypeScript
import {
|
|
$createNodeSelection,
|
|
$createParagraphNode, $getRoot,
|
|
$getSelection,
|
|
$isTextNode, $setSelection,
|
|
BaseSelection,
|
|
LexicalEditor, LexicalNode, TextFormatType
|
|
} from "lexical";
|
|
import {getNodesForPageEditor, LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
|
|
import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
|
|
import {$setBlocksType} from "@lexical/selection";
|
|
|
|
export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
|
|
const el = document.createElement(tag);
|
|
const attrKeys = Object.keys(attrs);
|
|
for (const attr of attrKeys) {
|
|
if (attrs[attr] !== null) {
|
|
el.setAttribute(attr, attrs[attr] as string);
|
|
}
|
|
}
|
|
|
|
for (const child of children) {
|
|
if (typeof child === 'string') {
|
|
el.append(document.createTextNode(child));
|
|
} else {
|
|
el.append(child);
|
|
}
|
|
}
|
|
|
|
return el;
|
|
}
|
|
|
|
export function selectionContainsNodeType(selection: BaseSelection|null, matcher: LexicalNodeMatcher): boolean {
|
|
return getNodeFromSelection(selection, matcher) !== null;
|
|
}
|
|
|
|
export function getNodeFromSelection(selection: BaseSelection|null, matcher: LexicalNodeMatcher): LexicalNode|null {
|
|
if (!selection) {
|
|
return null;
|
|
}
|
|
|
|
for (const node of selection.getNodes()) {
|
|
if (matcher(node)) {
|
|
return node;
|
|
}
|
|
|
|
for (const parent of node.getParents()) {
|
|
if (matcher(parent)) {
|
|
return parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function selectionContainsTextFormat(selection: BaseSelection|null, format: TextFormatType): boolean {
|
|
if (!selection) {
|
|
return false;
|
|
}
|
|
|
|
for (const node of selection.getNodes()) {
|
|
if ($isTextNode(node) && node.hasFormat(format)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function toggleSelectionBlockNodeType(editor: LexicalEditor, matcher: LexicalNodeMatcher, creator: LexicalElementNodeCreator) {
|
|
editor.update(() => {
|
|
const selection = $getSelection();
|
|
const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
|
|
if (selection && matcher(blockElement)) {
|
|
$setBlocksType(selection, $createParagraphNode);
|
|
} else {
|
|
$setBlocksType(selection, creator);
|
|
}
|
|
});
|
|
}
|
|
|
|
export function insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: boolean = true) {
|
|
const selection = $getSelection();
|
|
const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
|
|
|
|
if (blockElement) {
|
|
if (insertAfter) {
|
|
blockElement.insertAfter(node);
|
|
} else {
|
|
blockElement.insertBefore(node);
|
|
}
|
|
} else {
|
|
$getRoot().append(node);
|
|
}
|
|
}
|
|
|
|
export function selectSingleNode(node: LexicalNode) {
|
|
const nodeSelection = $createNodeSelection();
|
|
nodeSelection.add(node.getKey());
|
|
$setSelection(nodeSelection);
|
|
}
|
|
|
|
export function selectionContainsNode(selection: BaseSelection|null, node: LexicalNode): boolean {
|
|
if (!selection) {
|
|
return false;
|
|
}
|
|
|
|
const key = node.getKey();
|
|
for (const node of selection.getNodes()) {
|
|
if (node.getKey() === key) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} |