4 DOMConversionMap, DOMConversionOutput,
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import type {RangeSelection} from "lexical/LexicalSelection";
13 CommonBlockAlignment, commonPropertiesDifferent,
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 = '';
34 static clone(node: CalloutNode) {
35 const newNode = new CalloutNode(node.__category, node.__key);
36 newNode.__id = node.__id;
40 constructor(category: CalloutCategory, key?: string) {
42 this.__category = category;
45 setCategory(category: CalloutCategory) {
46 const self = this.getWritable();
47 self.__category = category;
50 getCategory(): CalloutCategory {
51 const self = this.getLatest();
52 return self.__category;
56 const self = this.getWritable();
61 const self = this.getLatest();
65 setAlignment(alignment: CommonBlockAlignment) {
66 const self = this.getWritable();
67 self.__alignment = alignment;
70 getAlignment(): CommonBlockAlignment {
71 const self = this.getLatest();
72 return self.__alignment;
75 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
76 const element = document.createElement('p');
77 element.classList.add('callout', this.__category || '');
78 updateElementWithCommonBlockProps(element, this);
82 updateDOM(prevNode: CalloutNode): boolean {
83 return prevNode.__category !== this.__category ||
84 commonPropertiesDifferent(prevNode, this);
87 insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode {
88 const anchorOffset = selection ? selection.anchor.offset : 0;
89 const newElement = anchorOffset === this.getTextContentSize() || !selection
90 ? $createParagraphNode() : $createCalloutNode(this.__category);
92 newElement.setDirection(this.getDirection());
93 this.insertAfter(newElement, restoreSelection);
95 if (anchorOffset === 0 && !this.isEmpty() && selection) {
96 const paragraph = $createParagraphNode();
98 this.replace(paragraph, true);
104 static importDOM(): DOMConversionMap|null {
106 p(node: HTMLElement): DOMConversion|null {
107 if (node.classList.contains('callout')) {
109 conversion: (element: HTMLElement): DOMConversionOutput|null => {
110 let category: CalloutCategory = 'info';
111 const categories: CalloutCategory[] = ['info', 'success', 'warning', 'danger'];
113 for (const c of categories) {
114 if (element.classList.contains(c)) {
120 const node = new CalloutNode(category);
121 setCommonBlockPropsFromElement(element, node);
135 exportJSON(): SerializedCalloutNode {
137 ...super.exportJSON(),
140 category: this.__category,
142 alignment: this.__alignment,
146 static importJSON(serializedNode: SerializedCalloutNode): CalloutNode {
147 const node = $createCalloutNode(serializedNode.category);
148 node.setId(serializedNode.id);
149 node.setAlignment(serializedNode.alignment);
155 export function $createCalloutNode(category: CalloutCategory = 'info') {
156 return new CalloutNode(category);
159 export function $isCalloutNode(node: LexicalNode | null | undefined): node is CalloutNode {
160 return node instanceof CalloutNode;
163 export function $isCalloutNodeOfCategory(node: LexicalNode | null | undefined, category: CalloutCategory = 'info') {
164 return node instanceof CalloutNode && (node as CalloutNode).getCategory() === category;