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:
@@ -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`
|
### `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.
|
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>
|
</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>
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# WYSIWYG JavaScript API
|
# 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.**
|
**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 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
|
This API is built and designed to abstract the internals of the editor away
|
||||||
to provide a stable interface for performing common customizations.
|
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
|
The API is provided as an object, which itself provides a number of modules
|
||||||
via its properties:
|
via its properties:
|
||||||
|
|
||||||
- `ui` - Provides all actions related to the UI of the editor, like buttons and toolbars.
|
- `ui` - Provides methods related to the UI of the editor, like buttons and toolbars.
|
||||||
- `content` - Provides all actions related to the live user content being edited upon.
|
- `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.
|
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
|
## 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
|
### 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
|
Get the main editor toolbar. This is typically the toolbar at the top of the editor.
|
||||||
with overflow control.
|
The function returns an [EditorApiToolbar](#editorapitoolbar) object, or null if no toolbar is found.
|
||||||
|
|
||||||
The function returns an array of [EditorToolbarSection](#editortoolbarsection) objects.
|
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const sections = api.ui.getMainToolbarSections();
|
const toolbar = api.ui.getMainToolbar();
|
||||||
|
const sections = toolbar?.getSections() || [];
|
||||||
if (sections.length > 0) {
|
if (sections.length > 0) {
|
||||||
sections[0].addButton(button);
|
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).
|
- `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.
|
Represents a section of the main editor toolbar, which contains a set of buttons.
|
||||||
This has the following methods:
|
This has the following methods:
|
||||||
|
|
||||||
- `getLabel(): string` - Provides the string label of the section.
|
- `getLabel(): string` - Provides the string label of the section.
|
||||||
- `addButton(button: EditorApiButton, targetIndex: number = -1): void` - Adds a button to 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
|
## 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
|
### Methods
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {createEditorApiInstance} from "./api-test-utils";
|
import {createEditorApiInstance} from "./api-test-utils";
|
||||||
import {EditorApiButton, EditorApiToolbarSection} from "../ui";
|
import {EditorApiButton, EditorApiToolbar, EditorApiToolbarSection} from "../ui";
|
||||||
import {getMainEditorFullToolbar} from "../../ui/defaults/toolbars";
|
import {getMainEditorFullToolbar} from "../../ui/defaults/toolbars";
|
||||||
import {EditorContainerUiElement} from "../../ui/framework/core";
|
import {EditorContainerUiElement} from "../../ui/framework/core";
|
||||||
import {EditorOverflowContainer} from "../../ui/framework/blocks/overflow-container";
|
import {EditorOverflowContainer} from "../../ui/framework/blocks/overflow-container";
|
||||||
@@ -59,25 +59,39 @@ describe('Editor API: UI Module', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMainToolbarSections()', () => {
|
describe('getMainToolbar()', () => {
|
||||||
it('should return an array of toolbar sections', () => {
|
it('should return the main editor toolbar', () => {
|
||||||
const {api, context} = createEditorApiInstance();
|
const {api, context} = createEditorApiInstance();
|
||||||
context.manager.setToolbar(getMainEditorFullToolbar(context));
|
context.manager.setToolbar(getMainEditorFullToolbar(context));
|
||||||
|
|
||||||
const sections = api.ui.getMainToolbarSections();
|
const toolbar = api.ui.getMainToolbar();
|
||||||
expect(Array.isArray(sections)).toBe(true);
|
|
||||||
|
|
||||||
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('EditorApiToolbarSection', () => {
|
||||||
|
|
||||||
describe('getLabel()', () => {
|
describe('getLabel()', () => {
|
||||||
it('should return the label of the section', () => {
|
it('should return the label of the section', () => {
|
||||||
const {api, context} = createEditorApiInstance();
|
const {api, context} = createEditorApiInstance();
|
||||||
context.manager.setToolbar(testToolbar());
|
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');
|
expect(section.getLabel()).toBe('section-a');
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -87,7 +101,7 @@ describe('Editor API: UI Module', () => {
|
|||||||
const {api, context} = createEditorApiInstance();
|
const {api, context} = createEditorApiInstance();
|
||||||
const toolbar = testToolbar();
|
const toolbar = testToolbar();
|
||||||
context.manager.setToolbar(toolbar);
|
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: () => ''});
|
const button = api.ui.createButton({label: 'TestButtonText!', action: () => ''});
|
||||||
section.addButton(button);
|
section.addButton(button);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {EditorButton} from "../ui/framework/buttons";
|
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";
|
import {EditorOverflowContainer} from "../ui/framework/blocks/overflow-container";
|
||||||
|
|
||||||
type EditorApiButtonOptions = {
|
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 {
|
export class EditorApiToolbarSection {
|
||||||
readonly #section: EditorOverflowContainer;
|
readonly #section: EditorOverflowContainer;
|
||||||
label: string;
|
|
||||||
|
|
||||||
constructor(section: EditorOverflowContainer) {
|
constructor(section: EditorOverflowContainer) {
|
||||||
this.#section = section;
|
this.#section = section;
|
||||||
this.label = section.getLabel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLabel(): string {
|
getLabel(): string {
|
||||||
@@ -65,15 +78,12 @@ export class EditorApiUiModule {
|
|||||||
return new EditorApiButton(options, this.#context);
|
return new EditorApiButton(options, this.#context);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMainToolbarSections(): EditorApiToolbarSection[] {
|
getMainToolbar(): EditorApiToolbar|null {
|
||||||
const toolbar = this.#context.manager.getToolbar();
|
const toolbar = this.#context.manager.getToolbar();
|
||||||
if (!toolbar) {
|
if (!toolbar) {
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sections = toolbar.getChildren();
|
return new EditorApiToolbar(toolbar);
|
||||||
return sections.filter(section => {
|
|
||||||
return section instanceof EditorOverflowContainer;
|
|
||||||
}).map(section => new EditorApiToolbarSection(section));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user