| 
									
										
										
										
											2024-12-04 01:04:50 +08:00
										 |  |  | import { | 
					
						
							|  |  |  |     $applyNodeReplacement, | 
					
						
							|  |  |  |     $createParagraphNode, | 
					
						
							|  |  |  |     type DOMConversionMap, | 
					
						
							|  |  |  |     DOMConversionOutput, | 
					
						
							|  |  |  |     type DOMExportOutput, | 
					
						
							|  |  |  |     type EditorConfig, | 
					
						
							|  |  |  |     isHTMLElement, | 
					
						
							|  |  |  |     type LexicalEditor, | 
					
						
							|  |  |  |     type LexicalNode, | 
					
						
							|  |  |  |     type NodeKey, | 
					
						
							|  |  |  |     type ParagraphNode, | 
					
						
							|  |  |  |     type RangeSelection, | 
					
						
							|  |  |  |     type Spread | 
					
						
							|  |  |  | } from "lexical"; | 
					
						
							|  |  |  | import {addClassNamesToElement} from "@lexical/utils"; | 
					
						
							| 
									
										
										
										
											2024-12-05 02:53:59 +08:00
										 |  |  | import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; | 
					
						
							| 
									
										
										
										
											2024-12-04 01:04:50 +08:00
										 |  |  | import { | 
					
						
							|  |  |  |     commonPropertiesDifferent, deserializeCommonBlockNode, | 
					
						
							| 
									
										
										
										
											2024-12-05 02:53:59 +08:00
										 |  |  |     setCommonBlockPropsFromElement, | 
					
						
							| 
									
										
										
										
											2024-12-04 01:04:50 +08:00
										 |  |  |     updateElementWithCommonBlockProps | 
					
						
							| 
									
										
										
										
											2024-12-05 02:53:59 +08:00
										 |  |  | } from "lexical/nodes/common"; | 
					
						
							| 
									
										
										
										
											2024-12-04 01:04:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type SerializedHeadingNode = Spread< | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     SerializedCommonBlockNode | 
					
						
							|  |  |  | >; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @noInheritDoc */ | 
					
						
							|  |  |  | export class HeadingNode extends CommonBlockNode { | 
					
						
							|  |  |  |     /** @internal */ | 
					
						
							|  |  |  |     __tag: HeadingTagType; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static getType(): string { | 
					
						
							|  |  |  |         return 'heading'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static clone(node: HeadingNode): HeadingNode { | 
					
						
							|  |  |  |         const clone = new HeadingNode(node.__tag, node.__key); | 
					
						
							|  |  |  |         copyCommonBlockProperties(node, clone); | 
					
						
							|  |  |  |         return clone; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     constructor(tag: HeadingTagType, key?: NodeKey) { | 
					
						
							|  |  |  |         super(key); | 
					
						
							|  |  |  |         this.__tag = tag; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getTag(): HeadingTagType { | 
					
						
							|  |  |  |         return this.__tag; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // View
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     createDOM(config: EditorConfig): HTMLElement { | 
					
						
							|  |  |  |         const tag = this.__tag; | 
					
						
							|  |  |  |         const element = document.createElement(tag); | 
					
						
							|  |  |  |         const theme = config.theme; | 
					
						
							|  |  |  |         const classNames = theme.heading; | 
					
						
							|  |  |  |         if (classNames !== undefined) { | 
					
						
							|  |  |  |             const className = classNames[tag]; | 
					
						
							|  |  |  |             addClassNamesToElement(element, className); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         updateElementWithCommonBlockProps(element, this); | 
					
						
							|  |  |  |         return element; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     updateDOM(prevNode: HeadingNode, dom: HTMLElement): boolean { | 
					
						
							|  |  |  |         return commonPropertiesDifferent(prevNode, this); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static importDOM(): DOMConversionMap | null { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             h1: (node: Node) => ({ | 
					
						
							|  |  |  |                 conversion: $convertHeadingElement, | 
					
						
							|  |  |  |                 priority: 0, | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |             h2: (node: Node) => ({ | 
					
						
							|  |  |  |                 conversion: $convertHeadingElement, | 
					
						
							|  |  |  |                 priority: 0, | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |             h3: (node: Node) => ({ | 
					
						
							|  |  |  |                 conversion: $convertHeadingElement, | 
					
						
							|  |  |  |                 priority: 0, | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |             h4: (node: Node) => ({ | 
					
						
							|  |  |  |                 conversion: $convertHeadingElement, | 
					
						
							|  |  |  |                 priority: 0, | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |             h5: (node: Node) => ({ | 
					
						
							|  |  |  |                 conversion: $convertHeadingElement, | 
					
						
							|  |  |  |                 priority: 0, | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |             h6: (node: Node) => ({ | 
					
						
							|  |  |  |                 conversion: $convertHeadingElement, | 
					
						
							|  |  |  |                 priority: 0, | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     exportDOM(editor: LexicalEditor): DOMExportOutput { | 
					
						
							|  |  |  |         const {element} = super.exportDOM(editor); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (element && isHTMLElement(element)) { | 
					
						
							|  |  |  |             if (this.isEmpty()) { | 
					
						
							|  |  |  |                 element.append(document.createElement('br')); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             element, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static importJSON(serializedNode: SerializedHeadingNode): HeadingNode { | 
					
						
							|  |  |  |         const node = $createHeadingNode(serializedNode.tag); | 
					
						
							|  |  |  |         deserializeCommonBlockNode(serializedNode, node); | 
					
						
							|  |  |  |         return node; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     exportJSON(): SerializedHeadingNode { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             ...super.exportJSON(), | 
					
						
							|  |  |  |             tag: this.getTag(), | 
					
						
							|  |  |  |             type: 'heading', | 
					
						
							|  |  |  |             version: 1, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Mutation
 | 
					
						
							|  |  |  |     insertNewAfter( | 
					
						
							|  |  |  |         selection?: RangeSelection, | 
					
						
							|  |  |  |         restoreSelection = true, | 
					
						
							|  |  |  |     ): ParagraphNode | HeadingNode { | 
					
						
							|  |  |  |         const anchorOffet = selection ? selection.anchor.offset : 0; | 
					
						
							|  |  |  |         const lastDesc = this.getLastDescendant(); | 
					
						
							|  |  |  |         const isAtEnd = | 
					
						
							|  |  |  |             !lastDesc || | 
					
						
							|  |  |  |             (selection && | 
					
						
							|  |  |  |                 selection.anchor.key === lastDesc.getKey() && | 
					
						
							|  |  |  |                 anchorOffet === lastDesc.getTextContentSize()); | 
					
						
							|  |  |  |         const newElement = | 
					
						
							|  |  |  |             isAtEnd || !selection | 
					
						
							|  |  |  |                 ? $createParagraphNode() | 
					
						
							|  |  |  |                 : $createHeadingNode(this.getTag()); | 
					
						
							|  |  |  |         const direction = this.getDirection(); | 
					
						
							|  |  |  |         newElement.setDirection(direction); | 
					
						
							|  |  |  |         this.insertAfter(newElement, restoreSelection); | 
					
						
							|  |  |  |         if (anchorOffet === 0 && !this.isEmpty() && selection) { | 
					
						
							|  |  |  |             const paragraph = $createParagraphNode(); | 
					
						
							|  |  |  |             paragraph.select(); | 
					
						
							|  |  |  |             this.replace(paragraph, true); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return newElement; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     collapseAtStart(): true { | 
					
						
							|  |  |  |         const newElement = !this.isEmpty() | 
					
						
							|  |  |  |             ? $createHeadingNode(this.getTag()) | 
					
						
							|  |  |  |             : $createParagraphNode(); | 
					
						
							|  |  |  |         const children = this.getChildren(); | 
					
						
							|  |  |  |         children.forEach((child) => newElement.append(child)); | 
					
						
							|  |  |  |         this.replace(newElement); | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     extractWithChild(): boolean { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function $convertHeadingElement(element: HTMLElement): DOMConversionOutput { | 
					
						
							|  |  |  |     const nodeName = element.nodeName.toLowerCase(); | 
					
						
							|  |  |  |     let node = null; | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |         nodeName === 'h1' || | 
					
						
							|  |  |  |         nodeName === 'h2' || | 
					
						
							|  |  |  |         nodeName === 'h3' || | 
					
						
							|  |  |  |         nodeName === 'h4' || | 
					
						
							|  |  |  |         nodeName === 'h5' || | 
					
						
							|  |  |  |         nodeName === 'h6' | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         node = $createHeadingNode(nodeName); | 
					
						
							|  |  |  |         setCommonBlockPropsFromElement(element, node); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return {node}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode { | 
					
						
							|  |  |  |     return $applyNodeReplacement(new HeadingNode(headingTag)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function $isHeadingNode( | 
					
						
							|  |  |  |     node: LexicalNode | null | undefined, | 
					
						
							|  |  |  | ): node is HeadingNode { | 
					
						
							|  |  |  |     return node instanceof HeadingNode; | 
					
						
							|  |  |  | } |