Skip to content

Commit c74f7cc

Browse files
committed
Documented public JS events used
Related to BookStackApp#4179
1 parent 9f467f4 commit c74f7cc

3 files changed

Lines changed: 266 additions & 1 deletion

File tree

dev/docs/javascript-code.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ There are various global helper libraries in BookStack which can be accessed via
115115

116116
```js
117117
// HTTP service
118+
// Relative URLs will be resolved against the instance BASE_URL
118119
window.$http.get(url, params);
119120
window.$http.post(url, data);
120121
window.$http.put(url, data);
@@ -153,4 +154,10 @@ window.$components.get(name);
153154
// Get the first active component of the given name that's been
154155
// created on the given element.
155156
window.$components.firstOnElement(element, name);
156-
```
157+
```
158+
159+
## Public Events
160+
161+
There are a range of available events that are emitted as part of a public & supported API for accessing or extending JavaScript libraries and components used in the system.
162+
163+
Details on these events can be found in the [JavaScript Public Events file](javascript-public-events.md).
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# JavaScript Public Events
2+
3+
There are a range of available events that are emitted as part of a public & supported API for accessing or extending JavaScript libraries and components used in the system.
4+
These are emitted via standard DOM events so can be listened to using standard APIs like so:
5+
6+
```javascript
7+
window.addEventListener('event-name', event => {
8+
const eventData = event.detail;
9+
});
10+
```
11+
12+
Such events are typically emitted from a DOM element relevant to event, which then bubbles up.
13+
For most use-cases you can probably just listen on the `window` as shown above.
14+
15+
## Support
16+
17+
This event system, and the events emitted, are considered semi-supported.
18+
Breaking changes of the event API, event names or event properties, will be documented in update notes but may change.
19+
Upon that, the detail provided within the events, and the libraries made accessible, are not considered supported nor stable, and changes to these won't be clearly documented within changelogs.
20+
21+
## Event Naming Scheme
22+
23+
Events are typically named in the following format:
24+
25+
```text
26+
<context>::<action/lifecycle>
27+
28+
# Examples:
29+
editor-tinymce::setup
30+
library-cm6::configure-theme
31+
```
32+
33+
If the event is generic in use but specific to a library, the name `<context>` will start with `library-` followed by the library name. Otherwise `<context>` may reflect the UI context/component.
34+
35+
The `<action/lifecycle>` reflects the lifecycle stage of the context, or a specific action to perform if the event is very specific to a certain use-case.
36+
37+
## Event Listing
38+
39+
### `editor-markdown-cm6::pre-init`
40+
41+
This event is called before the markdown input editor CodeMirror instance is created or loaded.
42+
43+
#### Event Data
44+
45+
- `editorViewConfig` - An [EditorViewConfig](https://codemirror.net/docs/ref/#view.EditorViewConfig) object that will eventially be passed when creating the CodeMirror EditorView instance.
46+
47+
##### Example
48+
49+
```javascript
50+
// Always load the editor with specific pre-defined content if empty
51+
window.addEventListener('editor-markdown-cm6::pre-init', event => {
52+
const config = event.detail.editorViewConfig;
53+
config.doc = config.doc || "Start this page with a nice story";
54+
});
55+
```
56+
57+
### `editor-markdown::setup`
58+
59+
This event is called when the markdown editor loads, post configuration but before the editor is ready to use.
60+
61+
#### Event Data
62+
63+
- `markdownIt` - A references to the [MarkdownIt](https://markdown-it.github.io/markdown-it/#MarkdownIt) instance used to render markdown to HTML (Just for the preview).
64+
- `displayEl` - The DOM Element that wraps the HTML preview display.
65+
- `cmEditorView` - The CodeMirror [EditorView](https://codemirror.net/docs/ref/#view.EditorView) instance used for the markdown input editor.
66+
67+
##### Example
68+
69+
```javascript
70+
// Set all text in the display to be red by default.
71+
window.addEventListener('editor-markdown::setup', event => {
72+
const display = event.detail.displayEl;
73+
display.style.color = 'red';
74+
});
75+
```
76+
77+
### `editor-drawio::configure`
78+
79+
This event is called as the embedded diagrams.net drawing editor loads, as a means to allow configuration of the diagrams.net interface.
80+
81+
See [this diagrams.net page](https://www.diagrams.net/doc/faq/configure-diagram-editor) for details on the available options for the configure event.
82+
83+
If using a custom diagrams.net instance, via the `DRAWIO` option, you will need to ensure your DRAWIO option URL has the `configure=1` query parameter.
84+
85+
#### Event Data
86+
87+
- `config` - The configuration object that will be passed to diagrams.net.
88+
- This will likely be empty by default, but modify this object in-place as needed with your desired options.
89+
90+
##### Example
91+
92+
```javascript
93+
// Set only the "general" and "android" libraries to show by default
94+
window.addEventListener('editor-drawio::configure', event => {
95+
const config = event.detail.config;
96+
config.defaultLibraries = "general;android";
97+
});
98+
```
99+
100+
### `editor-tinymce::pre-init`
101+
102+
This event is called before the TinyMCE editor, used as the BookStack WYSIWYG page editor, is initialised.
103+
104+
#### Event Data
105+
106+
- `config` - Object containing the configuration that's going to be passed to [tinymce.init](https://www.tiny.cloud/docs/api/tinymce/root_tinymce/#init).
107+
108+
##### Example
109+
110+
```javascript
111+
// Removed "bold" from the editor toolbar
112+
window.addEventListener('editor-tinymce::pre-init', event => {
113+
const tinyConfig = event.detail.config;
114+
tinyConfig.toolbar = tinyConfig.toolbar.replace('bold ', '');
115+
});
116+
```
117+
118+
### `editor-tinymce::setup`
119+
120+
This event is called during the `setup` lifecycle stage of the TinyMCE editor used as the BookStack WYSIWYG editor. This is post configuration, but before the editor is typically visible.
121+
122+
##### Event Data
123+
124+
- `editor` - The [tinymce.Editor](https://www.tiny.cloud/docs/api/tinymce/tinymce.editor/) instance used for the WYSIWYG editor.
125+
126+
##### Example
127+
128+
```javascript
129+
// Replaces the editor content with redacted message 3 seconds after load.
130+
window.addEventListener('editor-tinymce::setup', event => {
131+
const editor = event.detail.editor;
132+
setTimeout(() => {
133+
editor.setContent('REDACTED!');
134+
}, 3000);
135+
});
136+
```
137+
138+
### `library-cm6::configure-theme`
139+
140+
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.
141+
142+
#### Event Data
143+
144+
- `darkModeActive` - A boolean to indicate if the current view/page is being loaded with dark mode active.
145+
- `registerViewTheme(builder)` - A method that can be called to register a new view (CodeMirror UI) theme.
146+
- `builder` - A function that will return an object that will be passed into the CodeMirror [EditorView.theme()](https://codemirror.net/docs/ref/#view.EditorView^theme) function as a StyleSpec.
147+
- `registerHighlightStyle(builder)` - A method that can be called to register a new HighlightStyle (code highlighting) theme.
148+
- `builder` - A function, that receives a reference to [Tag.tags](https://lezer.codemirror.net/docs/ref/#highlight.tags) and returns an array of [TagStyle](https://codemirror.net/docs/ref/#language.TagStyle) objects.
149+
150+
##### Example
151+
152+
The below shows registering a custom "Solarized dark" editor and syntax theme:
153+
154+
<details>
155+
<summary>Show Example</summary>
156+
157+
```javascript
158+
// Theme data taken from:
159+
// https://github.com/craftzdog/cm6-themes/blob/main/packages/solarized-dark/src/index.ts
160+
// (MIT License) - Copyright (C) 2022 by Takuya Matsuyama and others
161+
const base00 = '#002b36',
162+
base01 = '#073642',
163+
base02 = '#586e75',
164+
base03 = '#657b83',
165+
base04 = '#839496',
166+
base05 = '#93a1a1',
167+
base06 = '#eee8d5',
168+
base07 = '#fdf6e3',
169+
base_red = '#dc322f',
170+
base_orange = '#cb4b16',
171+
base_yellow = '#b58900',
172+
base_green = '#859900',
173+
base_cyan = '#2aa198',
174+
base_blue = '#268bd2',
175+
base_violet = '#6c71c4',
176+
base_magenta = '#d33682'
177+
178+
const invalid = '#d30102',
179+
stone = base04,
180+
darkBackground = '#00252f',
181+
highlightBackground = '#173541',
182+
background = base00,
183+
tooltipBackground = base01,
184+
selection = '#173541',
185+
cursor = base04
186+
187+
function viewThemeBuilder() {
188+
return {
189+
'&':{color:base05,backgroundColor:background},
190+
'.cm-content':{caretColor:cursor},
191+
'.cm-cursor, .cm-dropCursor':{borderLeftColor:cursor},
192+
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':{backgroundColor:selection},
193+
'.cm-panels':{backgroundColor:darkBackground,color:base03},
194+
'.cm-panels.cm-panels-top':{borderBottom:'2px solid black'},
195+
'.cm-panels.cm-panels-bottom':{borderTop:'2px solid black'},
196+
'.cm-searchMatch':{backgroundColor:'#72a1ff59',outline:'1px solid #457dff'},
197+
'.cm-searchMatch.cm-searchMatch-selected':{backgroundColor:'#6199ff2f'},
198+
'.cm-activeLine':{backgroundColor:highlightBackground},
199+
'.cm-selectionMatch':{backgroundColor:'#aafe661a'},
200+
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket':{outline:`1px solid ${base06}`},
201+
'.cm-gutters':{backgroundColor:darkBackground,color:stone,border:'none'},
202+
'.cm-activeLineGutter':{backgroundColor:highlightBackground},
203+
'.cm-foldPlaceholder':{backgroundColor:'transparent',border:'none',color:'#ddd'},
204+
'.cm-tooltip':{border:'none',backgroundColor:tooltipBackground},
205+
'.cm-tooltip .cm-tooltip-arrow:before':{borderTopColor:'transparent',borderBottomColor:'transparent'},
206+
'.cm-tooltip .cm-tooltip-arrow:after':{borderTopColor:tooltipBackground,borderBottomColor:tooltipBackground},
207+
'.cm-tooltip-autocomplete':{
208+
'& > ul > li[aria-selected]':{backgroundColor:highlightBackground,color:base03}
209+
}
210+
};
211+
}
212+
213+
function highlightStyleBuilder(t) {
214+
return [{tag:t.keyword,color:base_green},
215+
{tag:[t.name,t.deleted,t.character,t.propertyName,t.macroName],color:base_cyan},
216+
{tag:[t.variableName],color:base05},
217+
{tag:[t.function(t.variableName)],color:base_blue},
218+
{tag:[t.labelName],color:base_magenta},
219+
{tag:[t.color,t.constant(t.name),t.standard(t.name)],color:base_yellow},
220+
{tag:[t.definition(t.name),t.separator],color:base_cyan},
221+
{tag:[t.brace],color:base_magenta},
222+
{tag:[t.annotation],color:invalid},
223+
{tag:[t.number,t.changed,t.annotation,t.modifier,t.self,t.namespace],color:base_magenta},
224+
{tag:[t.typeName,t.className],color:base_orange},
225+
{tag:[t.operator,t.operatorKeyword],color:base_violet},
226+
{tag:[t.tagName],color:base_blue},
227+
{tag:[t.squareBracket],color:base_red},
228+
{tag:[t.angleBracket],color:base02},
229+
{tag:[t.attributeName],color:base05},
230+
{tag:[t.regexp],color:invalid},
231+
{tag:[t.quote],color:base_green},
232+
{tag:[t.string],color:base_yellow},
233+
{tag:t.link,color:base_cyan,textDecoration:'underline',textUnderlinePosition:'under'},
234+
{tag:[t.url,t.escape,t.special(t.string)],color:base_yellow},
235+
{tag:[t.meta],color:base_red},
236+
{tag:[t.comment],color:base02,fontStyle:'italic'},
237+
{tag:t.strong,fontWeight:'bold',color:base06},
238+
{tag:t.emphasis,fontStyle:'italic',color:base_green},
239+
{tag:t.strikethrough,textDecoration:'line-through'},
240+
{tag:t.heading,fontWeight:'bold',color:base_yellow},
241+
{tag:t.heading1,fontWeight:'bold',color:base07},
242+
{tag:[t.heading2,t.heading3,t.heading4],fontWeight:'bold',color:base06},
243+
{tag:[t.heading5,t.heading6],color:base06},
244+
{tag:[t.atom,t.bool,t.special(t.variableName)],color:base_magenta},
245+
{tag:[t.processingInstruction,t.inserted,t.contentSeparator],color:base_red},
246+
{tag:[t.contentSeparator],color:base_yellow},
247+
{tag:t.invalid,color:base02,borderBottom:`1px dotted ${base_red}`}];
248+
}
249+
250+
window.addEventListener('library-cm6::configure-theme', event => {
251+
const detail = event.detail;
252+
detail.registerViewTheme(viewThemeBuilder);
253+
detail.registerHighlightStyle(highlightStyleBuilder);
254+
});
255+
```
256+
</details>

resources/js/services/drawio.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Docs: https://www.diagrams.net/doc/faq/embed-mode
2+
13
let iFrame = null;
24
let lastApprovedOrigin;
35
let onInit; let

0 commit comments

Comments
 (0)