import {
DOMConversion,
- DOMConversionMap, DOMConversionOutput,
+ DOMConversionMap, DOMConversionOutput, DOMExportOutput,
ElementNode,
LexicalEditor,
LexicalNode,
} from 'lexical';
import type {EditorConfig} from "lexical/LexicalEditor";
-import {el} from "../utils/dom";
+import {el, sizeToPixels} from "../utils/dom";
import {
CommonBlockAlignment,
SerializedCommonBlockNode,
updateElementWithCommonBlockProps
} from "./_common";
import {elem} from "../../services/dom";
+import {$selectSingleNode} from "../utils/selection";
export type MediaNodeTag = 'iframe' | 'embed' | 'object' | 'video' | 'audio';
export type MediaNodeSource = {
const newNode = new MediaNode(node.__tag, node.__key);
newNode.__attributes = Object.assign({}, node.__attributes);
newNode.__sources = node.__sources.map(s => Object.assign({}, s));
+ newNode.__id = node.__id;
+ newNode.__alignment = node.__alignment;
return newNode;
}
return self.__alignment;
}
- createDOM(_config: EditorConfig, _editor: LexicalEditor) {
+ setHeight(height: number): void {
+ if (!height) {
+ return;
+ }
+
+ const attrs = Object.assign({}, this.getAttributes(), {height});
+ this.setAttributes(attrs);
+ }
+
+ getHeight(): number {
+ const self = this.getLatest();
+ return sizeToPixels(self.__attributes.height || '0');
+ }
+
+ setWidth(width: number): void {
+ const attrs = Object.assign({}, this.getAttributes(), {width});
+ this.setAttributes(attrs);
+ }
+
+ getWidth(): number {
+ const self = this.getLatest();
+ return sizeToPixels(self.__attributes.width || '0');
+ }
+
+ isInline(): boolean {
+ return true;
+ }
+
+ createInnerDOM() {
const sources = (this.__tag === 'video' || this.__tag === 'audio') ? this.__sources : [];
const sourceEls = sources.map(source => el('source', source));
const element = el(this.__tag, this.__attributes, sourceEls);
return element;
}
+ createDOM(_config: EditorConfig, _editor: LexicalEditor) {
+ const media = this.createInnerDOM();
+ const wrap = el('span', {
+ class: media.className + ' editor-media-wrap',
+ }, [media]);
+
+ wrap.addEventListener('click', e => {
+ _editor.update(() => $selectSingleNode(this));
+ });
+
+ return wrap;
+ }
+
updateDOM(prevNode: unknown, dom: HTMLElement) {
return true;
}
};
}
+ exportDOM(editor: LexicalEditor): DOMExportOutput {
+ const element = this.createInnerDOM();
+ return { element };
+ }
+
exportJSON(): SerializedMediaNode {
return {
...super.exportJSON(),
-import {BaseSelection,} from "lexical";
+import {BaseSelection, LexicalNode,} from "lexical";
import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker";
import {el} from "../../../utils/dom";
-import {$isImageNode, ImageNode} from "../../../nodes/image";
+import {$isImageNode} from "../../../nodes/image";
import {EditorUiContext} from "../core";
+import {NodeHasSize} from "../../../nodes/_common";
+import {$isMediaNode} from "../../../nodes/media";
-class ImageResizer {
+function isNodeWithSize(node: LexicalNode): node is NodeHasSize&LexicalNode {
+ return $isImageNode(node) || $isMediaNode(node);
+}
+
+class NodeResizer {
protected context: EditorUiContext;
protected dom: HTMLElement|null = null;
protected scrollContainer: HTMLElement;
this.hide();
}
- if (nodes.length === 1 && $isImageNode(nodes[0])) {
- const imageNode = nodes[0];
- const nodeKey = imageNode.getKey();
- const imageDOM = this.context.editor.getElementByKey(nodeKey);
+ if (nodes.length === 1 && isNodeWithSize(nodes[0])) {
+ const node = nodes[0];
+ const nodeKey = node.getKey();
+ let nodeDOM = this.context.editor.getElementByKey(nodeKey);
- if (imageDOM) {
- this.showForImage(imageNode, imageDOM);
+ if (nodeDOM && nodeDOM.nodeName === 'SPAN') {
+ nodeDOM = nodeDOM.firstElementChild as HTMLElement;
+ }
+
+ if (nodeDOM) {
+ this.showForNode(node, nodeDOM);
}
}
}
this.hide();
}
- protected showForImage(node: ImageNode, dom: HTMLElement) {
+ protected showForNode(node: NodeHasSize&LexicalNode, dom: HTMLElement) {
this.dom = this.buildDOM();
- const ghost = el('img', {src: dom.getAttribute('src'), class: 'editor-image-resizer-ghost'});
+ let ghost = el('span', {class: 'editor-node-resizer-ghost'});
+ if ($isImageNode(node)) {
+ ghost = el('img', {src: dom.getAttribute('src'), class: 'editor-node-resizer-ghost'});
+ }
this.dom.append(ghost);
this.context.scrollDOM.append(this.dom);
this.activeSelection = node.getKey();
}
- protected updateDOMPosition(imageDOM: HTMLElement) {
+ protected updateDOMPosition(nodeDOM: HTMLElement) {
if (!this.dom) {
return;
}
- const imageBounds = imageDOM.getBoundingClientRect();
- this.dom.style.left = imageDOM.offsetLeft + 'px';
- this.dom.style.top = imageDOM.offsetTop + 'px';
- this.dom.style.width = imageBounds.width + 'px';
- this.dom.style.height = imageBounds.height + 'px';
+ const nodeDOMBounds = nodeDOM.getBoundingClientRect();
+ this.dom.style.left = nodeDOM.offsetLeft + 'px';
+ this.dom.style.top = nodeDOM.offsetTop + 'px';
+ this.dom.style.width = nodeDOMBounds.width + 'px';
+ this.dom.style.height = nodeDOMBounds.height + 'px';
}
protected updateDOMSize(width: number, height: number): void {
protected buildDOM() {
const handleClasses = ['nw', 'ne', 'se', 'sw'];
const handleElems = handleClasses.map(c => {
- return el('div', {class: `editor-image-resizer-handle ${c}`});
+ return el('div', {class: `editor-node-resizer-handle ${c}`});
});
return el('div', {
- class: 'editor-image-resizer',
+ class: 'editor-node-resizer',
}, handleElems);
}
- setupTracker(container: HTMLElement, node: ImageNode, imageDOM: HTMLElement): MouseDragTracker {
+ setupTracker(container: HTMLElement, node: NodeHasSize, nodeDOM: HTMLElement): MouseDragTracker {
let startingWidth: number = 0;
let startingHeight: number = 0;
let startingRatio: number = 0;
const increase = xChange + yChange > 0;
const directedChange = increase ? balancedChange : 0-balancedChange;
const newWidth = Math.max(5, Math.round(startingWidth + directedChange));
- const newHeight = newWidth * startingRatio;
+ const newHeight = Math.round(newWidth * startingRatio);
return {width: newWidth, height: newHeight};
};
- return new MouseDragTracker(container, '.editor-image-resizer-handle', {
+ return new MouseDragTracker(container, '.editor-node-resizer-handle', {
down(event: MouseEvent, handle: HTMLElement) {
_this.dom?.classList.add('active');
_this.context.editor.getEditorState().read(() => {
- const imageRect = imageDOM.getBoundingClientRect();
- startingWidth = node.getWidth() || imageRect.width;
- startingHeight = node.getHeight() || imageRect.height;
+ const domRect = nodeDOM.getBoundingClientRect();
+ startingWidth = node.getWidth() || domRect.width;
+ startingHeight = node.getHeight() || domRect.height;
if (node.getHeight()) {
hasHeight = true;
}
- startingRatio = startingWidth / startingHeight;
+ startingRatio = startingHeight / startingWidth;
});
flipXChange = handle.classList.contains('nw') || handle.classList.contains('sw');
node.setHeight(hasHeight ? size.height : 0);
_this.context.manager.triggerLayoutUpdate();
requestAnimationFrame(() => {
- _this.updateDOMPosition(imageDOM);
+ _this.updateDOMPosition(nodeDOM);
})
});
_this.dom?.classList.remove('active');
}
-export function registerImageResizer(context: EditorUiContext): (() => void) {
- const resizer = new ImageResizer(context);
+export function registerNodeResizer(context: EditorUiContext): (() => void) {
+ const resizer = new NodeResizer(context);
return () => {
resizer.teardown();