]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/nodes/callout.ts
Lexical: Switched to ts for new editor build
[bookstack] / resources / js / wysiwyg / nodes / callout.ts
1 import {
2     $createParagraphNode,
3     DOMConversion,
4     DOMConversionMap, DOMConversionOutput,
5     ElementNode,
6     LexicalEditor,
7     LexicalNode,
8     ParagraphNode, SerializedElementNode, Spread
9 } from 'lexical';
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import type {RangeSelection} from "lexical/LexicalSelection";
12
13 export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success';
14
15 export type SerializedCalloutNode = Spread<{
16     category: CalloutCategory;
17 }, SerializedElementNode>
18
19 export class Callout extends ElementNode {
20
21     __category: CalloutCategory = 'info';
22
23     static getType() {
24         return 'callout';
25     }
26
27     static clone(node: Callout) {
28         return new Callout(node.__category, node.__key);
29     }
30
31     constructor(category: CalloutCategory, key?: string) {
32         super(key);
33         this.__category = category;
34     }
35
36     createDOM(_config: EditorConfig, _editor: LexicalEditor) {
37         const element = document.createElement('p');
38         element.classList.add('callout', this.__category || '');
39         return element;
40     }
41
42     updateDOM(prevNode: unknown, dom: HTMLElement) {
43         // Returning false tells Lexical that this node does not need its
44         // DOM element replacing with a new copy from createDOM.
45         return false;
46     }
47
48     insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): Callout|ParagraphNode {
49         const anchorOffset = selection ? selection.anchor.offset : 0;
50         const newElement = anchorOffset === this.getTextContentSize() || !selection
51             ? $createParagraphNode() : $createCalloutNode(this.__category);
52
53         newElement.setDirection(this.getDirection());
54         this.insertAfter(newElement, restoreSelection);
55
56         if (anchorOffset === 0 && !this.isEmpty() && selection) {
57             const paragraph = $createParagraphNode();
58             paragraph.select();
59             this.replace(paragraph, true);
60         }
61
62         return newElement;
63     }
64
65     static importDOM(): DOMConversionMap|null {
66         return {
67             p(node: HTMLElement): DOMConversion|null {
68                 if (node.classList.contains('callout')) {
69                     return {
70                         conversion: (element: HTMLElement): DOMConversionOutput|null => {
71                             let category: CalloutCategory = 'info';
72                             const categories: CalloutCategory[] = ['info', 'success', 'warning', 'danger'];
73
74                             for (const c of categories) {
75                                 if (element.classList.contains(c)) {
76                                     category = c;
77                                     break;
78                                 }
79                             }
80
81                             return {
82                                 node: new Callout(category),
83                             };
84                         },
85                         priority: 3,
86                     };
87                 }
88                 return null;
89             },
90         };
91     }
92
93     exportJSON(): SerializedCalloutNode {
94         return {
95             ...super.exportJSON(),
96             type: 'callout',
97             version: 1,
98             category: this.__category,
99         };
100     }
101
102     static importJSON(serializedNode: SerializedCalloutNode): Callout {
103         return $createCalloutNode(serializedNode.category);
104     }
105
106 }
107
108 export function $createCalloutNode(category: CalloutCategory = 'info') {
109     return new Callout(category);
110 }
111
112 export function $isCalloutNode(node: LexicalNode | null | undefined) {
113     return node instanceof Callout;
114 }