From 0722960260d740e7e0c00b9f6da13992688a8752 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 5 Jun 2024 18:43:42 +0100 Subject: [PATCH] Lexical: Added selection to state for aligned reading Connected up to work with image form --- resources/js/wysiwyg/nodes/image.ts | 10 +++++ resources/js/wysiwyg/ui/decorators/image.ts | 7 +++- .../wysiwyg/ui/defaults/button-definitions.ts | 33 ++++++++++++++++ .../wysiwyg/ui/defaults/form-definitions.ts | 39 +++++++++++++++++++ resources/js/wysiwyg/ui/framework/core.ts | 1 + resources/js/wysiwyg/ui/index.ts | 11 +++++- resources/js/wysiwyg/ui/toolbars.ts | 3 +- 7 files changed, 99 insertions(+), 5 deletions(-) diff --git a/resources/js/wysiwyg/nodes/image.ts b/resources/js/wysiwyg/nodes/image.ts index 9f017b5fe..92d5518db 100644 --- a/resources/js/wysiwyg/nodes/image.ts +++ b/resources/js/wysiwyg/nodes/image.ts @@ -57,6 +57,16 @@ export class ImageNode extends DecoratorNode { } } + 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; diff --git a/resources/js/wysiwyg/ui/decorators/image.ts b/resources/js/wysiwyg/ui/decorators/image.ts index b3ceee65f..1692d078d 100644 --- a/resources/js/wysiwyg/ui/decorators/image.ts +++ b/resources/js/wysiwyg/ui/decorators/image.ts @@ -88,6 +88,7 @@ export class ImageDecorator extends EditorDecorator { 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; @@ -109,7 +110,7 @@ export class ImageDecorator extends EditorDecorator { 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)); @@ -118,11 +119,13 @@ export class ImageDecorator extends EditorDecorator { 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) => { diff --git a/resources/js/wysiwyg/ui/defaults/button-definitions.ts b/resources/js/wysiwyg/ui/defaults/button-definitions.ts index f5be82519..92f0cfc81 100644 --- a/resources/js/wysiwyg/ui/defaults/button-definitions.ts +++ b/resources/js/wysiwyg/ui/defaults/button-definitions.ts @@ -25,6 +25,7 @@ import { } 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', @@ -168,3 +169,35 @@ export const link: EditorButtonDefinition = { } }; +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); + } +}; + diff --git a/resources/js/wysiwyg/ui/defaults/form-definitions.ts b/resources/js/wysiwyg/ui/defaults/form-definitions.ts index 457efa421..13e7a9c9f 100644 --- a/resources/js/wysiwyg/ui/defaults/form-definitions.ts +++ b/resources/js/wysiwyg/ui/defaults/form-definitions.ts @@ -2,6 +2,7 @@ import {EditorFormDefinition, EditorSelectFormFieldDefinition} from "../framewor import {EditorUiContext} from "../framework/core"; import {$createLinkNode} from "@lexical/link"; import {$createTextNode, $getSelection} from "lexical"; +import {$createImageNode} from "../../nodes/image"; export const link: EditorFormDefinition = { @@ -47,4 +48,42 @@ 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 diff --git a/resources/js/wysiwyg/ui/framework/core.ts b/resources/js/wysiwyg/ui/framework/core.ts index 68d845b42..2fdadcb40 100644 --- a/resources/js/wysiwyg/ui/framework/core.ts +++ b/resources/js/wysiwyg/ui/framework/core.ts @@ -10,6 +10,7 @@ export type EditorUiContext = { editor: LexicalEditor, translate: (text: string) => string, manager: EditorUIManager, + lastSelection: BaseSelection|null, }; export abstract class EditorUiElement { diff --git a/resources/js/wysiwyg/ui/index.ts b/resources/js/wysiwyg/ui/index.ts index 19320b262..8227dec68 100644 --- a/resources/js/wysiwyg/ui/index.ts +++ b/resources/js/wysiwyg/ui/index.ts @@ -6,18 +6,20 @@ import { } 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); @@ -31,6 +33,10 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { title: 'Insert/Edit link', form: linkFormDefinition, }); + manager.registerModal('image', { + title: 'Insert/Edit Image', + form: imageFormDefinition + }) // Register decorator listener // Maybe move to manager? @@ -54,6 +60,7 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { 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 diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index 2d5063cf4..2802b1ca7 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -2,7 +2,7 @@ import {EditorButton, FormatPreviewButton} from "./framework/buttons"; import { blockquote, bold, code, dangerCallout, - h2, h3, h4, h5, + h2, h3, h4, h5, image, infoCallout, italic, link, paragraph, redo, strikethrough, subscript, successCallout, superscript, underline, @@ -40,5 +40,6 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { new EditorButton(code), new EditorButton(link), + new EditorButton(image), ]); } \ No newline at end of file