]> BookStack Code Mirror - bookstack/blob - dev/docs/javascript-public-events.md
1e95dc8c5043a0de3e7b503ed6d9129dfe419585
[bookstack] / dev / docs / javascript-public-events.md
1 # JavaScript Public Events
2
3 There are a range of available events emitted as part of a public & [supported](#support) API for accessing or extending JavaScript libraries and components used in the system.
4 These are emitted via standard DOM events so can be consumed using standard DOM 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, are possible but will be documented in update notes.
19 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 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 `<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 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 IFrame 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.contentDocument.body.style.color = 'red';
74 });
75 ```
76
77 ### `editor-drawio::configure`
78
79 This event is called as the embedded diagrams.net drawing editor loads, to allow configuration of the diagrams.net interface.
80 See [this diagrams.net page](https://www.diagrams.net/doc/faq/configure-diagram-editor) for details on the available options for the configure event.
81
82 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.
83
84 #### Event Data
85
86 - `config` - The configuration object that will be passed to diagrams.net.
87   - This will likely be empty by default, but modify this object in-place as needed with your desired options.
88
89 ##### Example
90
91 ```javascript
92 // Set only the "general" and "android" libraries to show by default
93 window.addEventListener('editor-drawio::configure', event => {
94     const config = event.detail.config;
95     config.enabledLibraries = ["general", "android"];
96 });
97 ```
98
99 ### `editor-tinymce::pre-init`
100
101 This event is called before the TinyMCE editor, used as the BookStack WYSIWYG page editor, is initialised.
102
103 #### Event Data
104
105 - `config` - Object containing the configuration that's going to be passed to [tinymce.init](https://www.tiny.cloud/docs/api/tinymce/root_tinymce/#init).
106
107 ##### Example
108
109 ```javascript
110 // Removed "bold" from the editor toolbar
111 window.addEventListener('editor-tinymce::pre-init', event => {
112     const tinyConfig = event.detail.config;
113     tinyConfig.toolbar = tinyConfig.toolbar.replace('bold ', '');
114 });
115 ```
116
117 ### `editor-tinymce::setup`
118
119 This event is called during the `setup` lifecycle stage of the TinyMCE editor used as the BookStack WYSIWYG editor. This is after configuration, but before the editor is fully loaded and ready to use. 
120
121 ##### Event Data
122
123 - `editor` - The [tinymce.Editor](https://www.tiny.cloud/docs/api/tinymce/tinymce.editor/) instance used for the WYSIWYG editor.
124
125 ##### Example
126
127 ```javascript
128 // Replaces the editor content with redacted message 3 seconds after load.
129 window.addEventListener('editor-tinymce::setup', event => {
130     const editor = event.detail.editor;
131     setTimeout(() => {
132         editor.setContent('REDACTED!');
133     }, 3000);
134 });
135 ```
136
137 ### `library-cm6::configure-theme`
138
139 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.
140
141 #### Event Data
142
143 - `darkModeActive` - A boolean to indicate if the current view/page is being loaded with dark mode active.
144 - `registerViewTheme(builder)` - A method that can be called to register a new view (CodeMirror UI) theme.
145   - `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.
146 - `registerHighlightStyle(builder)` - A method that can be called to register a new HighlightStyle (code highlighting) theme.
147   - `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.
148
149 ##### Example
150
151 The below shows registering a custom "Solarized dark" editor and syntax theme:
152
153 <details>
154 <summary>Show Example</summary>
155
156 ```javascript
157 // Theme data taken from:
158 // https://github.com/craftzdog/cm6-themes/blob/main/packages/solarized-dark/src/index.ts
159 // (MIT License) - Copyright (C) 2022 by Takuya Matsuyama and others
160 const base00 = '#002b36',
161     base01 = '#073642',
162     base02 = '#586e75',
163     base03 = '#657b83',
164     base04 = '#839496',
165     base05 = '#93a1a1',
166     base06 = '#eee8d5',
167     base07 = '#fdf6e3',
168     base_red = '#dc322f',
169     base_orange = '#cb4b16',
170     base_yellow = '#b58900',
171     base_green = '#859900',
172     base_cyan = '#2aa198',
173     base_blue = '#268bd2',
174     base_violet = '#6c71c4',
175     base_magenta = '#d33682'
176
177 const invalid = '#d30102',
178     stone = base04,
179     darkBackground = '#00252f',
180     highlightBackground = '#173541',
181     background = base00,
182     tooltipBackground = base01,
183     selection = '#173541',
184     cursor = base04
185
186 function viewThemeBuilder() {
187     return {
188       '&':{color:base05,backgroundColor:background},
189       '.cm-content':{caretColor:cursor},
190       '.cm-cursor, .cm-dropCursor':{borderLeftColor:cursor},
191       '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':{backgroundColor:selection},
192       '.cm-panels':{backgroundColor:darkBackground,color:base03},
193       '.cm-panels.cm-panels-top':{borderBottom:'2px solid black'},
194       '.cm-panels.cm-panels-bottom':{borderTop:'2px solid black'},
195       '.cm-searchMatch':{backgroundColor:'#72a1ff59',outline:'1px solid #457dff'},
196       '.cm-searchMatch.cm-searchMatch-selected':{backgroundColor:'#6199ff2f'},
197       '.cm-activeLine':{backgroundColor:highlightBackground},
198       '.cm-selectionMatch':{backgroundColor:'#aafe661a'},
199       '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket':{outline:`1px solid ${base06}`},
200       '.cm-gutters':{backgroundColor:darkBackground,color:stone,border:'none'},
201       '.cm-activeLineGutter':{backgroundColor:highlightBackground},
202       '.cm-foldPlaceholder':{backgroundColor:'transparent',border:'none',color:'#ddd'},
203       '.cm-tooltip':{border:'none',backgroundColor:tooltipBackground},
204       '.cm-tooltip .cm-tooltip-arrow:before':{borderTopColor:'transparent',borderBottomColor:'transparent'},
205       '.cm-tooltip .cm-tooltip-arrow:after':{borderTopColor:tooltipBackground,borderBottomColor:tooltipBackground},
206       '.cm-tooltip-autocomplete':{
207         '& > ul > li[aria-selected]':{backgroundColor:highlightBackground,color:base03}
208       }
209     };
210 }
211
212 function highlightStyleBuilder(t) {
213     return [{tag:t.keyword,color:base_green},
214       {tag:[t.name,t.deleted,t.character,t.propertyName,t.macroName],color:base_cyan},
215       {tag:[t.variableName],color:base05},
216       {tag:[t.function(t.variableName)],color:base_blue},
217       {tag:[t.labelName],color:base_magenta},
218       {tag:[t.color,t.constant(t.name),t.standard(t.name)],color:base_yellow},
219       {tag:[t.definition(t.name),t.separator],color:base_cyan},
220       {tag:[t.brace],color:base_magenta},
221       {tag:[t.annotation],color:invalid},
222       {tag:[t.number,t.changed,t.annotation,t.modifier,t.self,t.namespace],color:base_magenta},
223       {tag:[t.typeName,t.className],color:base_orange},
224       {tag:[t.operator,t.operatorKeyword],color:base_violet},
225       {tag:[t.tagName],color:base_blue},
226       {tag:[t.squareBracket],color:base_red},
227       {tag:[t.angleBracket],color:base02},
228       {tag:[t.attributeName],color:base05},
229       {tag:[t.regexp],color:invalid},
230       {tag:[t.quote],color:base_green},
231       {tag:[t.string],color:base_yellow},
232       {tag:t.link,color:base_cyan,textDecoration:'underline',textUnderlinePosition:'under'},
233       {tag:[t.url,t.escape,t.special(t.string)],color:base_yellow},
234       {tag:[t.meta],color:base_red},
235       {tag:[t.comment],color:base02,fontStyle:'italic'},
236       {tag:t.strong,fontWeight:'bold',color:base06},
237       {tag:t.emphasis,fontStyle:'italic',color:base_green},
238       {tag:t.strikethrough,textDecoration:'line-through'},
239       {tag:t.heading,fontWeight:'bold',color:base_yellow},
240       {tag:t.heading1,fontWeight:'bold',color:base07},
241       {tag:[t.heading2,t.heading3,t.heading4],fontWeight:'bold',color:base06},
242       {tag:[t.heading5,t.heading6],color:base06},
243       {tag:[t.atom,t.bool,t.special(t.variableName)],color:base_magenta},
244       {tag:[t.processingInstruction,t.inserted,t.contentSeparator],color:base_red},
245       {tag:[t.contentSeparator],color:base_yellow},
246       {tag:t.invalid,color:base02,borderBottom:`1px dotted ${base_red}`}];
247 }
248
249 window.addEventListener('library-cm6::configure-theme', event => {
250     const detail = event.detail;
251     detail.registerViewTheme(viewThemeBuilder);
252     detail.registerHighlightStyle(highlightStyleBuilder);
253 });
254 ```
255 </details>