]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts
Lexical: Media form improvements
[bookstack] / resources / js / wysiwyg / lexical / rich-text / LexicalQuoteNode.ts
1 import {
2     $applyNodeReplacement,
3     $createParagraphNode,
4     type DOMConversionMap,
5     type DOMConversionOutput,
6     type DOMExportOutput,
7     type EditorConfig,
8     isHTMLElement,
9     type LexicalEditor,
10     LexicalNode,
11     type NodeKey,
12     type ParagraphNode,
13     type RangeSelection
14 } from "lexical";
15 import {addClassNamesToElement} from "@lexical/utils";
16 import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode";
17 import {
18     commonPropertiesDifferent, deserializeCommonBlockNode,
19     setCommonBlockPropsFromElement,
20     updateElementWithCommonBlockProps
21 } from "lexical/nodes/common";
22
23 export type SerializedQuoteNode = SerializedCommonBlockNode;
24
25 /** @noInheritDoc */
26 export class QuoteNode extends CommonBlockNode {
27     static getType(): string {
28         return 'quote';
29     }
30
31     static clone(node: QuoteNode): QuoteNode {
32         const clone = new QuoteNode(node.__key);
33         copyCommonBlockProperties(node, clone);
34         return clone;
35     }
36
37     constructor(key?: NodeKey) {
38         super(key);
39     }
40
41     // View
42
43     createDOM(config: EditorConfig): HTMLElement {
44         const element = document.createElement('blockquote');
45         addClassNamesToElement(element, config.theme.quote);
46         updateElementWithCommonBlockProps(element, this);
47         return element;
48     }
49
50     updateDOM(prevNode: QuoteNode, dom: HTMLElement): boolean {
51         return commonPropertiesDifferent(prevNode, this);
52     }
53
54     static importDOM(): DOMConversionMap | null {
55         return {
56             blockquote: (node: Node) => ({
57                 conversion: $convertBlockquoteElement,
58                 priority: 0,
59             }),
60         };
61     }
62
63     exportDOM(editor: LexicalEditor): DOMExportOutput {
64         const {element} = super.exportDOM(editor);
65
66         if (element && isHTMLElement(element)) {
67             if (this.isEmpty()) {
68                 element.append(document.createElement('br'));
69             }
70         }
71
72         return {
73             element,
74         };
75     }
76
77     static importJSON(serializedNode: SerializedQuoteNode): QuoteNode {
78         const node = $createQuoteNode();
79         deserializeCommonBlockNode(serializedNode, node);
80         return node;
81     }
82
83     exportJSON(): SerializedQuoteNode {
84         return {
85             ...super.exportJSON(),
86             type: 'quote',
87         };
88     }
89
90     // Mutation
91
92     insertNewAfter(_: RangeSelection, restoreSelection?: boolean): ParagraphNode {
93         const newBlock = $createParagraphNode();
94         const direction = this.getDirection();
95         newBlock.setDirection(direction);
96         this.insertAfter(newBlock, restoreSelection);
97         return newBlock;
98     }
99
100     collapseAtStart(): true {
101         const paragraph = $createParagraphNode();
102         const children = this.getChildren();
103         children.forEach((child) => paragraph.append(child));
104         this.replace(paragraph);
105         return true;
106     }
107
108     canMergeWhenEmpty(): true {
109         return true;
110     }
111 }
112
113 export function $createQuoteNode(): QuoteNode {
114     return $applyNodeReplacement(new QuoteNode());
115 }
116
117 export function $isQuoteNode(
118     node: LexicalNode | null | undefined,
119 ): node is QuoteNode {
120     return node instanceof QuoteNode;
121 }
122
123 function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput {
124     const node = $createQuoteNode();
125     setCommonBlockPropsFromElement(element, node);
126     return {node};
127 }