2024-06-03 11:56:31 -04:00
|
|
|
import {
|
|
|
|
DecoratorNode,
|
|
|
|
DOMConversion,
|
|
|
|
DOMConversionMap,
|
|
|
|
DOMConversionOutput,
|
|
|
|
LexicalEditor, LexicalNode,
|
|
|
|
SerializedLexicalNode,
|
|
|
|
Spread
|
|
|
|
} from "lexical";
|
|
|
|
import type {EditorConfig} from "lexical/LexicalEditor";
|
|
|
|
import {el} from "../helpers";
|
2024-06-05 08:04:49 -04:00
|
|
|
import {EditorDecoratorAdapter} from "../ui/framework/decorator";
|
2024-06-03 11:56:31 -04:00
|
|
|
|
|
|
|
export interface ImageNodeOptions {
|
|
|
|
alt?: string;
|
|
|
|
width?: number;
|
|
|
|
height?: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type SerializedImageNode = Spread<{
|
|
|
|
src: string;
|
|
|
|
alt: string;
|
|
|
|
width: number;
|
|
|
|
height: number;
|
|
|
|
}, SerializedLexicalNode>
|
|
|
|
|
2024-06-05 08:04:49 -04:00
|
|
|
export class ImageNode extends DecoratorNode<EditorDecoratorAdapter> {
|
2024-06-03 11:56:31 -04:00
|
|
|
__src: string = '';
|
|
|
|
__alt: string = '';
|
|
|
|
__width: number = 0;
|
|
|
|
__height: number = 0;
|
|
|
|
// TODO - Alignment
|
|
|
|
|
|
|
|
static getType(): string {
|
|
|
|
return 'image';
|
|
|
|
}
|
|
|
|
|
|
|
|
static clone(node: ImageNode): ImageNode {
|
|
|
|
return new ImageNode(node.__src, {
|
|
|
|
alt: node.__alt,
|
|
|
|
width: node.__width,
|
|
|
|
height: node.__height,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(src: string, options: ImageNodeOptions, key?: string) {
|
|
|
|
super(key);
|
|
|
|
this.__src = src;
|
|
|
|
if (options.alt) {
|
|
|
|
this.__alt = options.alt;
|
|
|
|
}
|
|
|
|
if (options.width) {
|
|
|
|
this.__width = options.width;
|
|
|
|
}
|
|
|
|
if (options.height) {
|
|
|
|
this.__height = options.height;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-05 13:43:42 -04:00
|
|
|
setSrc(src: string): void {
|
|
|
|
const self = this.getWritable();
|
|
|
|
self.__src = src;
|
|
|
|
}
|
|
|
|
|
|
|
|
getSrc(): string {
|
|
|
|
const self = this.getLatest();
|
|
|
|
return self.__src;
|
|
|
|
}
|
|
|
|
|
2024-06-03 11:56:31 -04:00
|
|
|
setAltText(altText: string): void {
|
|
|
|
const self = this.getWritable();
|
|
|
|
self.__alt = altText;
|
|
|
|
}
|
|
|
|
|
|
|
|
getAltText(): string {
|
|
|
|
const self = this.getLatest();
|
|
|
|
return self.__alt;
|
|
|
|
}
|
|
|
|
|
|
|
|
setHeight(height: number): void {
|
|
|
|
const self = this.getWritable();
|
|
|
|
self.__height = height;
|
|
|
|
}
|
|
|
|
|
|
|
|
getHeight(): number {
|
|
|
|
const self = this.getLatest();
|
|
|
|
return self.__height;
|
|
|
|
}
|
|
|
|
|
|
|
|
setWidth(width: number): void {
|
|
|
|
const self = this.getWritable();
|
|
|
|
self.__width = width;
|
|
|
|
}
|
|
|
|
|
|
|
|
getWidth(): number {
|
|
|
|
const self = this.getLatest();
|
|
|
|
return self.__width;
|
|
|
|
}
|
|
|
|
|
|
|
|
isInline(): boolean {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-06-05 08:04:49 -04:00
|
|
|
decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
|
|
|
|
return {
|
|
|
|
type: 'image',
|
|
|
|
getNode: () => this,
|
|
|
|
};
|
2024-06-03 11:56:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
createDOM(_config: EditorConfig, _editor: LexicalEditor) {
|
|
|
|
const element = document.createElement('img');
|
|
|
|
element.setAttribute('src', this.__src);
|
|
|
|
|
|
|
|
if (this.__width) {
|
|
|
|
element.setAttribute('width', String(this.__width));
|
|
|
|
}
|
|
|
|
if (this.__height) {
|
|
|
|
element.setAttribute('height', String(this.__height));
|
|
|
|
}
|
|
|
|
if (this.__alt) {
|
|
|
|
element.setAttribute('alt', this.__alt);
|
|
|
|
}
|
|
|
|
return el('span', {class: 'editor-image-wrap'}, [
|
|
|
|
element,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2024-06-05 08:04:49 -04:00
|
|
|
updateDOM(prevNode: ImageNode, dom: HTMLElement) {
|
|
|
|
const image = dom.querySelector('img');
|
|
|
|
if (!image) return false;
|
|
|
|
|
|
|
|
if (prevNode.__src !== this.__src) {
|
|
|
|
image.setAttribute('src', this.__src);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prevNode.__width !== this.__width) {
|
|
|
|
if (this.__width) {
|
|
|
|
image.setAttribute('width', String(this.__width));
|
|
|
|
} else {
|
|
|
|
image.removeAttribute('width');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prevNode.__height !== this.__height) {
|
|
|
|
if (this.__height) {
|
|
|
|
image.setAttribute('height', String(this.__height));
|
|
|
|
} else {
|
|
|
|
image.removeAttribute('height');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prevNode.__alt !== this.__alt) {
|
|
|
|
if (this.__alt) {
|
|
|
|
image.setAttribute('alt', String(this.__alt));
|
|
|
|
} else {
|
|
|
|
image.removeAttribute('alt');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-03 11:56:31 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static importDOM(): DOMConversionMap|null {
|
|
|
|
return {
|
|
|
|
img(node: HTMLElement): DOMConversion|null {
|
|
|
|
return {
|
|
|
|
conversion: (element: HTMLElement): DOMConversionOutput|null => {
|
|
|
|
|
|
|
|
const src = element.getAttribute('src') || '';
|
|
|
|
const options: ImageNodeOptions = {
|
|
|
|
alt: element.getAttribute('alt') || '',
|
|
|
|
height: Number.parseInt(element.getAttribute('height') || '0'),
|
|
|
|
width: Number.parseInt(element.getAttribute('width') || '0'),
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
node: new ImageNode(src, options),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
priority: 3,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
exportJSON(): SerializedImageNode {
|
|
|
|
return {
|
|
|
|
type: 'image',
|
|
|
|
version: 1,
|
|
|
|
src: this.__src,
|
|
|
|
alt: this.__alt,
|
|
|
|
height: this.__height,
|
|
|
|
width: this.__width
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
static importJSON(serializedNode: SerializedImageNode): ImageNode {
|
|
|
|
return $createImageNode(serializedNode.src, {
|
|
|
|
alt: serializedNode.alt,
|
|
|
|
width: serializedNode.width,
|
|
|
|
height: serializedNode.height,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function $createImageNode(src: string, options: ImageNodeOptions = {}): ImageNode {
|
|
|
|
return new ImageNode(src, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function $isImageNode(node: LexicalNode | null | undefined) {
|
|
|
|
return node instanceof ImageNode;
|
|
|
|
}
|