]> BookStack Code Mirror - bookstack/commitdiff
Split out codemirror JS to its own module 3247/head
authorDan Brown <redacted>
Tue, 8 Feb 2022 11:10:01 +0000 (11:10 +0000)
committerDan Brown <redacted>
Tue, 8 Feb 2022 11:10:01 +0000 (11:10 +0000)
Added a cache-compatible module loading system/pattern to the codebase.

package.json
resources/js/app.js [moved from resources/js/index.js with 76% similarity]
resources/js/code.mjs [moved from resources/js/services/code.js with 93% similarity]
resources/js/components/code-editor.js
resources/js/components/code-highlighter.js
resources/js/components/details-highlighter.js
resources/js/components/markdown-editor.js
resources/js/components/page-display.js
resources/js/wysiwyg/plugin-codeeditor.js
resources/sass/_tinymce.scss

index 23fc902eba66222bbfee848641cab77493fb7e53..b1899e232a923c686104e42aa145eee5f5eb743a 100644 (file)
@@ -4,9 +4,9 @@
     "build:css:dev": "sass ./resources/sass:./public/dist",
     "build:css:watch": "sass ./resources/sass:./public/dist --watch",
     "build:css:production": "sass ./resources/sass:./public/dist -s compressed",
-    "build:js:dev": "esbuild --bundle ./resources/js/index.js --outfile=public/dist/app.js --sourcemap --target=es2019 --main-fields=module,main",
+    "build:js:dev": "esbuild --bundle ./resources/js/*.{js,mjs} --outdir=public/dist/ --sourcemap --target=es2020 --main-fields=module,main --format=esm",
     "build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
-    "build:js:production": "NODE_ENV=production esbuild --bundle ./resources/js/index.js --outfile=public/dist/app.js --sourcemap --target=es2019 --main-fields=module,main --minify",
+    "build:js:production": "NODE_ENV=production esbuild --bundle ./resources/js/*.{js,mjs} --outdir=public/dist/ --sourcemap --target=es2020 --main-fields=module,main --minify --format=esm",
     "build": "npm-run-all --parallel build:*:dev",
     "production": "npm-run-all --parallel build:*:production",
     "dev": "npm-run-all --parallel watch livereload",
similarity index 76%
rename from resources/js/index.js
rename to resources/js/app.js
index ffdb54e191e1557b7c3cbb6e9f6b4402913c40b9..82748b75e223c2f21612a2662bc2fad22c23b6c0 100644 (file)
@@ -6,6 +6,12 @@ window.baseUrl = function(path) {
     return basePath + '/' + path;
 };
 
+window.importVersioned = function(moduleName) {
+    const version = document.querySelector('link[href*="/dist/styles.css?version="]').href.split('?version=').pop();
+    const importPath = window.baseUrl(`dist/${moduleName}.js?version=${version}`);
+    return import(importPath);
+};
+
 // Set events and http services on window
 import events from "./services/events"
 import httpInstance from "./services/http"
similarity index 93%
rename from resources/js/services/code.js
rename to resources/js/code.mjs
index d82db52710c9b0b0f15cda09ab0866d688632c49..3a77065731000315653a0cb8d4c5f867b32fd886 100644 (file)
@@ -98,7 +98,7 @@ const modeMap = {
 /**
  * Highlight pre elements on a page
  */
-function highlight() {
+export function highlight() {
     const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
     for (const codeBlock of codeBlocks) {
         highlightElem(codeBlock);
@@ -109,7 +109,7 @@ function highlight() {
  * Highlight all code blocks within the given parent element
  * @param {HTMLElement} parent
  */
-function highlightWithin(parent) {
+export function highlightWithin(parent) {
     const codeBlocks = parent.querySelectorAll('pre');
     for (const codeBlock of codeBlocks) {
         highlightElem(codeBlock);
@@ -207,7 +207,7 @@ function getTheme() {
  * @param {HTMLElement} elem
  * @returns {{wrap: Element, editor: *}}
  */
-function wysiwygView(elem) {
+export function wysiwygView(elem) {
     const doc = elem.ownerDocument;
     const codeElem = elem.querySelector('code');
 
@@ -261,7 +261,7 @@ function getLanguageFromCssClasses(classes) {
  * @param {String} modeSuggestion
  * @returns {*}
  */
-function popupEditor(elem, modeSuggestion) {
+export function popupEditor(elem, modeSuggestion) {
     const content = elem.textContent;
 
     return CodeMirror(function(elt) {
@@ -281,7 +281,7 @@ function popupEditor(elem, modeSuggestion) {
  * @param cmInstance
  * @param modeSuggestion
  */
-function setMode(cmInstance, modeSuggestion, content) {
+export function setMode(cmInstance, modeSuggestion, content) {
       cmInstance.setOption('mode', getMode(modeSuggestion, content));
 }
 
@@ -290,7 +290,7 @@ function setMode(cmInstance, modeSuggestion, content) {
  * @param cmInstance
  * @param codeContent
  */
-function setContent(cmInstance, codeContent) {
+export function setContent(cmInstance, codeContent) {
     cmInstance.setValue(codeContent);
     setTimeout(() => {
         updateLayout(cmInstance);
@@ -301,7 +301,7 @@ function setContent(cmInstance, codeContent) {
  * Update the layout (codemirror refresh) of a cm instance.
  * @param cmInstance
  */
-function updateLayout(cmInstance) {
+export function updateLayout(cmInstance) {
     cmInstance.refresh();
 }
 
@@ -310,7 +310,7 @@ function updateLayout(cmInstance) {
  * @param {HTMLElement} elem
  * @returns {*}
  */
-function markdownEditor(elem) {
+export function markdownEditor(elem) {
     const content = elem.textContent;
     const config = {
         value: content,
@@ -330,22 +330,10 @@ function markdownEditor(elem) {
 }
 
 /**
- * Get the 'meta' key dependant on the user's system.
+ * Get the 'meta' key dependent on the user's system.
  * @returns {string}
  */
-function getMetaKey() {
+export function getMetaKey() {
     let mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
     return mac ? "Cmd" : "Ctrl";
-}
-
-export default {
-    highlight: highlight,
-    highlightWithin: highlightWithin,
-    wysiwygView: wysiwygView,
-    popupEditor: popupEditor,
-    setMode: setMode,
-    setContent: setContent,
-    updateLayout: updateLayout,
-    markdownEditor: markdownEditor,
-    getMetaKey: getMetaKey,
-};
+}
\ No newline at end of file
index 2e3506ec7a428d7dc0c146e8ec1d4e446ce8979f..a8a2c0c6f0ac793f0c36f7913533a148176a30f5 100644 (file)
@@ -1,4 +1,3 @@
-import Code from "../services/code";
 import {onChildEvent, onEnterPress, onSelect} from "../services/dom";
 
 /**
@@ -63,13 +62,17 @@ class CodeEditor {
         this.show();
         this.updateEditorMode(language);
 
-        Code.setContent(this.editor, code);
+        window.importVersioned('code').then(Code => {
+            Code.setContent(this.editor, code);
+        });
     }
 
-    show() {
+    async show() {
+        const Code = await window.importVersioned('code');
         if (!this.editor) {
             this.editor = Code.popupEditor(this.editorInput, this.languageInput.value);
         }
+
         this.loadHistory();
         this.popup.components.popup.show(() => {
             Code.updateLayout(this.editor);
@@ -84,7 +87,8 @@ class CodeEditor {
         this.addHistory();
     }
 
-    updateEditorMode(language) {
+    async updateEditorMode(language) {
+        const Code = await window.importVersioned('code');
         Code.setMode(this.editor, language, this.editor.getValue());
     }
 
index db611288701d060e7bfa56fee39567b26b260522..5ffab377525d875f34e02f7be6d65e68dee1c4c3 100644 (file)
@@ -1,8 +1,12 @@
-import Code from "../services/code"
 class CodeHighlighter {
 
     constructor(elem) {
-        Code.highlightWithin(elem);
+        const codeBlocks = elem.querySelectorAll('pre');
+        if (codeBlocks.length > 0) {
+            window.importVersioned('code').then(Code => {
+               Code.highlightWithin(elem);
+            });
+        }
     }
 
 }
index 18c5165faf49408e140685d5cc0e0ddceeaead2c..1f3b66c674c2c882b7f1d71b4fcb79867c06dcfe 100644 (file)
@@ -1,4 +1,3 @@
-import Code from "../services/code"
 class DetailsHighlighter {
 
     constructor(elem) {
@@ -10,7 +9,11 @@ class DetailsHighlighter {
     onToggle() {
         if (this.dealtWith) return;
 
-        Code.highlightWithin(this.elem);
+        if (this.elem.querySelector('pre')) {
+            window.importVersioned('code').then(Code => {
+                Code.highlightWithin(this.elem);
+            });
+        }
         this.dealtWith = true;
     }
 }
index e41ab15f622ad1386c19f416679a01da1377d8ce..297d9c8ece8bcdae60befb118b59ffc2388165de 100644 (file)
@@ -1,6 +1,5 @@
 import MarkdownIt from "markdown-it";
 import mdTasksLists from 'markdown-it-task-lists';
-import code from '../services/code';
 import Clipboard from "../services/clipboard";
 import {debounce} from "../services/util";
 
@@ -23,13 +22,20 @@ class MarkdownEditor {
 
         this.displayStylesLoaded = false;
         this.input = this.elem.querySelector('textarea');
-        this.cm = code.markdownEditor(this.input);
+
+        this.cm = null;
+        this.Code = null;
+        const cmLoadPromise = window.importVersioned('code').then(Code => {
+            this.cm = Code.markdownEditor(this.input);
+            this.Code = Code;
+            return this.cm;
+        });
 
         this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
 
         const displayLoad = () => {
             this.displayDoc = this.display.contentDocument;
-            this.init();
+            this.init(cmLoadPromise);
         };
 
         if (this.display.contentDocument.readyState === 'complete') {
@@ -45,7 +51,7 @@ class MarkdownEditor {
         });
     }
 
-    init() {
+    init(cmLoadPromise) {
 
         let lastClick = 0;
 
@@ -98,7 +104,15 @@ class MarkdownEditor {
             toolbarLabel.closest('.markdown-editor-wrap').classList.add('active');
         });
 
-        this.codeMirrorSetup();
+        cmLoadPromise.then(cm => {
+            this.codeMirrorSetup(cm);
+
+            // Refresh CodeMirror on container resize
+            const resizeDebounced = debounce(() => this.Code.updateLayout(cm), 100, false);
+            const observer = new ResizeObserver(resizeDebounced);
+            observer.observe(this.elem);
+        });
+
         this.listenForBookStackEditorEvents();
 
         // Scroll to text if needed.
@@ -107,11 +121,6 @@ class MarkdownEditor {
         if (scrollText) {
             this.scrollToText(scrollText);
         }
-
-        // Refresh CodeMirror on container resize
-        const resizeDebounced = debounce(() => code.updateLayout(this.cm), 100, false);
-        const observer = new ResizeObserver(resizeDebounced);
-        observer.observe(this.elem);
     }
 
     // Update the input content and render the display.
@@ -158,15 +167,14 @@ class MarkdownEditor {
         topElem.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth'});
     }
 
-    codeMirrorSetup() {
-        const cm = this.cm;
+    codeMirrorSetup(cm) {
         const context = this;
 
         // Text direction
         // cm.setOption('direction', this.textDirection);
         cm.setOption('direction', 'ltr'); // Will force to remain as ltr for now due to issues when HTML is in editor.
         // Custom key commands
-        let metaKey = code.getMetaKey();
+        let metaKey = this.Code.getMetaKey();
         const extraKeys = {};
         // Insert Image shortcut
         extraKeys[`${metaKey}-Alt-I`] = function(cm) {
index cc55fe35e1e38caae9de9f9cca013e0c7b1d14f6..88254fd3a4efa97719bd1d3028afda2971ea601a 100644 (file)
@@ -1,5 +1,4 @@
 import Clipboard from "clipboard/dist/clipboard.min";
-import Code from "../services/code";
 import * as DOM from "../services/dom";
 import {scrollAndHighlightElement} from "../services/util";
 
@@ -9,7 +8,7 @@ class PageDisplay {
         this.elem = elem;
         this.pageId = elem.getAttribute('page-display');
 
-        Code.highlight();
+        window.importVersioned('code').then(Code => Code.highlight());
         this.setupPointer();
         this.setupNavHighlighting();
         this.setupDetailsCodeBlockRefresh();
index 92781f02462afdda1e480e3effa1a17ee49e7c1c..f0ec78afcc852a8c952fb2f9701ae5e301a7536e 100644 (file)
@@ -1,5 +1,3 @@
-import Code from "../services/code";
-
 function elemIsCodeBlock(elem) {
     return elem.className === 'CodeMirrorContainer';
 }
@@ -31,8 +29,10 @@ function showPopup(editor) {
         const editorElem = selectedNode.querySelector('.CodeMirror');
         const cmInstance = editorElem.CodeMirror;
         if (cmInstance) {
-            Code.setContent(cmInstance, code);
-            Code.setMode(cmInstance, lang, code);
+            window.importVersioned('code').then(Code => {
+                Code.setContent(cmInstance, code);
+                Code.setMode(cmInstance, lang, code);
+            });
         }
         const textArea = selectedNode.querySelector('textarea');
         if (textArea) textArea.textContent = code;
@@ -93,7 +93,7 @@ function register(editor, url) {
         showPopup(editor);
     });
 
-    function parseCodeMirrorInstances() {
+    function parseCodeMirrorInstances(Code) {
 
         // Recover broken codemirror instances
         $('.CodeMirrorContainer').filter((index ,elem) => {
@@ -111,17 +111,18 @@ function register(editor, url) {
         });
     }
 
-    editor.on('init', function() {
+    editor.on('init', async function() {
+        const Code = await window.importVersioned('code');
         // Parse code mirror instances on init, but delay a little so this runs after
         // initial styles are fetched into the editor.
         editor.undoManager.transact(function () {
-            parseCodeMirrorInstances();
+            parseCodeMirrorInstances(Code);
         });
         // Parsed code mirror blocks when content is set but wait before setting this handler
         // to avoid any init 'SetContent' events.
         setTimeout(() => {
             editor.on('SetContent', () => {
-                setTimeout(parseCodeMirrorInstances, 100);
+                setTimeout(() => parseCodeMirrorInstances(Code), 100);
             });
         }, 200);
     });
index f98de06a0024d3a948d31f92c347b2acf8556f26..642598496bd36bbcbd2aa48e62e5720cac99ae19 100644 (file)
   justify-content: center;
 }
 
+// Prevent scroll jumps on codemirror clicks
+.page-content.mce-content-body .CodeMirror {
+  pointer-events: none;
+}
 
 /**
  * Dark Mode Overrides