4 DOMConversionMap, DOMConversionOutput,
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import type {RangeSelection} from "lexical/LexicalSelection";
13 CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode,
14 SerializedCommonBlockNode,
15 setCommonBlockPropsFromElement,
16 updateElementWithCommonBlockProps
19 export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success';
21 export type SerializedCalloutNode = Spread<{
22 category: CalloutCategory;
23 }, SerializedCommonBlockNode>
25 export class CalloutNode extends ElementNode {
27 __category: CalloutCategory = 'info';
28 __alignment: CommonBlockAlignment = '';
35 static clone(node: CalloutNode) {
36 const newNode = new CalloutNode(node.__category, node.__key);
37 newNode.__id = node.__id;
38 newNode.__alignment = node.__alignment;
39 newNode.__inset = node.__inset;
43 constructor(category: CalloutCategory, key?: string) {
45 this.__category = category;
48 setCategory(category: CalloutCategory) {
49 const self = this.getWritable();
50 self.__category = category;
53 getCategory(): CalloutCategory {
54 const self = this.getLatest();
55 return self.__category;
59 const self = this.getWritable();
64 const self = this.getLatest();
68 setAlignment(alignment: CommonBlockAlignment) {
69 const self = this.getWritable();
70 self.__alignment = alignment;
73 getAlignment(): CommonBlockAlignment {
74 const self = this.getLatest();
75 return self.__alignment;
78 setInset(size: number) {
79 const self = this.getWritable();
84 const self = this.getLatest();
88 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
89 const element = document.createElement('p');
90 element.classList.add('callout', this.__category || '');
91 updateElementWithCommonBlockProps(element, this);
95 updateDOM(prevNode: CalloutNode): boolean {
96 return prevNode.__category !== this.__category ||
97 commonPropertiesDifferent(prevNode, this);
100 insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode {
101 const anchorOffset = selection ? selection.anchor.offset : 0;
102 const newElement = anchorOffset === this.getTextContentSize() || !selection
103 ? $createParagraphNode() : $createCalloutNode(this.__category);
105 newElement.setDirection(this.getDirection());
106 this.insertAfter(newElement, restoreSelection);
108 if (anchorOffset === 0 && !this.isEmpty() && selection) {
109 const paragraph = $createParagraphNode();
111 this.replace(paragraph, true);
117 static importDOM(): DOMConversionMap|null {
119 p(node: HTMLElement): DOMConversion|null {
120 if (node.classList.contains('callout')) {
122 conversion: (element: HTMLElement): DOMConversionOutput|null => {
123 let category: CalloutCategory = 'info';
124 const categories: CalloutCategory[] = ['info', 'success', 'warning', 'danger'];
126 for (const c of categories) {
127 if (element.classList.contains(c)) {
133 const node = new CalloutNode(category);
134 setCommonBlockPropsFromElement(element, node);
148 exportJSON(): SerializedCalloutNode {
150 ...super.exportJSON(),
153 category: this.__category,
155 alignment: this.__alignment,
160 static importJSON(serializedNode: SerializedCalloutNode): CalloutNode {
161 const node = $createCalloutNode(serializedNode.category);
162 deserializeCommonBlockNode(serializedNode, node);
168 export function $createCalloutNode(category: CalloutCategory = 'info') {
169 return new CalloutNode(category);
172 export function $isCalloutNode(node: LexicalNode | null | undefined): node is CalloutNode {
173 return node instanceof CalloutNode;
176 export function $isCalloutNodeOfCategory(node: LexicalNode | null | undefined, category: CalloutCategory = 'info') {
177 return node instanceof CalloutNode && (node as CalloutNode).getCategory() === category;