diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 2ef117b88..685031ff8 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -4,7 +4,7 @@ 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"; - +import {EditorContextToolbar, EditorContextToolbarDefinition} from "./toolbars"; export class EditorUIManager { @@ -13,6 +13,8 @@ export class EditorUIManager { protected decoratorInstancesByNodeKey: Record = {}; protected context: EditorUiContext|null = null; protected toolbar: EditorContainerUiElement|null = null; + protected contextToolbarDefinitionsByKey: Record = {}; + protected activeContextToolbars: EditorContextToolbar[] = []; setContext(context: EditorUiContext) { this.context = context; @@ -80,10 +82,59 @@ export class EditorUIManager { this.getContext().editorDOM.before(toolbar.getDOMElement()); } - protected triggerStateUpdate(state: EditorUiStateUpdate): void { + registerContextToolbar(key: string, definition: EditorContextToolbarDefinition) { + this.contextToolbarDefinitionsByKey[key] = definition; + } + + protected triggerStateUpdate(update: EditorUiStateUpdate): void { const context = this.getContext(); - context.lastSelection = state.selection; - this.toolbar?.updateState(state); + context.lastSelection = update.selection; + this.toolbar?.updateState(update); + this.updateContextToolbars(update); + for (const toolbar of this.activeContextToolbars) { + toolbar.updateState(update); + } + } + + protected updateContextToolbars(update: EditorUiStateUpdate): void { + for (const toolbar of this.activeContextToolbars) { + toolbar.empty(); + toolbar.getDOMElement().remove(); + } + + const node = (update.selection?.getNodes() || [])[0] || null; + if (!node) { + return; + } + + const element = update.editor.getElementByKey(node.getKey()); + if (!element) { + return; + } + + const toolbarKeys = Object.keys(this.contextToolbarDefinitionsByKey); + const contentByTarget = new Map(); + for (const key of toolbarKeys) { + const definition = this.contextToolbarDefinitionsByKey[key]; + const matchingElem = ((element.closest(definition.selector)) || (element.querySelector(definition.selector))) as HTMLElement|null; + if (matchingElem) { + const targetEl = definition.displayTargetLocator ? definition.displayTargetLocator(matchingElem) : matchingElem; + if (!contentByTarget.has(targetEl)) { + contentByTarget.set(targetEl, []) + } + // @ts-ignore + contentByTarget.get(targetEl).push(...definition.content); + } + } + + for (const [target, contents] of contentByTarget) { + const toolbar = new EditorContextToolbar(contents); + toolbar.setContext(this.getContext()); + this.activeContextToolbars.push(toolbar); + + this.getContext().editorDOM.after(toolbar.getDOMElement()); + toolbar.attachTo(target); + } } protected setupEditor(editor: LexicalEditor) { diff --git a/resources/js/wysiwyg/ui/framework/toolbars.ts b/resources/js/wysiwyg/ui/framework/toolbars.ts new file mode 100644 index 000000000..a844161f4 --- /dev/null +++ b/resources/js/wysiwyg/ui/framework/toolbars.ts @@ -0,0 +1,36 @@ +import {EditorContainerUiElement, EditorUiElement} from "./core"; +import {el} from "../../helpers"; + +export type EditorContextToolbarDefinition = { + selector: string; + content: EditorUiElement[], + displayTargetLocator?: (originalTarget: HTMLElement) => HTMLElement; +}; + +export class EditorContextToolbar extends EditorContainerUiElement { + + protected buildDOM(): HTMLElement { + return el('div', { + class: 'editor-context-toolbar', + }, this.getChildren().map(child => child.getDOMElement())); + } + + attachTo(target: HTMLElement) { + // Todo - attach to target position + console.log('attaching context toolbar to', target); + } + + insert(children: EditorUiElement[]) { + this.addChildren(...children); + const dom = this.getDOMElement(); + dom.append(...children.map(child => child.getDOMElement())); + } + + empty() { + const children = this.getChildren(); + for (const child of children) { + child.getDOMElement().remove(); + } + this.removeChildren(...children); + } +} \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/index.ts b/resources/js/wysiwyg/ui/index.ts index c823f9f95..9f05e211e 100644 --- a/resources/js/wysiwyg/ui/index.ts +++ b/resources/js/wysiwyg/ui/index.ts @@ -1,5 +1,5 @@ import {LexicalEditor} from "lexical"; -import {getMainEditorFullToolbar} from "./toolbars"; +import {getImageToolbarContent, getMainEditorFullToolbar} from "./toolbars"; import {EditorUIManager} from "./framework/manager"; import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} from "./defaults/form-definitions"; import {ImageDecorator} from "./decorators/image"; @@ -33,6 +33,15 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { form: sourceFormDefinition, }); + // Register context toolbars + manager.registerContextToolbar('image', { + selector: 'img', + content: getImageToolbarContent(), + displayTargetLocator(originalTarget: HTMLElement) { + return originalTarget.closest('a') || originalTarget; + } + }); + // Register image decorator listener manager.registerDecoratorType('image', ImageDecorator); } \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index 02e46549e..6e822e9c1 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -9,7 +9,7 @@ import { undo, warningCallout } from "./defaults/button-definitions"; -import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiContext} from "./framework/core"; +import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiContext, EditorUiElement} from "./framework/core"; import {el} from "../helpers"; import {EditorFormatMenu} from "./framework/blocks/format-menu"; import {FormatPreviewButton} from "./framework/blocks/format-preview-button"; @@ -87,4 +87,8 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { } }) ]); +} + +export function getImageToolbarContent(): EditorUiElement[] { + return [new EditorButton(image)]; } \ No newline at end of file