]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/nodes/custom-heading.ts
Lexical: Merged custom paragraph node, removed old format/indent refs
[bookstack] / resources / js / wysiwyg / nodes / custom-heading.ts
1 import {
2     DOMConversionMap,
3     DOMConversionOutput,
4     LexicalNode,
5     Spread
6 } from "lexical";
7 import {EditorConfig} from "lexical/LexicalEditor";
8 import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text";
9 import {
10     CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode,
11     SerializedCommonBlockNode,
12     setCommonBlockPropsFromElement,
13     updateElementWithCommonBlockProps
14 } from "./_common";
15
16
17 export type SerializedCustomHeadingNode = Spread<SerializedCommonBlockNode, SerializedHeadingNode>
18
19 export class CustomHeadingNode extends HeadingNode {
20     __id: string = '';
21     __alignment: CommonBlockAlignment = '';
22     __inset: number = 0;
23
24     static getType() {
25         return 'custom-heading';
26     }
27
28     setId(id: string) {
29         const self = this.getWritable();
30         self.__id = id;
31     }
32
33     getId(): string {
34         const self = this.getLatest();
35         return self.__id;
36     }
37
38     setAlignment(alignment: CommonBlockAlignment) {
39         const self = this.getWritable();
40         self.__alignment = alignment;
41     }
42
43     getAlignment(): CommonBlockAlignment {
44         const self = this.getLatest();
45         return self.__alignment;
46     }
47
48     setInset(size: number) {
49         const self = this.getWritable();
50         self.__inset = size;
51     }
52
53     getInset(): number {
54         const self = this.getLatest();
55         return self.__inset;
56     }
57
58     static clone(node: CustomHeadingNode) {
59         const newNode = new CustomHeadingNode(node.__tag, node.__key);
60         newNode.__alignment = node.__alignment;
61         newNode.__inset = node.__inset;
62         return newNode;
63     }
64
65     createDOM(config: EditorConfig): HTMLElement {
66         const dom = super.createDOM(config);
67         updateElementWithCommonBlockProps(dom, this);
68         return dom;
69     }
70
71     updateDOM(prevNode: CustomHeadingNode, dom: HTMLElement): boolean {
72         return super.updateDOM(prevNode, dom)
73             || commonPropertiesDifferent(prevNode, this);
74     }
75
76     exportJSON(): SerializedCustomHeadingNode {
77         return {
78             ...super.exportJSON(),
79             type: 'custom-heading',
80             version: 1,
81             id: this.__id,
82             alignment: this.__alignment,
83             inset: this.__inset,
84         };
85     }
86
87     static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode {
88         const node = $createCustomHeadingNode(serializedNode.tag);
89         deserializeCommonBlockNode(serializedNode, node);
90         return node;
91     }
92
93     static importDOM(): DOMConversionMap | null {
94         return {
95             h1: (node: Node) => ({
96                 conversion: $convertHeadingElement,
97                 priority: 0,
98             }),
99             h2: (node: Node) => ({
100                 conversion: $convertHeadingElement,
101                 priority: 0,
102             }),
103             h3: (node: Node) => ({
104                 conversion: $convertHeadingElement,
105                 priority: 0,
106             }),
107             h4: (node: Node) => ({
108                 conversion: $convertHeadingElement,
109                 priority: 0,
110             }),
111             h5: (node: Node) => ({
112                 conversion: $convertHeadingElement,
113                 priority: 0,
114             }),
115             h6: (node: Node) => ({
116                 conversion: $convertHeadingElement,
117                 priority: 0,
118             }),
119         };
120     }
121 }
122
123 function $convertHeadingElement(element: HTMLElement): DOMConversionOutput {
124     const nodeName = element.nodeName.toLowerCase();
125     let node = null;
126     if (
127         nodeName === 'h1' ||
128         nodeName === 'h2' ||
129         nodeName === 'h3' ||
130         nodeName === 'h4' ||
131         nodeName === 'h5' ||
132         nodeName === 'h6'
133     ) {
134         node = $createCustomHeadingNode(nodeName);
135         setCommonBlockPropsFromElement(element, node);
136     }
137     return {node};
138 }
139
140 export function $createCustomHeadingNode(tag: HeadingTagType) {
141     return new CustomHeadingNode(tag);
142 }
143
144 export function $isCustomHeadingNode(node: LexicalNode | null | undefined): node is CustomHeadingNode {
145     return node instanceof CustomHeadingNode;
146 }