import { $createParagraphNode, DOMConversion, DOMConversionMap, DOMConversionOutput, ElementNode, LexicalEditor, LexicalNode, ParagraphNode, Spread } from 'lexical'; import type {EditorConfig} from "lexical/LexicalEditor"; import type {RangeSelection} from "lexical/LexicalSelection"; import { CommonBlockAlignment, commonPropertiesDifferent, SerializedCommonBlockNode, setCommonBlockPropsFromElement, updateElementWithCommonBlockProps } from "./_common"; export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success'; export type SerializedCalloutNode = Spread<{ category: CalloutCategory; }, SerializedCommonBlockNode> export class CalloutNode extends ElementNode { __id: string = ''; __category: CalloutCategory = 'info'; __alignment: CommonBlockAlignment = ''; static getType() { return 'callout'; } static clone(node: CalloutNode) { const newNode = new CalloutNode(node.__category, node.__key); newNode.__id = node.__id; return newNode; } constructor(category: CalloutCategory, key?: string) { super(key); this.__category = category; } setCategory(category: CalloutCategory) { const self = this.getWritable(); self.__category = category; } getCategory(): CalloutCategory { const self = this.getLatest(); return self.__category; } setId(id: string) { const self = this.getWritable(); self.__id = id; } getId(): string { const self = this.getLatest(); return self.__id; } setAlignment(alignment: CommonBlockAlignment) { const self = this.getWritable(); self.__alignment = alignment; } getAlignment(): CommonBlockAlignment { const self = this.getLatest(); return self.__alignment; } createDOM(_config: EditorConfig, _editor: LexicalEditor) { const element = document.createElement('p'); element.classList.add('callout', this.__category || ''); updateElementWithCommonBlockProps(element, this); return element; } updateDOM(prevNode: CalloutNode): boolean { return prevNode.__category !== this.__category || commonPropertiesDifferent(prevNode, this); } insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode { const anchorOffset = selection ? selection.anchor.offset : 0; const newElement = anchorOffset === this.getTextContentSize() || !selection ? $createParagraphNode() : $createCalloutNode(this.__category); newElement.setDirection(this.getDirection()); this.insertAfter(newElement, restoreSelection); if (anchorOffset === 0 && !this.isEmpty() && selection) { const paragraph = $createParagraphNode(); paragraph.select(); this.replace(paragraph, true); } return newElement; } static importDOM(): DOMConversionMap|null { return { p(node: HTMLElement): DOMConversion|null { if (node.classList.contains('callout')) { return { conversion: (element: HTMLElement): DOMConversionOutput|null => { let category: CalloutCategory = 'info'; const categories: CalloutCategory[] = ['info', 'success', 'warning', 'danger']; for (const c of categories) { if (element.classList.contains(c)) { category = c; break; } } const node = new CalloutNode(category); setCommonBlockPropsFromElement(element, node); return { node, }; }, priority: 3, }; } return null; }, }; } exportJSON(): SerializedCalloutNode { return { ...super.exportJSON(), type: 'callout', version: 1, category: this.__category, id: this.__id, alignment: this.__alignment, }; } static importJSON(serializedNode: SerializedCalloutNode): CalloutNode { const node = $createCalloutNode(serializedNode.category); node.setId(serializedNode.id); node.setAlignment(serializedNode.alignment); return node; } } export function $createCalloutNode(category: CalloutCategory = 'info') { return new CalloutNode(category); } export function $isCalloutNode(node: LexicalNode | null | undefined): node is CalloutNode { return node instanceof CalloutNode; } export function $isCalloutNodeOfCategory(node: LexicalNode | null | undefined, category: CalloutCategory = 'info') { return node instanceof CalloutNode && (node as CalloutNode).getCategory() === category; }