}
}
+ 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;
let startingHeight = element.clientHeight;
let startingRatio = startingWidth / startingHeight;
let hasHeight = false;
+ let firstChange = true;
context.editor.getEditorState().read(() => {
startingWidth = node.getWidth() || startingWidth;
startingHeight = node.getHeight() || startingHeight;
if (flipYChange) {
yChange = 0 - yChange;
}
- const balancedChange = Math.sqrt(Math.pow(xChange, 2) + Math.pow(yChange, 2));
+ const balancedChange = Math.sqrt(Math.pow(Math.abs(xChange), 2) + Math.pow(Math.abs(yChange), 2));
const increase = xChange + yChange > 0;
const directedChange = increase ? balancedChange : 0-balancedChange;
const newWidth = Math.max(5, Math.round(startingWidth + directedChange));
newHeight = newWidth * startingRatio;
}
+ const updateOptions = firstChange ? {} : {tag: 'history-merge'};
context.editor.update(() => {
const node = this.getNode() as ImageNode;
node.setWidth(newWidth);
node.setHeight(newHeight);
- });
+ }, updateOptions);
+ firstChange = false;
};
const mouseUpListener = (event: MouseEvent) => {
} from "@lexical/rich-text";
import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link";
import {EditorUiContext} from "../framework/core";
+import {$isImageNode, ImageNode} from "../../nodes/image";
export const undo: EditorButtonDefinition = {
label: 'Undo',
}
};
+export const image: EditorButtonDefinition = {
+ label: 'Insert/Edit Image',
+ action(context: EditorUiContext) {
+ const imageModal = context.manager.createModal('image');
+ const selection = context.lastSelection;
+ const selectedImage = getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
+
+ context.editor.getEditorState().read(() => {
+ let formDefaults = {};
+ if (selectedImage) {
+ formDefaults = {
+ src: selectedImage.getSrc(),
+ alt: selectedImage.getAltText(),
+ height: selectedImage.getHeight(),
+ width: selectedImage.getWidth(),
+ }
+
+ context.editor.update(() => {
+ const selection = $createNodeSelection();
+ selection.add(selectedImage.getKey());
+ $setSelection(selection);
+ });
+ }
+
+ imageModal.show(formDefaults);
+ });
+ },
+ isActive(selection: BaseSelection|null): boolean {
+ return selectionContainsNodeType(selection, $isImageNode);
+ }
+};
+
import {EditorUiContext} from "../framework/core";
import {$createLinkNode} from "@lexical/link";
import {$createTextNode, $getSelection} from "lexical";
+import {$createImageNode} from "../../nodes/image";
export const link: EditorFormDefinition = {
}
} as EditorSelectFormFieldDefinition,
],
+};
+
+export const image: EditorFormDefinition = {
+ submitText: 'Apply',
+ action(formData, context: EditorUiContext) {
+ context.editor.update(() => {
+ const selection = $getSelection();
+ const imageNode = $createImageNode(formData.get('src')?.toString() || '', {
+ alt: formData.get('alt')?.toString() || '',
+ height: Number(formData.get('height')?.toString() || '0'),
+ width: Number(formData.get('width')?.toString() || '0'),
+ });
+ selection?.insertNodes([imageNode]);
+ });
+ return true;
+ },
+ fields: [
+ {
+ label: 'Source',
+ name: 'src',
+ type: 'text',
+ },
+ {
+ label: 'Alternative description',
+ name: 'alt',
+ type: 'text',
+ },
+ {
+ label: 'Width',
+ name: 'width',
+ type: 'text',
+ },
+ {
+ label: 'Height',
+ name: 'height',
+ type: 'text',
+ },
+ ],
};
\ No newline at end of file
editor: LexicalEditor,
translate: (text: string) => string,
manager: EditorUIManager,
+ lastSelection: BaseSelection|null,
};
export abstract class EditorUiElement {
} from "lexical";
import {getMainEditorFullToolbar} from "./toolbars";
import {EditorUIManager} from "./framework/manager";
-import {link as linkFormDefinition} from "./defaults/form-definitions";
+import {image as imageFormDefinition, link as linkFormDefinition} from "./defaults/form-definitions";
import {DecoratorListener} from "lexical/LexicalEditor";
import type {NodeKey} from "lexical/LexicalNode";
import {EditorDecoratorAdapter} from "./framework/decorator";
import {ImageDecorator} from "./decorators/image";
+import {EditorUiContext} from "./framework/core";
export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
const manager = new EditorUIManager();
- const context = {
+ const context: EditorUiContext = {
editor,
manager,
translate: (text: string): string => text,
+ lastSelection: null,
};
manager.setContext(context);
title: 'Insert/Edit link',
form: linkFormDefinition,
});
+ manager.registerModal('image', {
+ title: 'Insert/Edit Image',
+ form: imageFormDefinition
+ })
// Register decorator listener
// Maybe move to manager?
editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
const selection = $getSelection();
toolbar.updateState({editor, selection});
+ context.lastSelection = selection;
return false;
}, COMMAND_PRIORITY_LOW);
}
\ No newline at end of file
import {
blockquote, bold, code,
dangerCallout,
- h2, h3, h4, h5,
+ h2, h3, h4, h5, image,
infoCallout, italic, link, paragraph,
redo, strikethrough, subscript,
successCallout, superscript, underline,
new EditorButton(code),
new EditorButton(link),
+ new EditorButton(image),
]);
}
\ No newline at end of file