1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-12-11 19:57:23 +03:00

Lexical API: Reviewed docs, Made toolbar its own UI class

This commit is contained in:
Dan Brown
2025-12-05 14:37:46 +00:00
parent 8890746278
commit ab4b1c8efa
4 changed files with 102 additions and 69 deletions

View File

@@ -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:
<details>
<summary>Show Example</summary>
```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(`<strong>Hello!</strong>`);
}
});
// 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);
}
});
```
</details>
### `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 => {
});
```
</details>
### `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:
<details>
<summary>Show Example</summary>
```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(`<strong>Hello!</strong>`);
}
});
// Add the button to the start of the first section within the main toolbar.
api.ui.getMainToolbarSections()[0]?.addButton(button, 0);
});
```
</details>

View File

@@ -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

View File

@@ -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,17 +59,31 @@ 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(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', () => {
@@ -77,7 +91,7 @@ describe('Editor API: UI Module', () => {
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);

View File

@@ -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);
}
}