From c74f7cc62854516f812573333b8ae0aad5970f78 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 20 Apr 2023 16:25:48 +0100 Subject: [PATCH] Documented public JS events used Related to #4179 --- dev/docs/javascript-code.md | 9 +- dev/docs/javascript-public-events.md | 256 +++++++++++++++++++++++++++ resources/js/services/drawio.js | 2 + 3 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 dev/docs/javascript-public-events.md diff --git a/dev/docs/javascript-code.md b/dev/docs/javascript-code.md index a1092ce92..f3d4f8fa8 100644 --- a/dev/docs/javascript-code.md +++ b/dev/docs/javascript-code.md @@ -115,6 +115,7 @@ There are various global helper libraries in BookStack which can be accessed via ```js // HTTP service +// Relative URLs will be resolved against the instance BASE_URL window.$http.get(url, params); window.$http.post(url, data); window.$http.put(url, data); @@ -153,4 +154,10 @@ window.$components.get(name); // Get the first active component of the given name that's been // created on the given element. window.$components.firstOnElement(element, name); -``` \ No newline at end of file +``` + +## Public Events + +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. + +Details on these events can be found in the [JavaScript Public Events file](javascript-public-events.md). \ No newline at end of file diff --git a/dev/docs/javascript-public-events.md b/dev/docs/javascript-public-events.md new file mode 100644 index 000000000..6bc11dbcd --- /dev/null +++ b/dev/docs/javascript-public-events.md @@ -0,0 +1,256 @@ +# JavaScript Public Events + +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. +These are emitted via standard DOM events so can be listened to using standard APIs like so: + +```javascript +window.addEventListener('event-name', event => { + const eventData = event.detail; +}); +``` + +Such events are typically emitted from a DOM element relevant to event, which then bubbles up. +For most use-cases you can probably just listen on the `window` as shown above. + +## Support + +This event system, and the events emitted, are considered semi-supported. +Breaking changes of the event API, event names or event properties, will be documented in update notes but may change. +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. + +## Event Naming Scheme + +Events are typically named in the following format: + +```text +:: + +# Examples: +editor-tinymce::setup +library-cm6::configure-theme +``` + +If the event is generic in use but specific to a library, the name `` will start with `library-` followed by the library name. Otherwise `` may reflect the UI context/component. + +The `` reflects the lifecycle stage of the context, or a specific action to perform if the event is very specific to a certain use-case. + +## Event Listing + +### `editor-markdown-cm6::pre-init` + +This event is called before the markdown input editor CodeMirror instance is created or loaded. + +#### Event Data + +- `editorViewConfig` - An [EditorViewConfig](https://codemirror.net/docs/ref/#view.EditorViewConfig) object that will eventially be passed when creating the CodeMirror EditorView instance. + +##### Example + +```javascript +// Always load the editor with specific pre-defined content if empty +window.addEventListener('editor-markdown-cm6::pre-init', event => { + const config = event.detail.editorViewConfig; + config.doc = config.doc || "Start this page with a nice story"; +}); +``` + +### `editor-markdown::setup` + +This event is called when the markdown editor loads, post configuration but before the editor is ready to use. + +#### Event Data + +- `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). +- `displayEl` - The DOM Element that wraps the HTML preview display. +- `cmEditorView` - The CodeMirror [EditorView](https://codemirror.net/docs/ref/#view.EditorView) instance used for the markdown input editor. + +##### Example + +```javascript +// Set all text in the display to be red by default. +window.addEventListener('editor-markdown::setup', event => { + const display = event.detail.displayEl; + display.style.color = 'red'; +}); +``` + +### `editor-drawio::configure` + +This event is called as the embedded diagrams.net drawing editor loads, as a means to allow configuration of the diagrams.net interface. + +See [this diagrams.net page](https://www.diagrams.net/doc/faq/configure-diagram-editor) for details on the available options for the configure event. + +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. + +#### Event Data + +- `config` - The configuration object that will be passed to diagrams.net. + - This will likely be empty by default, but modify this object in-place as needed with your desired options. + +##### Example + +```javascript +// Set only the "general" and "android" libraries to show by default +window.addEventListener('editor-drawio::configure', event => { + const config = event.detail.config; + config.defaultLibraries = "general;android"; +}); +``` + +### `editor-tinymce::pre-init` + +This event is called before the TinyMCE editor, used as the BookStack WYSIWYG page editor, is initialised. + +#### Event Data + +- `config` - Object containing the configuration that's going to be passed to [tinymce.init](https://www.tiny.cloud/docs/api/tinymce/root_tinymce/#init). + +##### Example + +```javascript +// Removed "bold" from the editor toolbar +window.addEventListener('editor-tinymce::pre-init', event => { + const tinyConfig = event.detail.config; + tinyConfig.toolbar = tinyConfig.toolbar.replace('bold ', ''); +}); +``` + +### `editor-tinymce::setup` + +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. + +##### Event Data + +- `editor` - The [tinymce.Editor](https://www.tiny.cloud/docs/api/tinymce/tinymce.editor/) instance used for the WYSIWYG editor. + +##### Example + +```javascript +// Replaces the editor content with redacted message 3 seconds after load. +window.addEventListener('editor-tinymce::setup', event => { + const editor = event.detail.editor; + setTimeout(() => { + editor.setContent('REDACTED!'); + }, 3000); +}); +``` + +### `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. + +#### Event Data + +- `darkModeActive` - A boolean to indicate if the current view/page is being loaded with dark mode active. +- `registerViewTheme(builder)` - A method that can be called to register a new view (CodeMirror UI) theme. + - `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. +- `registerHighlightStyle(builder)` - A method that can be called to register a new HighlightStyle (code highlighting) theme. + - `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. + +##### Example + +The below shows registering a custom "Solarized dark" editor and syntax theme: + +
+Show Example + +```javascript +// Theme data taken from: +// https://github.com/craftzdog/cm6-themes/blob/main/packages/solarized-dark/src/index.ts +// (MIT License) - Copyright (C) 2022 by Takuya Matsuyama and others +const base00 = '#002b36', + base01 = '#073642', + base02 = '#586e75', + base03 = '#657b83', + base04 = '#839496', + base05 = '#93a1a1', + base06 = '#eee8d5', + base07 = '#fdf6e3', + base_red = '#dc322f', + base_orange = '#cb4b16', + base_yellow = '#b58900', + base_green = '#859900', + base_cyan = '#2aa198', + base_blue = '#268bd2', + base_violet = '#6c71c4', + base_magenta = '#d33682' + +const invalid = '#d30102', + stone = base04, + darkBackground = '#00252f', + highlightBackground = '#173541', + background = base00, + tooltipBackground = base01, + selection = '#173541', + cursor = base04 + +function viewThemeBuilder() { + return { + '&':{color:base05,backgroundColor:background}, + '.cm-content':{caretColor:cursor}, + '.cm-cursor, .cm-dropCursor':{borderLeftColor:cursor}, + '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':{backgroundColor:selection}, + '.cm-panels':{backgroundColor:darkBackground,color:base03}, + '.cm-panels.cm-panels-top':{borderBottom:'2px solid black'}, + '.cm-panels.cm-panels-bottom':{borderTop:'2px solid black'}, + '.cm-searchMatch':{backgroundColor:'#72a1ff59',outline:'1px solid #457dff'}, + '.cm-searchMatch.cm-searchMatch-selected':{backgroundColor:'#6199ff2f'}, + '.cm-activeLine':{backgroundColor:highlightBackground}, + '.cm-selectionMatch':{backgroundColor:'#aafe661a'}, + '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket':{outline:`1px solid ${base06}`}, + '.cm-gutters':{backgroundColor:darkBackground,color:stone,border:'none'}, + '.cm-activeLineGutter':{backgroundColor:highlightBackground}, + '.cm-foldPlaceholder':{backgroundColor:'transparent',border:'none',color:'#ddd'}, + '.cm-tooltip':{border:'none',backgroundColor:tooltipBackground}, + '.cm-tooltip .cm-tooltip-arrow:before':{borderTopColor:'transparent',borderBottomColor:'transparent'}, + '.cm-tooltip .cm-tooltip-arrow:after':{borderTopColor:tooltipBackground,borderBottomColor:tooltipBackground}, + '.cm-tooltip-autocomplete':{ + '& > ul > li[aria-selected]':{backgroundColor:highlightBackground,color:base03} + } + }; +} + +function highlightStyleBuilder(t) { + return [{tag:t.keyword,color:base_green}, + {tag:[t.name,t.deleted,t.character,t.propertyName,t.macroName],color:base_cyan}, + {tag:[t.variableName],color:base05}, + {tag:[t.function(t.variableName)],color:base_blue}, + {tag:[t.labelName],color:base_magenta}, + {tag:[t.color,t.constant(t.name),t.standard(t.name)],color:base_yellow}, + {tag:[t.definition(t.name),t.separator],color:base_cyan}, + {tag:[t.brace],color:base_magenta}, + {tag:[t.annotation],color:invalid}, + {tag:[t.number,t.changed,t.annotation,t.modifier,t.self,t.namespace],color:base_magenta}, + {tag:[t.typeName,t.className],color:base_orange}, + {tag:[t.operator,t.operatorKeyword],color:base_violet}, + {tag:[t.tagName],color:base_blue}, + {tag:[t.squareBracket],color:base_red}, + {tag:[t.angleBracket],color:base02}, + {tag:[t.attributeName],color:base05}, + {tag:[t.regexp],color:invalid}, + {tag:[t.quote],color:base_green}, + {tag:[t.string],color:base_yellow}, + {tag:t.link,color:base_cyan,textDecoration:'underline',textUnderlinePosition:'under'}, + {tag:[t.url,t.escape,t.special(t.string)],color:base_yellow}, + {tag:[t.meta],color:base_red}, + {tag:[t.comment],color:base02,fontStyle:'italic'}, + {tag:t.strong,fontWeight:'bold',color:base06}, + {tag:t.emphasis,fontStyle:'italic',color:base_green}, + {tag:t.strikethrough,textDecoration:'line-through'}, + {tag:t.heading,fontWeight:'bold',color:base_yellow}, + {tag:t.heading1,fontWeight:'bold',color:base07}, + {tag:[t.heading2,t.heading3,t.heading4],fontWeight:'bold',color:base06}, + {tag:[t.heading5,t.heading6],color:base06}, + {tag:[t.atom,t.bool,t.special(t.variableName)],color:base_magenta}, + {tag:[t.processingInstruction,t.inserted,t.contentSeparator],color:base_red}, + {tag:[t.contentSeparator],color:base_yellow}, + {tag:t.invalid,color:base02,borderBottom:`1px dotted ${base_red}`}]; +} + +window.addEventListener('library-cm6::configure-theme', event => { + const detail = event.detail; + detail.registerViewTheme(viewThemeBuilder); + detail.registerHighlightStyle(highlightStyleBuilder); +}); +``` +
\ No newline at end of file diff --git a/resources/js/services/drawio.js b/resources/js/services/drawio.js index c773a780d..efc071d3e 100644 --- a/resources/js/services/drawio.js +++ b/resources/js/services/drawio.js @@ -1,3 +1,5 @@ +// Docs: https://www.diagrams.net/doc/faq/embed-mode + let iFrame = null; let lastApprovedOrigin; let onInit; let