From 324e403ae52ea83f843e3379986e19f55613ab72 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 1 Nov 2023 17:56:52 +0000 Subject: [PATCH] JS Events: Added CM pre/post init events To allow hacking of all CodeMirror instances. Closes #4639. --- dev/docs/javascript-public-events.md | 66 ++++++++++++++++++++++++++++ resources/js/code/index.mjs | 15 +++---- resources/js/code/views.js | 20 +++++++-- 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/dev/docs/javascript-public-events.md b/dev/docs/javascript-public-events.md index 95300ddd3..4f68daaeb 100644 --- a/dev/docs/javascript-public-events.md +++ b/dev/docs/javascript-public-events.md @@ -253,3 +253,69 @@ window.addEventListener('library-cm6::configure-theme', event => { }); ``` + +### `library-cm6::pre-init` + +This event is called just before any CodeMirror instances are initialised so that the instance configuration can be viewed and altered before the instance is created. + +#### Event Data + +- `usage` - A string label to identify the usage type of the CodeMirror instance in BookStack. +- `editorViewConfig` - A reference to the [EditorViewConfig](https://codemirror.net/docs/ref/#view.EditorViewConfig) that will be used to create the instance. +- `libEditorView` - The CodeMirror [EditorView](https://codemirror.net/docs/ref/#view.EditorView) class object, provided for convenience. +- `libEditorState` - The CodeMirror [EditorState](https://codemirror.net/docs/ref/#state.EditorState) class object, provided for convenience. +- `libCompartment` - The CodeMirror [Compartment](https://codemirror.net/docs/ref/#state.Compartment) class object, provided for convenience. + +##### Example + +The below shows how you'd enable the built-in line wrapping extension for page content code blocks: + +
+Show Example + +```javascript +window.addEventListener('library-cm6::pre-init', event => { + const detail = event.detail; + const config = detail.editorViewConfig; + const EditorView = detail.libEditorView; + + if (detail.usage === 'content-code-block') { + config.extensions.push(EditorView.lineWrapping); + } +}); +``` +
+ +### `library-cm6::post-init` + +This event is called just after any CodeMirror instances are initialised so that you're able to gain a reference to the CodeMirror EditorView instance. + +#### Event Data + +- `usage` - A string label to identify the usage type of the CodeMirror instance in BookStack. +- `editorView` - A reference to the [EditorView](https://codemirror.net/docs/ref/#view.EditorView) instance that has been created. +- `editorViewConfig` - A reference to the [EditorViewConfig](https://codemirror.net/docs/ref/#view.EditorViewConfig) that was used to create the instance. +- `libEditorView` - The CodeMirror [EditorView](https://codemirror.net/docs/ref/#view.EditorView) class object, provided for convenience. +- `libEditorState` - The CodeMirror [EditorState](https://codemirror.net/docs/ref/#state.EditorState) class object, provided for convenience. +- `libCompartment` - The CodeMirror [Compartment](https://codemirror.net/docs/ref/#state.Compartment) class object, provided for convenience. + +##### Example + +The below shows how you'd prepend some default text to all content (page) code blocks. + +
+Show Example + +```javascript +window.addEventListener('library-cm6::post-init', event => { + const detail = event.detail; + const editorView = detail.editorView; + + if (detail.usage === 'content-code-block') { + editorView.dispatch({ + changes: {from: 0, to: 0, insert: 'Copyright 2023\n\n'} + }); + } +}); +``` +
\ No newline at end of file diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs index e51472dc4..ada4529db 100644 --- a/resources/js/code/index.mjs +++ b/resources/js/code/index.mjs @@ -55,7 +55,7 @@ function highlightElem(elem) { const wrapper = document.createElement('div'); elem.parentNode.insertBefore(wrapper, elem); - const ev = createView({ + const ev = createView('content-code-block', { parent: wrapper, doc: content, extensions: viewerExtensions(wrapper), @@ -99,7 +99,7 @@ export function highlight() { * @returns {SimpleEditorInterface} */ export function wysiwygView(cmContainer, shadowRoot, content, language) { - const ev = createView({ + const ev = createView('content-code-block', { parent: cmContainer, doc: content, extensions: viewerExtensions(cmContainer), @@ -125,16 +125,11 @@ export function popupEditor(elem, modeSuggestion) { doc: content, extensions: [ ...editorExtensions(elem.parentElement), - EditorView.updateListener.of(v => { - if (v.docChanged) { - // textArea.value = v.state.doc.toString(); - } - }), ], }; // Create editor, hide original input - const editor = new SimpleEditorInterface(createView(config)); + const editor = new SimpleEditorInterface(createView('code-editor', config)); editor.setMode(modeSuggestion, content); elem.style.display = 'none'; @@ -163,7 +158,7 @@ export function inlineEditor(textArea, mode) { }; // Create editor view, hide original input - const ev = createView(config); + const ev = createView('code-input', config); const editor = new SimpleEditorInterface(ev); editor.setMode(mode, content); textArea.style.display = 'none'; @@ -198,7 +193,7 @@ export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) { window.$events.emitPublic(elem, 'editor-markdown-cm6::pre-init', {editorViewConfig: config}); // Create editor view, hide original input - const ev = createView(config); + const ev = createView('markdown-editor', config); (new SimpleEditorInterface(ev)).setMode('markdown', ''); elem.style.display = 'none'; diff --git a/resources/js/code/views.js b/resources/js/code/views.js index 12148ca09..5599c35dd 100644 --- a/resources/js/code/views.js +++ b/resources/js/code/views.js @@ -1,4 +1,4 @@ -import {Compartment} from '@codemirror/state'; +import {Compartment, EditorState} from '@codemirror/state'; import {EditorView} from '@codemirror/view'; import {getLanguageExtension} from './languages'; @@ -7,17 +7,31 @@ const viewLangCompartments = new WeakMap(); /** * Create a new editor view. * + * @param {String} usageType * @param {{parent: Element, doc: String, extensions: Array}} config * @returns {EditorView} */ -export function createView(config) { +export function createView(usageType, config) { const langCompartment = new Compartment(); config.extensions.push(langCompartment.of([])); - const ev = new EditorView(config); + const commonEventData = { + usage: usageType, + editorViewConfig: config, + libEditorView: EditorView, + libEditorState: EditorState, + libCompartment: Compartment, + }; + // Emit a pre-init public event so the user can tweak the config before instance creation + window.$events.emitPublic(config.parent, 'library-cm6::pre-init', commonEventData); + + const ev = new EditorView(config); viewLangCompartments.set(ev, langCompartment); + // Emit a post-init public event so the user can gain a reference to the EditorView + window.$events.emitPublic(config.parent, 'library-cm6::post-init', {editorView: ev, ...commonEventData}); + return ev; }