mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-01-10 11:07:36 +00:00
9fdd100f2d
Also cleaned up old unused imports.
201 lines
No EOL
5.7 KiB
TypeScript
201 lines
No EOL
5.7 KiB
TypeScript
import {
|
|
$applyNodeReplacement,
|
|
$createParagraphNode,
|
|
type DOMConversionMap,
|
|
DOMConversionOutput,
|
|
type DOMExportOutput,
|
|
type EditorConfig,
|
|
isHTMLElement,
|
|
type LexicalEditor,
|
|
type LexicalNode,
|
|
type NodeKey,
|
|
type ParagraphNode,
|
|
type RangeSelection,
|
|
type Spread
|
|
} from "lexical";
|
|
import {addClassNamesToElement} from "@lexical/utils";
|
|
import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode";
|
|
import {
|
|
commonPropertiesDifferent, deserializeCommonBlockNode,
|
|
setCommonBlockPropsFromElement,
|
|
updateElementWithCommonBlockProps
|
|
} from "lexical/nodes/common";
|
|
|
|
export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
|
|
export type SerializedHeadingNode = Spread<
|
|
{
|
|
tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
},
|
|
SerializedCommonBlockNode
|
|
>;
|
|
|
|
/** @noInheritDoc */
|
|
export class HeadingNode extends CommonBlockNode {
|
|
/** @internal */
|
|
__tag: HeadingTagType;
|
|
|
|
static getType(): string {
|
|
return 'heading';
|
|
}
|
|
|
|
static clone(node: HeadingNode): HeadingNode {
|
|
const clone = new HeadingNode(node.__tag, node.__key);
|
|
copyCommonBlockProperties(node, clone);
|
|
return clone;
|
|
}
|
|
|
|
constructor(tag: HeadingTagType, key?: NodeKey) {
|
|
super(key);
|
|
this.__tag = tag;
|
|
}
|
|
|
|
getTag(): HeadingTagType {
|
|
return this.__tag;
|
|
}
|
|
|
|
// View
|
|
|
|
createDOM(config: EditorConfig): HTMLElement {
|
|
const tag = this.__tag;
|
|
const element = document.createElement(tag);
|
|
const theme = config.theme;
|
|
const classNames = theme.heading;
|
|
if (classNames !== undefined) {
|
|
const className = classNames[tag];
|
|
addClassNamesToElement(element, className);
|
|
}
|
|
updateElementWithCommonBlockProps(element, this);
|
|
return element;
|
|
}
|
|
|
|
updateDOM(prevNode: HeadingNode, dom: HTMLElement): boolean {
|
|
return commonPropertiesDifferent(prevNode, this);
|
|
}
|
|
|
|
static importDOM(): DOMConversionMap | null {
|
|
return {
|
|
h1: (node: Node) => ({
|
|
conversion: $convertHeadingElement,
|
|
priority: 0,
|
|
}),
|
|
h2: (node: Node) => ({
|
|
conversion: $convertHeadingElement,
|
|
priority: 0,
|
|
}),
|
|
h3: (node: Node) => ({
|
|
conversion: $convertHeadingElement,
|
|
priority: 0,
|
|
}),
|
|
h4: (node: Node) => ({
|
|
conversion: $convertHeadingElement,
|
|
priority: 0,
|
|
}),
|
|
h5: (node: Node) => ({
|
|
conversion: $convertHeadingElement,
|
|
priority: 0,
|
|
}),
|
|
h6: (node: Node) => ({
|
|
conversion: $convertHeadingElement,
|
|
priority: 0,
|
|
}),
|
|
};
|
|
}
|
|
|
|
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
|
const {element} = super.exportDOM(editor);
|
|
|
|
if (element && isHTMLElement(element)) {
|
|
if (this.isEmpty()) {
|
|
element.append(document.createElement('br'));
|
|
}
|
|
}
|
|
|
|
return {
|
|
element,
|
|
};
|
|
}
|
|
|
|
static importJSON(serializedNode: SerializedHeadingNode): HeadingNode {
|
|
const node = $createHeadingNode(serializedNode.tag);
|
|
deserializeCommonBlockNode(serializedNode, node);
|
|
return node;
|
|
}
|
|
|
|
exportJSON(): SerializedHeadingNode {
|
|
return {
|
|
...super.exportJSON(),
|
|
tag: this.getTag(),
|
|
type: 'heading',
|
|
version: 1,
|
|
};
|
|
}
|
|
|
|
// Mutation
|
|
insertNewAfter(
|
|
selection?: RangeSelection,
|
|
restoreSelection = true,
|
|
): ParagraphNode | HeadingNode {
|
|
const anchorOffet = selection ? selection.anchor.offset : 0;
|
|
const lastDesc = this.getLastDescendant();
|
|
const isAtEnd =
|
|
!lastDesc ||
|
|
(selection &&
|
|
selection.anchor.key === lastDesc.getKey() &&
|
|
anchorOffet === lastDesc.getTextContentSize());
|
|
const newElement =
|
|
isAtEnd || !selection
|
|
? $createParagraphNode()
|
|
: $createHeadingNode(this.getTag());
|
|
const direction = this.getDirection();
|
|
newElement.setDirection(direction);
|
|
this.insertAfter(newElement, restoreSelection);
|
|
if (anchorOffet === 0 && !this.isEmpty() && selection) {
|
|
const paragraph = $createParagraphNode();
|
|
paragraph.select();
|
|
this.replace(paragraph, true);
|
|
}
|
|
return newElement;
|
|
}
|
|
|
|
collapseAtStart(): true {
|
|
const newElement = !this.isEmpty()
|
|
? $createHeadingNode(this.getTag())
|
|
: $createParagraphNode();
|
|
const children = this.getChildren();
|
|
children.forEach((child) => newElement.append(child));
|
|
this.replace(newElement);
|
|
return true;
|
|
}
|
|
|
|
extractWithChild(): boolean {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
|
|
const nodeName = element.nodeName.toLowerCase();
|
|
let node = null;
|
|
if (
|
|
nodeName === 'h1' ||
|
|
nodeName === 'h2' ||
|
|
nodeName === 'h3' ||
|
|
nodeName === 'h4' ||
|
|
nodeName === 'h5' ||
|
|
nodeName === 'h6'
|
|
) {
|
|
node = $createHeadingNode(nodeName);
|
|
setCommonBlockPropsFromElement(element, node);
|
|
}
|
|
return {node};
|
|
}
|
|
|
|
export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode {
|
|
return $applyNodeReplacement(new HeadingNode(headingTag));
|
|
}
|
|
|
|
export function $isHeadingNode(
|
|
node: LexicalNode | null | undefined,
|
|
): node is HeadingNode {
|
|
return node instanceof HeadingNode;
|
|
} |