/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the Chameleon License found in the * LICENSE file in the root directory of this source tree. * */ import type { DOMConversionMap, DOMConversionOutput, DOMExportOutput, EditorConfig, LexicalEditor, LexicalNode, NodeKey, SerializedEditor, SerializedLexicalNode, Spread, } from "lexical"; import { $applyNodeReplacement, createEditor, DecoratorNode } from "lexical"; import * as React from "react"; import { Suspense } from "react"; const ImageComponent = React.lazy( () => import("./ImageComponent"), ); export interface ImagePayload { altText: string; caption?: LexicalEditor; height?: number; key?: NodeKey; maxWidth?: number; showCaption?: boolean; src: string; width?: number; } function convertImageElement(domNode: Node): null | DOMConversionOutput { if (domNode instanceof HTMLImageElement) { const { alt: altText, src, width, height } = domNode; const node = $createImageNode({ altText, height, src, width }); return { node }; } return null; } export type SerializedImageNode = Spread< { altText: string; caption: SerializedEditor; height?: number; maxWidth: number; showCaption: boolean; src: string; width?: number; }, SerializedLexicalNode >; export class ImageNode extends DecoratorNode { __src: string; __altText: string; __width: "inherit" | number; __height: "inherit" | number; __maxWidth: number; __showCaption: boolean; __caption: LexicalEditor; static getType(): string { return "image"; } static clone(node: ImageNode): ImageNode { return new ImageNode( node.__src, node.__altText, node.__maxWidth, node.__width, node.__height, node.__showCaption, node.__caption, node.__key, ); } static importJSON(serializedNode: SerializedImageNode): ImageNode { const { altText, height, width, maxWidth, caption, src, showCaption } = serializedNode; const node = $createImageNode({ altText, height, maxWidth, showCaption, src, width, }); const nestedEditor = node.__caption; const editorState = nestedEditor.parseEditorState(caption.editorState); if (!editorState.isEmpty()) { nestedEditor.setEditorState(editorState); } return node; } exportDOM(): DOMExportOutput { const element = document.createElement("img"); element.setAttribute("src", this.__src); element.setAttribute("alt", this.__altText); element.setAttribute("width", this.__width.toString()); element.setAttribute("height", this.__height.toString()); return { element }; } static importDOM(): DOMConversionMap | null { return { img: () => ({ conversion: convertImageElement, priority: 0, }), }; } constructor( src: string, altText: string, maxWidth: number, width?: "inherit" | number, height?: "inherit" | number, showCaption?: boolean, caption?: LexicalEditor, key?: NodeKey, ) { super(key); this.__src = src; this.__altText = altText; this.__maxWidth = maxWidth; this.__width = width || "inherit"; this.__height = height || "inherit"; this.__showCaption = showCaption || false; this.__caption = caption || createEditor(); } exportJSON(): SerializedImageNode { return { altText: this.getAltText(), caption: this.__caption.toJSON(), height: this.__height === "inherit" ? 0 : this.__height, maxWidth: this.__maxWidth, showCaption: this.__showCaption, src: this.getSrc(), type: "image", version: 1, width: this.__width === "inherit" ? 0 : this.__width, }; } setWidthAndHeight( width: "inherit" | number, height: "inherit" | number, ): void { const writable = this.getWritable(); writable.__width = width; writable.__height = height; } setShowCaption(showCaption: boolean): void { const writable = this.getWritable(); writable.__showCaption = showCaption; } // View createDOM(config: EditorConfig): HTMLElement { const span = document.createElement("span"); const theme = config.theme; const className = theme.image; if (className !== undefined) { span.className = className; } return span; } updateDOM(): false { return false; } getSrc(): string { return this.__src; } getAltText(): string { return this.__altText; } decorate(): JSX.Element { return ( ); } } export function $createImageNode({ altText, height, maxWidth = 500, src, width, showCaption, caption, key, }: ImagePayload): ImageNode { return $applyNodeReplacement( new ImageNode( src, altText, maxWidth, width, height, showCaption, caption, key, ), ); } export function $isImageNode( node: LexicalNode | null | undefined, ): node is ImageNode { return node instanceof ImageNode; }