4 DOMConversionMap, DOMConversionOutput,
8 ParagraphNode, SerializedElementNode, Spread
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import type {RangeSelection} from "lexical/LexicalSelection";
13 export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success';
15 export type SerializedCalloutNode = Spread<{
16 category: CalloutCategory;
17 }, SerializedElementNode>
19 export class CalloutNode extends ElementNode {
21 __category: CalloutCategory = 'info';
27 static clone(node: CalloutNode) {
28 return new CalloutNode(node.__category, node.__key);
31 constructor(category: CalloutCategory, key?: string) {
33 this.__category = category;
36 setCategory(category: CalloutCategory) {
37 const self = this.getWritable();
38 self.__category = category;
41 getCategory(): CalloutCategory {
42 const self = this.getLatest();
43 return self.__category;
46 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
47 const element = document.createElement('p');
48 element.classList.add('callout', this.__category || '');
52 updateDOM(prevNode: unknown, dom: HTMLElement) {
53 // Returning false tells Lexical that this node does not need its
54 // DOM element replacing with a new copy from createDOM.
58 insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode {
59 const anchorOffset = selection ? selection.anchor.offset : 0;
60 const newElement = anchorOffset === this.getTextContentSize() || !selection
61 ? $createParagraphNode() : $createCalloutNode(this.__category);
63 newElement.setDirection(this.getDirection());
64 this.insertAfter(newElement, restoreSelection);
66 if (anchorOffset === 0 && !this.isEmpty() && selection) {
67 const paragraph = $createParagraphNode();
69 this.replace(paragraph, true);
75 static importDOM(): DOMConversionMap|null {
77 p(node: HTMLElement): DOMConversion|null {
78 if (node.classList.contains('callout')) {
80 conversion: (element: HTMLElement): DOMConversionOutput|null => {
81 let category: CalloutCategory = 'info';
82 const categories: CalloutCategory[] = ['info', 'success', 'warning', 'danger'];
84 for (const c of categories) {
85 if (element.classList.contains(c)) {
92 node: new CalloutNode(category),
103 exportJSON(): SerializedCalloutNode {
105 ...super.exportJSON(),
108 category: this.__category,
112 static importJSON(serializedNode: SerializedCalloutNode): CalloutNode {
113 return $createCalloutNode(serializedNode.category);
118 export function $createCalloutNode(category: CalloutCategory = 'info') {
119 return new CalloutNode(category);
122 export function $isCalloutNode(node: LexicalNode | null | undefined) {
123 return node instanceof CalloutNode;
126 export function $isCalloutNodeOfCategory(node: LexicalNode | null | undefined, category: CalloutCategory = 'info') {
127 return node instanceof CalloutNode && (node as CalloutNode).getCategory() === category;