From 9d732d8dd826af6b988ab103560b9b84eab8640e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Nov 2025 17:02:17 +0000 Subject: [PATCH] Lexical: Started dev API outline --- dev/docs/wysiwyg-js-api.md | 8 ++ resources/js/wysiwyg/api/api.ts | 11 +++ resources/js/wysiwyg/api/ui.ts | 79 +++++++++++++++++++ resources/js/wysiwyg/index.ts | 7 ++ resources/js/wysiwyg/ui/defaults/toolbars.ts | 18 ++--- .../ui/framework/blocks/overflow-container.ts | 12 ++- resources/js/wysiwyg/ui/framework/manager.ts | 4 + 7 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 dev/docs/wysiwyg-js-api.md create mode 100644 resources/js/wysiwyg/api/api.ts create mode 100644 resources/js/wysiwyg/api/ui.ts diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md new file mode 100644 index 000000000..7fd91a5e6 --- /dev/null +++ b/dev/docs/wysiwyg-js-api.md @@ -0,0 +1,8 @@ +# WYSIWYG JavaScript API + +TODO - Link to this from JS code doc. +TODO - Create JS events and add to the js public events doc. + +TODO - Document the JS API. + +TODO - Add testing coverage \ No newline at end of file diff --git a/resources/js/wysiwyg/api/api.ts b/resources/js/wysiwyg/api/api.ts new file mode 100644 index 000000000..434109c7a --- /dev/null +++ b/resources/js/wysiwyg/api/api.ts @@ -0,0 +1,11 @@ +import {EditorApiUiModule} from "./ui"; +import {EditorUiContext} from "../ui/framework/core"; + +export class EditorApi { + + public ui: EditorApiUiModule; + + constructor(context: EditorUiContext) { + this.ui = new EditorApiUiModule(context); + } +} \ No newline at end of file diff --git a/resources/js/wysiwyg/api/ui.ts b/resources/js/wysiwyg/api/ui.ts new file mode 100644 index 000000000..faad5d316 --- /dev/null +++ b/resources/js/wysiwyg/api/ui.ts @@ -0,0 +1,79 @@ +import {EditorButton} from "../ui/framework/buttons"; +import {EditorUiContext} from "../ui/framework/core"; +import {EditorOverflowContainer} from "../ui/framework/blocks/overflow-container"; + +type EditorApiButtonOptions = { + label?: string; + icon?: string; + onClick: () => void; +}; + +class EditorApiButton { + #button: EditorButton; + #isActive: boolean = false; + + constructor(options: EditorApiButtonOptions, context: EditorUiContext) { + this.#button = new EditorButton({ + label: options.label || '', + icon: options.icon || '', + action: () => { + options.onClick(); + }, + isActive: () => this.#isActive, + }); + this.#button.setContext(context); + } + + setActive(active: boolean = true): void { + this.#isActive = active; + this.#button.setActiveState(active); + } + + _getOriginalModel() { + return this.#button; + } +} + +class EditorApiToolbarSection { + #section: EditorOverflowContainer; + label: string; + + constructor(section: EditorOverflowContainer) { + this.#section = section; + this.label = section.getLabel(); + } + + getLabel(): string { + return this.#section.getLabel(); + } + + addButton(button: EditorApiButton, targetIndex: number = -1): void { + this.#section.addChild(button._getOriginalModel(), targetIndex); + this.#section.rebuildDOM(); + } +} + + +export class EditorApiUiModule { + #context: EditorUiContext; + + constructor(context: EditorUiContext) { + this.#context = context; + } + + createButton(options: EditorApiButtonOptions): EditorApiButton { + return new EditorApiButton(options, this.#context); + } + + getToolbarSections(): EditorApiToolbarSection[] { + const toolbar = this.#context.manager.getToolbar(); + if (!toolbar) { + return []; + } + + const sections = toolbar.getChildren(); + return sections.filter(section => { + return section instanceof EditorOverflowContainer; + }).map(section => new EditorApiToolbarSection(section)); + } +} \ No newline at end of file diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index 2c8c3b952..c3ff37e7e 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -21,6 +21,7 @@ import {CodeBlockDecorator} from "./ui/decorators/code-block"; import {DiagramDecorator} from "./ui/decorators/diagram"; import {registerMouseHandling} from "./services/mouse-handling"; import {registerSelectionHandling} from "./services/selection-handling"; +import {EditorApi} from "./api/api"; const theme = { text: { @@ -94,6 +95,12 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st registerCommonNodeMutationListeners(context); + // TODO - Emit this as a public event instead + // TODO - Add support to basic editor below + const api = new EditorApi(context); + // @ts-ignore + window.editorApi = api; + return new SimpleWysiwygEditorInterface(context); } diff --git a/resources/js/wysiwyg/ui/defaults/toolbars.ts b/resources/js/wysiwyg/ui/defaults/toolbars.ts index 33468e0a2..0c48f5954 100644 --- a/resources/js/wysiwyg/ui/defaults/toolbars.ts +++ b/resources/js/wysiwyg/ui/defaults/toolbars.ts @@ -88,7 +88,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai return new EditorSimpleClassContainer('editor-toolbar-main', [ // History state - new EditorOverflowContainer(2, [ + new EditorOverflowContainer('history', 2, [ new EditorButton(undo), new EditorButton(redo), ]), @@ -110,7 +110,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ]), // Inline formats - new EditorOverflowContainer(6, [ + new EditorOverflowContainer('inline_formats', 6, [ new EditorButton(bold), new EditorButton(italic), new EditorButton(underline), @@ -128,7 +128,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ]), // Alignment - new EditorOverflowContainer(6, [ + new EditorOverflowContainer('alignment', 6, [ new EditorButton(alignLeft), new EditorButton(alignCenter), new EditorButton(alignRight), @@ -138,7 +138,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ].filter(x => x !== null)), // Lists - new EditorOverflowContainer(3, [ + new EditorOverflowContainer('lists', 3, [ new EditorButton(bulletList), new EditorButton(numberList), new EditorButton(taskList), @@ -147,7 +147,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ]), // Insert types - new EditorOverflowContainer(4, [ + new EditorOverflowContainer('inserts', 4, [ new EditorButton(link), new EditorDropdownButton({button: table, direction: 'vertical', showAside: false}, [ @@ -200,7 +200,7 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai ]), // Meta elements - new EditorOverflowContainer(3, [ + new EditorOverflowContainer('meta', 3, [ new EditorButton(source), new EditorButton(about), new EditorButton(fullscreen), @@ -261,16 +261,16 @@ export const contextToolbars: Record = { selector: 'td,th', content() { return [ - new EditorOverflowContainer(2, [ + new EditorOverflowContainer('table', 2, [ new EditorButton(tableProperties), new EditorButton(deleteTable), ]), - new EditorOverflowContainer(3, [ + new EditorOverflowContainer('table_row',3, [ new EditorButton(insertRowAbove), new EditorButton(insertRowBelow), new EditorButton(deleteRow), ]), - new EditorOverflowContainer(3, [ + new EditorOverflowContainer('table_column', 3, [ new EditorButton(insertColumnBefore), new EditorButton(insertColumnAfter), new EditorButton(deleteColumn), diff --git a/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts b/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts index 1c9664505..3b582eee6 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts @@ -9,9 +9,11 @@ export class EditorOverflowContainer extends EditorContainerUiElement { protected size: number; protected overflowButton: EditorDropdownButton; protected content: EditorUiElement[]; + protected label: string; - constructor(size: number, children: EditorUiElement[]) { + constructor(label: string, size: number, children: EditorUiElement[]) { super(children); + this.label = label; this.size = size; this.content = children; this.overflowButton = new EditorDropdownButton({ @@ -24,6 +26,11 @@ export class EditorOverflowContainer extends EditorContainerUiElement { this.addChildren(this.overflowButton); } + addChild(child: EditorUiElement, targetIndex: number = -1): void { + this.content.splice(targetIndex, 0, child); + this.addChildren(child); + } + protected buildDOM(): HTMLElement { const slicePosition = this.content.length > this.size ? this.size - 1 : this.size; const visibleChildren = this.content.slice(0, slicePosition); @@ -41,5 +48,8 @@ export class EditorOverflowContainer extends EditorContainerUiElement { }, visibleElements); } + getLabel(): string { + return this.label; + } } \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 3f46455da..1adc0b619 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -109,6 +109,10 @@ export class EditorUIManager { this.getContext().containerDOM.prepend(toolbar.getDOMElement()); } + getToolbar(): EditorContainerUiElement|null { + return this.toolbar; + } + registerContextToolbar(key: string, definition: EditorContextToolbarDefinition) { this.contextToolbarDefinitionsByKey[key] = definition; }