181 lines
3.4 KiB
TypeScript
181 lines
3.4 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
*/
|
|
|
|
import { $applyNodeReplacement, DecoratorNode } from 'lexical';
|
|
import * as React from 'react';
|
|
import { Suspense } from 'react';
|
|
|
|
import type {
|
|
DOMConversionMap,
|
|
DOMConversionOutput,
|
|
DOMExportOutput,
|
|
EditorConfig,
|
|
LexicalNode,
|
|
NodeKey,
|
|
SerializedLexicalNode,
|
|
Spread,
|
|
} from 'lexical';
|
|
|
|
const ImageComponent = React.lazy(() => import('./image-component'));
|
|
|
|
interface ImagePayload {
|
|
altText?: string
|
|
key?: NodeKey
|
|
src: string
|
|
}
|
|
|
|
const convertImageElement = (domNode: Node): null | DOMConversionOutput => {
|
|
if (domNode instanceof HTMLImageElement) {
|
|
const { alt: altText, src } = domNode;
|
|
const node = $createImageNode({ altText, src });
|
|
return { node };
|
|
}
|
|
return null;
|
|
};
|
|
|
|
type SerializedImageNode = Spread<
|
|
{
|
|
altText: string
|
|
src: string
|
|
},
|
|
SerializedLexicalNode
|
|
>;
|
|
|
|
class ImageNode extends DecoratorNode<JSX.Element> {
|
|
|
|
__src: string;
|
|
__altText: string;
|
|
|
|
static getType(): string {
|
|
return 'image';
|
|
}
|
|
|
|
static clone(node: ImageNode): ImageNode {
|
|
return new ImageNode(
|
|
node.__src,
|
|
node.__altText,
|
|
node.__key,
|
|
);
|
|
}
|
|
|
|
static importJSON(serializedNode: SerializedImageNode): ImageNode {
|
|
const { altText, src } =
|
|
serializedNode;
|
|
const node = $createImageNode({
|
|
altText,
|
|
src,
|
|
});
|
|
return node;
|
|
}
|
|
|
|
exportDOM(): DOMExportOutput {
|
|
const element = document.createElement('img');
|
|
element.setAttribute('src', this.__src);
|
|
element.setAttribute('alt', this.__altText);
|
|
return { element };
|
|
}
|
|
|
|
static importDOM(): DOMConversionMap | null {
|
|
return {
|
|
img: (node: Node) => ({
|
|
conversion: convertImageElement,
|
|
priority: 0,
|
|
}),
|
|
};
|
|
}
|
|
|
|
constructor(
|
|
src: string,
|
|
altText: string,
|
|
key?: NodeKey,
|
|
) {
|
|
super(key);
|
|
this.__src = src;
|
|
this.__altText = altText;
|
|
}
|
|
|
|
exportJSON(): SerializedImageNode {
|
|
return {
|
|
altText: this.getAltText(),
|
|
src: this.getSrc(),
|
|
type: 'image',
|
|
version: 1,
|
|
};
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
setAltText(altText: string): void {
|
|
const writable = this.getWritable();
|
|
|
|
if (altText !== undefined) {
|
|
writable.__altText = altText;
|
|
}
|
|
}
|
|
|
|
decorate(): JSX.Element {
|
|
return (
|
|
<Suspense fallback={null}>
|
|
<ImageComponent
|
|
src={this.__src}
|
|
altText={this.__altText}
|
|
nodeKey={this.getKey()}
|
|
/>
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
const $createImageNode = ({
|
|
altText = '',
|
|
src,
|
|
key,
|
|
}: ImagePayload): ImageNode => {
|
|
return $applyNodeReplacement(
|
|
new ImageNode(
|
|
src,
|
|
altText,
|
|
key,
|
|
),
|
|
);
|
|
};
|
|
|
|
const $isImageNode = (
|
|
node: LexicalNode | null | undefined,
|
|
): node is ImageNode => node instanceof ImageNode;
|
|
|
|
export {
|
|
type ImagePayload,
|
|
type SerializedImageNode,
|
|
ImageNode,
|
|
$createImageNode,
|
|
$isImageNode,
|
|
};
|