/* eslint-disable no-underscore-dangle -- Lexical nodes use protected __ fields by convention. */ import { $applyNodeReplacement, $getSelection, $isRangeSelection, TextNode, } from "lexical" import type { DOMConversionMap, DOMExportOutput, EditorConfig, LexicalEditor, LexicalNode, NodeKey, SerializedTextNode, Spread, } from "lexical" import { isISODate } from "../date-value" export type SerializedDateNode = Spread< { date: string }, SerializedTextNode > export class DateNode extends TextNode { __date: string static getType(): string { return "date" } static clone(node: DateNode): DateNode { return new DateNode(node.__date, node.__text, node.__key) } static importJSON(node: SerializedDateNode): DateNode { return $createDateNode(node.date, node.text).updateFromJSON(node) } static importDOM(): DOMConversionMap | null { return { time: (element) => { const date = element.getAttribute("datetime") || element.dataset.lexicalDate if (!date || !isISODate(date)) return null return { conversion: () => ({ node: $createDateNode(date, element.textContent || date), forChild: () => null, }), priority: 4, } }, } } constructor(date: string, text?: string, key?: NodeKey) { super(text ?? date, key) this.__date = date } createDOM(config: EditorConfig, editor?: LexicalEditor): HTMLElement { const element = super.createDOM(config, editor) element.dataset.lexicalDate = this.__date return element } updateDOM( previousNode: this, element: HTMLElement, config: EditorConfig ): boolean { const didUpdate = super.updateDOM(previousNode, element, config) if (previousNode.__date !== this.__date) { element.dataset.lexicalDate = this.__date } return didUpdate } exportDOM(): DOMExportOutput { const element = document.createElement("time") element.dateTime = this.__date element.dataset.lexicalDate = this.__date element.textContent = this.getTextContent() return { element } } exportJSON(): SerializedDateNode { return { ...super.exportJSON(), date: this.__date, type: "date", version: 1, } } getDate(): string { return this.getLatest().__date } setDate(date: string, text: string): this { const writable = this.getWritable() writable.__date = date writable.__text = text return writable } isTextEntity(): true { return true } } export function $createDateNode(date: string, text?: string): DateNode { return $applyNodeReplacement(new DateNode(date, text)) .setMode("token") .setDetail("unmergable") } export function $isDateNode( node: LexicalNode | null | undefined ): node is DateNode { return node instanceof DateNode } export function $getSelectedDateNode(): DateNode | null { const selection = $getSelection() if (!$isRangeSelection(selection)) return null const anchorNode = selection.anchor.getNode() const focusNode = selection.focus.getNode() if (anchorNode === focusNode && $isDateNode(anchorNode)) return anchorNode const selectedNodes = selection.getNodes() return selectedNodes.length === 1 && $isDateNode(selectedNodes[0]) ? selectedNodes[0] : null } export function $insertOrUpdateDate( date: string, text: string ): DateNode | null { const selectedDateNode = $getSelectedDateNode() if (selectedDateNode) { selectedDateNode.setDate(date, text).select(0, text.length) return selectedDateNode } const selection = $getSelection() if (!$isRangeSelection(selection)) return null const dateNode = $createDateNode(date, text) selection.insertNodes([dateNode]) dateNode.select(0, text.length) return dateNode } export { isISODate } from "../date-value"