diff --git a/dev/docs/javascript-public-events.md b/dev/docs/javascript-public-events.md
index 85a4caad4..e9ea014ef 100644
--- a/dev/docs/javascript-public-events.md
+++ b/dev/docs/javascript-public-events.md
@@ -134,6 +134,47 @@ window.addEventListener('editor-tinymce::setup', event => {
});
```
+### `editor-wysiwyg::post-init`
+
+This is called after the (new custom-built Lexical-based) WYSIWYG editor has been initialised.
+
+#### Event Data
+
+- `usage` - A string label to identify the usage type of the WYSIWYG editor in BookStack.
+- `api` - An instance to the WYSIWYG editor API, as documented in the [WYSIWYG JavaScript API file](./wysiwyg-js-api.md).
+
+##### Example
+
+The below example shows how you'd use this API to create a button, with that button added to the main toolbar of the page editor, which inserts bold "Hello!" text on press:
+
+
+Show Example
+
+```javascript
+window.addEventListener('editor-wysiwyg::post-init', event => {
+ const {usage, api} = event.detail;
+ // Check that it's the page editor which is being loaded
+ if (usage !== 'page-editor') {
+ return;
+ }
+
+ // Create a custom button which inserts bold hello text on press
+ const button = api.ui.createButton({
+ label: 'Greet',
+ action: () => {
+ api.content.insertHtml(`Hello!`);
+ }
+ });
+
+ // Add the button to the start of the first section within the main toolbar
+ const toolbar = api.ui.getMainToolbar();
+ if (toolbar) {
+ toolbar.getSections()[0]?.addButton(button, 0);
+ }
+});
+```
+
+
### `library-cm6::configure-theme`
This event is called whenever a CodeMirror instance is loaded, as a method to configure the theme used by CodeMirror. This applies to all CodeMirror instances including in-page code blocks, editors using in BookStack settings, and the Page markdown editor.
@@ -319,41 +360,3 @@ window.addEventListener('library-cm6::post-init', event => {
});
```
-
-### `editor-wysiwyg::post-init`
-
-This is called after the (new custom-built Lexical-based) WYSIWYG editor has been initialised.
-
-#### Event Data
-
-- `usage` - A string label to identify the usage type of the WYSIWYG editor in BookStack.
-- `api` - An instance to the WYSIWYG editor API, as documented in the [WYSIWYG JavaScript API file](./wysiwyg-js-api.md).
-
-##### Example
-
-The below shows how you'd use this API to create a button, with that button added to the toolbar of the page editor, which inserts bold hello text on press:
-
-
-Show Example
-
-```javascript
-window.addEventListener('editor-wysiwyg::post-init', event => {
- const {usage, api} = event.detail;
- // Check that it's the page editor being loaded.
- if (usage !== 'page-editor') {
- return;
- }
-
- // Create a custom button which inserts bold hello text on press.
- const button = api.ui.createButton({
- label: 'Greet',
- action: () => {
- api.content.insertHtml(`Hello!`);
- }
- });
-
- // Add the button to the start of the first section within the main toolbar.
- api.ui.getMainToolbarSections()[0]?.addButton(button, 0);
-});
-```
-
\ No newline at end of file
diff --git a/dev/docs/wysiwyg-js-api.md b/dev/docs/wysiwyg-js-api.md
index 7286f943f..394f2f15f 100644
--- a/dev/docs/wysiwyg-js-api.md
+++ b/dev/docs/wysiwyg-js-api.md
@@ -1,9 +1,9 @@
# WYSIWYG JavaScript API
-TODO - Create JS events and add to the js public events doc.
-
**Warning: This API is currently in development and may change without notice.**
+Feedback is very much welcomed via this issue: https://github.com/BookStackApp/BookStack/issues/5937
+
This document covers the JavaScript API for the (newer Lexical-based) WYSIWYG editor.
This API is built and designed to abstract the internals of the editor away
to provide a stable interface for performing common customizations.
@@ -19,8 +19,8 @@ Stable parts of the API may still change where needed, but such changes would be
The API is provided as an object, which itself provides a number of modules
via its properties:
-- `ui` - Provides all actions related to the UI of the editor, like buttons and toolbars.
-- `content` - Provides all actions related to the live user content being edited upon.
+- `ui` - Provides methods related to the UI of the editor, like buttons and toolbars.
+- `content` - Provides methods related to the live user content being edited upon.
Each of these modules, and the relevant types used within, are documented in detail below.
@@ -28,7 +28,7 @@ Each of these modules, and the relevant types used within, are documented in det
## UI Module
-This module provides all actions related to the UI of the editor, like buttons and toolbars.
+This module provides methods related to the UI of the editor, like buttons and toolbars.
### Methods
@@ -55,17 +55,16 @@ const button = api.ui.createButton({
});
```
-### getMainToolbarSections()
+### getMainToolbar()
-Get the sections of the main editor toolbar. These are those which contain groups of buttons
-with overflow control.
-
-The function returns an array of [EditorToolbarSection](#editortoolbarsection) objects.
+Get the main editor toolbar. This is typically the toolbar at the top of the editor.
+The function returns an [EditorApiToolbar](#editorapitoolbar) object, or null if no toolbar is found.
**Example**
```javascript
-const sections = api.ui.getMainToolbarSections();
+const toolbar = api.ui.getMainToolbar();
+const sections = toolbar?.getSections() || [];
if (sections.length > 0) {
sections[0].addButton(button);
}
@@ -83,20 +82,27 @@ This has the following methods:
- `setActive(isActive: boolean): void` - Sets whether the button should be in an active state or not (typically active buttons appear as pressed).
-#### EditorToolbarSection
+#### EditorApiToolbar
+
+Represents a toolbar within the editor. This is a bar typically containing sets of buttons.
+This has the following methods:
+
+- `getSections(): EditorApiToolbarSection[]` - Provides the main [EditorApiToolbarSections](#editorapitoolbarsection) contained within this toolbar.
+
+#### EditorApiToolbarSection
Represents a section of the main editor toolbar, which contains a set of buttons.
This has the following methods:
- `getLabel(): string` - Provides the string label of the section.
- `addButton(button: EditorApiButton, targetIndex: number = -1): void` - Adds a button to the section.
- - By default, this will append the button, although a target index can be provided to insert the button at a specific position.
+ - By default, this will append the button, although a target index can be provided to insert at a specific position.
---
## Content Module
-This module provides all actions related to the live user content being edited within the editor.
+This module provides methods related to the live user content being edited within the editor.
### Methods
diff --git a/resources/js/wysiwyg/api/__tests__/ui.test.ts b/resources/js/wysiwyg/api/__tests__/ui.test.ts
index 6cd80895b..80045bb48 100644
--- a/resources/js/wysiwyg/api/__tests__/ui.test.ts
+++ b/resources/js/wysiwyg/api/__tests__/ui.test.ts
@@ -1,5 +1,5 @@
import {createEditorApiInstance} from "./api-test-utils";
-import {EditorApiButton, EditorApiToolbarSection} from "../ui";
+import {EditorApiButton, EditorApiToolbar, EditorApiToolbarSection} from "../ui";
import {getMainEditorFullToolbar} from "../../ui/defaults/toolbars";
import {EditorContainerUiElement} from "../../ui/framework/core";
import {EditorOverflowContainer} from "../../ui/framework/blocks/overflow-container";
@@ -59,25 +59,39 @@ describe('Editor API: UI Module', () => {
});
- describe('getMainToolbarSections()', () => {
- it('should return an array of toolbar sections', () => {
+ describe('getMainToolbar()', () => {
+ it('should return the main editor toolbar', () => {
const {api, context} = createEditorApiInstance();
context.manager.setToolbar(getMainEditorFullToolbar(context));
- const sections = api.ui.getMainToolbarSections();
- expect(Array.isArray(sections)).toBe(true);
+ const toolbar = api.ui.getMainToolbar();
- expect(sections[0]).toBeInstanceOf(EditorApiToolbarSection);
+ expect(toolbar).toBeInstanceOf(EditorApiToolbar);
});
});
+ describe('EditorApiToolbar', () => {
+ describe('getSections()', () => {
+ it('should return the sections of the toolbar', () => {
+ const {api, context} = createEditorApiInstance();
+ context.manager.setToolbar(testToolbar());
+ const toolbar = api.ui.getMainToolbar();
+
+ const sections = toolbar?.getSections() || [];
+
+ expect(sections.length).toBe(2);
+ expect(sections[0]).toBeInstanceOf(EditorApiToolbarSection);
+ })
+ })
+ })
+
describe('EditorApiToolbarSection', () => {
describe('getLabel()', () => {
it('should return the label of the section', () => {
const {api, context} = createEditorApiInstance();
context.manager.setToolbar(testToolbar());
- const section = api.ui.getMainToolbarSections()[0];
+ const section = api.ui.getMainToolbar()?.getSections()[0] as EditorApiToolbarSection;
expect(section.getLabel()).toBe('section-a');
})
});
@@ -87,7 +101,7 @@ describe('Editor API: UI Module', () => {
const {api, context} = createEditorApiInstance();
const toolbar = testToolbar();
context.manager.setToolbar(toolbar);
- const section = api.ui.getMainToolbarSections()[0];
+ const section = api.ui.getMainToolbar()?.getSections()[0] as EditorApiToolbarSection;
const button = api.ui.createButton({label: 'TestButtonText!', action: () => ''});
section.addButton(button);
diff --git a/resources/js/wysiwyg/api/ui.ts b/resources/js/wysiwyg/api/ui.ts
index cf559269f..c5b822a46 100644
--- a/resources/js/wysiwyg/api/ui.ts
+++ b/resources/js/wysiwyg/api/ui.ts
@@ -1,5 +1,5 @@
import {EditorButton} from "../ui/framework/buttons";
-import {EditorUiContext} from "../ui/framework/core";
+import {EditorContainerUiElement, EditorUiContext} from "../ui/framework/core";
import {EditorOverflowContainer} from "../ui/framework/blocks/overflow-container";
type EditorApiButtonOptions = {
@@ -34,13 +34,26 @@ export class EditorApiButton {
}
}
+export class EditorApiToolbar {
+ readonly #toolbar: EditorContainerUiElement;
+
+ constructor(toolbar: EditorContainerUiElement) {
+ this.#toolbar = toolbar;
+ }
+
+ getSections(): EditorApiToolbarSection[] {
+ const sections = this.#toolbar.getChildren();
+ return sections.filter(section => {
+ return section instanceof EditorOverflowContainer;
+ }).map(section => new EditorApiToolbarSection(section));
+ }
+}
+
export class EditorApiToolbarSection {
readonly #section: EditorOverflowContainer;
- label: string;
constructor(section: EditorOverflowContainer) {
this.#section = section;
- this.label = section.getLabel();
}
getLabel(): string {
@@ -65,15 +78,12 @@ export class EditorApiUiModule {
return new EditorApiButton(options, this.#context);
}
- getMainToolbarSections(): EditorApiToolbarSection[] {
+ getMainToolbar(): EditorApiToolbar|null {
const toolbar = this.#context.manager.getToolbar();
if (!toolbar) {
- return [];
+ return null;
}
- const sections = toolbar.getChildren();
- return sections.filter(section => {
- return section instanceof EditorOverflowContainer;
- }).map(section => new EditorApiToolbarSection(section));
+ return new EditorApiToolbar(toolbar);
}
}
\ No newline at end of file