|
| 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> |
0 commit comments