diff --git a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts index b24b4c16c..70e1a9ffc 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts @@ -49,10 +49,10 @@ export class EditorDropdownButton extends EditorContainerUiElement { handleDropdown(button, menu, () => { this.open = true; - this.getContext().manager.triggerStateUpdate(this.button); + this.getContext().manager.triggerStateUpdateForElement(this.button); }, () => { this.open = false; - this.getContext().manager.triggerStateUpdate(this.button); + this.getContext().manager.triggerStateUpdateForElement(this.button); }); return wrapper; diff --git a/resources/js/wysiwyg/ui/framework/core.ts b/resources/js/wysiwyg/ui/framework/core.ts index 5e29a0f9e..2972c9821 100644 --- a/resources/js/wysiwyg/ui/framework/core.ts +++ b/resources/js/wysiwyg/ui/framework/core.ts @@ -9,6 +9,7 @@ export type EditorUiStateUpdate = { export type EditorUiContext = { editor: LexicalEditor, + editorDOM: HTMLElement, translate: (text: string) => string, manager: EditorUIManager, lastSelection: BaseSelection|null, diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 78ddc8ce3..2ef117b88 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -1,6 +1,9 @@ import {EditorFormModal, EditorFormModalDefinition} from "./modals"; -import {EditorUiContext, EditorUiElement} from "./core"; -import {EditorDecorator} from "./decorator"; +import {EditorContainerUiElement, EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./core"; +import {EditorDecorator, EditorDecoratorAdapter} from "./decorator"; +import {$getSelection, COMMAND_PRIORITY_LOW, LexicalEditor, SELECTION_CHANGE_COMMAND} from "lexical"; +import {DecoratorListener} from "lexical/LexicalEditor"; +import type {NodeKey} from "lexical/LexicalNode"; export class EditorUIManager { @@ -9,9 +12,11 @@ export class EditorUIManager { protected decoratorConstructorsByType: Record = {}; protected decoratorInstancesByNodeKey: Record = {}; protected context: EditorUiContext|null = null; + protected toolbar: EditorContainerUiElement|null = null; setContext(context: EditorUiContext) { this.context = context; + this.setupEditor(context.editor); } getContext(): EditorUiContext { @@ -22,7 +27,7 @@ export class EditorUIManager { return this.context; } - triggerStateUpdate(element: EditorUiElement) { + triggerStateUpdateForElement(element: EditorUiElement) { element.updateState({ selection: null, editor: this.getContext().editor @@ -49,7 +54,7 @@ export class EditorUIManager { this.decoratorConstructorsByType[type] = decorator; } - getDecorator(decoratorType: string, nodeKey: string): EditorDecorator { + protected getDecorator(decoratorType: string, nodeKey: string): EditorDecorator { if (this.decoratorInstancesByNodeKey[nodeKey]) { return this.decoratorInstancesByNodeKey[nodeKey]; } @@ -64,4 +69,47 @@ export class EditorUIManager { this.decoratorInstancesByNodeKey[nodeKey] = decorator; return decorator; } + + setToolbar(toolbar: EditorContainerUiElement) { + if (this.toolbar) { + this.toolbar.getDOMElement().remove(); + } + + this.toolbar = toolbar; + toolbar.setContext(this.getContext()); + this.getContext().editorDOM.before(toolbar.getDOMElement()); + } + + protected triggerStateUpdate(state: EditorUiStateUpdate): void { + const context = this.getContext(); + context.lastSelection = state.selection; + this.toolbar?.updateState(state); + } + + protected setupEditor(editor: LexicalEditor) { + // Update button states on editor selection change + editor.registerCommand(SELECTION_CHANGE_COMMAND, () => { + this.triggerStateUpdate({ + editor: editor, + selection: $getSelection(), + }); + return false; + }, COMMAND_PRIORITY_LOW); + + // Register our DOM decorate listener with the editor + const domDecorateListener: DecoratorListener = (decorators: Record) => { + const keys = Object.keys(decorators); + for (const key of keys) { + const decoratedEl = editor.getElementByKey(key); + const adapter = decorators[key]; + const decorator = this.getDecorator(adapter.type, key); + decorator.setNode(adapter.getNode()); + const decoratorEl = decorator.render(this.getContext()); + if (decoratedEl) { + decoratedEl.append(decoratorEl); + } + } + } + editor.registerDecoratorListener(domDecorateListener); + } } \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/index.ts b/resources/js/wysiwyg/ui/index.ts index b2fd7e05a..c823f9f95 100644 --- a/resources/js/wysiwyg/ui/index.ts +++ b/resources/js/wysiwyg/ui/index.ts @@ -1,15 +1,7 @@ -import { - $getSelection, - COMMAND_PRIORITY_LOW, - LexicalEditor, - SELECTION_CHANGE_COMMAND -} from "lexical"; +import {LexicalEditor} from "lexical"; import {getMainEditorFullToolbar} from "./toolbars"; import {EditorUIManager} from "./framework/manager"; import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} 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"; @@ -17,6 +9,7 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { const manager = new EditorUIManager(); const context: EditorUiContext = { editor, + editorDOM: element, manager, translate: (text: string): string => text, lastSelection: null, @@ -24,9 +17,7 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { manager.setContext(context); // Create primary toolbar - const toolbar = getMainEditorFullToolbar(); - toolbar.setContext(context); - element.before(toolbar.getDOMElement()); + manager.setToolbar(getMainEditorFullToolbar()); // Register modals manager.registerModal('link', { @@ -42,29 +33,6 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { form: sourceFormDefinition, }); - // Register decorator listener - // Maybe move to manager? + // Register image decorator listener manager.registerDecoratorType('image', ImageDecorator); - const domDecorateListener: DecoratorListener = (decorators: Record) => { - const keys = Object.keys(decorators); - for (const key of keys) { - const decoratedEl = editor.getElementByKey(key); - const adapter = decorators[key]; - const decorator = manager.getDecorator(adapter.type, key); - decorator.setNode(adapter.getNode()); - const decoratorEl = decorator.render(context); - if (decoratedEl) { - decoratedEl.append(decoratorEl); - } - } - } - editor.registerDecoratorListener(domDecorateListener); - - // Update button states on editor selection change - 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