mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-22 07:12:36 +00:00
16518a4f89
- Updated selection change detection to be more accurate - Added UI refresh for extra actions - Fixed remove link deleting contents
238 lines
6.4 KiB
TypeScript
238 lines
6.4 KiB
TypeScript
import {
|
|
DOMConversion,
|
|
DOMConversionMap,
|
|
DOMConversionOutput, ElementNode,
|
|
LexicalEditor, LexicalNode,
|
|
Spread
|
|
} from "lexical";
|
|
import type {EditorConfig} from "lexical/LexicalEditor";
|
|
import {CommonBlockAlignment, extractAlignmentFromElement} from "./_common";
|
|
import {$selectSingleNode} from "../utils/selection";
|
|
import {SerializedElementNode} from "lexical/nodes/LexicalElementNode";
|
|
|
|
export interface ImageNodeOptions {
|
|
alt?: string;
|
|
width?: number;
|
|
height?: number;
|
|
}
|
|
|
|
export type SerializedImageNode = Spread<{
|
|
src: string;
|
|
alt: string;
|
|
width: number;
|
|
height: number;
|
|
alignment: CommonBlockAlignment;
|
|
}, SerializedElementNode>
|
|
|
|
export class ImageNode extends ElementNode {
|
|
__src: string = '';
|
|
__alt: string = '';
|
|
__width: number = 0;
|
|
__height: number = 0;
|
|
__alignment: CommonBlockAlignment = '';
|
|
|
|
static getType(): string {
|
|
return 'image';
|
|
}
|
|
|
|
static clone(node: ImageNode): ImageNode {
|
|
const newNode = new ImageNode(node.__src, {
|
|
alt: node.__alt,
|
|
width: node.__width,
|
|
height: node.__height,
|
|
}, node.__key);
|
|
newNode.__alignment = node.__alignment;
|
|
return newNode;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
setSrc(src: string): void {
|
|
const self = this.getWritable();
|
|
self.__src = src;
|
|
}
|
|
|
|
getSrc(): string {
|
|
const self = this.getLatest();
|
|
return self.__src;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
setAlignment(alignment: CommonBlockAlignment) {
|
|
const self = this.getWritable();
|
|
self.__alignment = alignment;
|
|
}
|
|
|
|
getAlignment(): CommonBlockAlignment {
|
|
const self = this.getLatest();
|
|
return self.__alignment;
|
|
}
|
|
|
|
isInline(): boolean {
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (this.__alignment) {
|
|
element.classList.add('align-' + this.__alignment);
|
|
}
|
|
|
|
element.addEventListener('click', e => {
|
|
_editor.update(() => {
|
|
$selectSingleNode(this);
|
|
});
|
|
});
|
|
|
|
return element;
|
|
}
|
|
|
|
updateDOM(prevNode: ImageNode, dom: HTMLElement) {
|
|
if (prevNode.__src !== this.__src) {
|
|
dom.setAttribute('src', this.__src);
|
|
}
|
|
|
|
if (prevNode.__width !== this.__width) {
|
|
if (this.__width) {
|
|
dom.setAttribute('width', String(this.__width));
|
|
} else {
|
|
dom.removeAttribute('width');
|
|
}
|
|
}
|
|
|
|
if (prevNode.__height !== this.__height) {
|
|
if (this.__height) {
|
|
dom.setAttribute('height', String(this.__height));
|
|
} else {
|
|
dom.removeAttribute('height');
|
|
}
|
|
}
|
|
|
|
if (prevNode.__alt !== this.__alt) {
|
|
if (this.__alt) {
|
|
dom.setAttribute('alt', String(this.__alt));
|
|
} else {
|
|
dom.removeAttribute('alt');
|
|
}
|
|
}
|
|
|
|
if (prevNode.__alignment !== this.__alignment) {
|
|
if (prevNode.__alignment) {
|
|
dom.classList.remove('align-' + prevNode.__alignment);
|
|
}
|
|
if (this.__alignment) {
|
|
dom.classList.add('align-' + this.__alignment);
|
|
}
|
|
}
|
|
|
|
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'),
|
|
}
|
|
|
|
const node = new ImageNode(src, options);
|
|
node.setAlignment(extractAlignmentFromElement(element));
|
|
|
|
return { node };
|
|
},
|
|
priority: 3,
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
exportJSON(): SerializedImageNode {
|
|
return {
|
|
...super.exportJSON(),
|
|
type: 'image',
|
|
version: 1,
|
|
src: this.__src,
|
|
alt: this.__alt,
|
|
height: this.__height,
|
|
width: this.__width,
|
|
alignment: this.__alignment,
|
|
};
|
|
}
|
|
|
|
static importJSON(serializedNode: SerializedImageNode): ImageNode {
|
|
const node = $createImageNode(serializedNode.src, {
|
|
alt: serializedNode.alt,
|
|
width: serializedNode.width,
|
|
height: serializedNode.height,
|
|
});
|
|
node.setAlignment(serializedNode.alignment);
|
|
return node;
|
|
}
|
|
}
|
|
|
|
export function $createImageNode(src: string, options: ImageNodeOptions = {}): ImageNode {
|
|
return new ImageNode(src, options);
|
|
}
|
|
|
|
export function $isImageNode(node: LexicalNode | null | undefined) {
|
|
return node instanceof ImageNode;
|
|
} |