Added a cache-compatible module loading system/pattern to the codebase.
"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",
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"
/**
* 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);
* 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);
* @param {HTMLElement} elem
* @returns {{wrap: Element, editor: *}}
*/
-function wysiwygView(elem) {
+export function wysiwygView(elem) {
const doc = elem.ownerDocument;
const codeElem = elem.querySelector('code');
* @param {String} modeSuggestion
* @returns {*}
*/
-function popupEditor(elem, modeSuggestion) {
+export function popupEditor(elem, modeSuggestion) {
const content = elem.textContent;
return CodeMirror(function(elt) {
* @param cmInstance
* @param modeSuggestion
*/
-function setMode(cmInstance, modeSuggestion, content) {
+export function setMode(cmInstance, modeSuggestion, content) {
cmInstance.setOption('mode', getMode(modeSuggestion, content));
}
* @param cmInstance
* @param codeContent
*/
-function setContent(cmInstance, codeContent) {
+export function setContent(cmInstance, codeContent) {
cmInstance.setValue(codeContent);
setTimeout(() => {
updateLayout(cmInstance);
* Update the layout (codemirror refresh) of a cm instance.
* @param cmInstance
*/
-function updateLayout(cmInstance) {
+export function updateLayout(cmInstance) {
cmInstance.refresh();
}
* @param {HTMLElement} elem
* @returns {*}
*/
-function markdownEditor(elem) {
+export function markdownEditor(elem) {
const content = elem.textContent;
const config = {
value: content,
}
/**
- * 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
-import Code from "../services/code";
import {onChildEvent, onEnterPress, onSelect} from "../services/dom";
/**
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);
this.addHistory();
}
- updateEditorMode(language) {
+ async updateEditorMode(language) {
+ const Code = await window.importVersioned('code');
Code.setMode(this.editor, language, this.editor.getValue());
}
-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);
+ });
+ }
}
}
-import Code from "../services/code"
class DetailsHighlighter {
constructor(elem) {
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;
}
}
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";
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') {
});
}
- init() {
+ init(cmLoadPromise) {
let lastClick = 0;
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.
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.
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) {
import Clipboard from "clipboard/dist/clipboard.min";
-import Code from "../services/code";
import * as DOM from "../services/dom";
import {scrollAndHighlightElement} from "../services/util";
this.elem = elem;
this.pageId = elem.getAttribute('page-display');
- Code.highlight();
+ window.importVersioned('code').then(Code => Code.highlight());
this.setupPointer();
this.setupNavHighlighting();
this.setupDetailsCodeBlockRefresh();
-import Code from "../services/code";
-
function elemIsCodeBlock(elem) {
return elem.className === 'CodeMirrorContainer';
}
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;
showPopup(editor);
});
- function parseCodeMirrorInstances() {
+ function parseCodeMirrorInstances(Code) {
// Recover broken codemirror instances
$('.CodeMirrorContainer').filter((index ,elem) => {
});
}
- 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);
});
justify-content: center;
}
+// Prevent scroll jumps on codemirror clicks
+.page-content.mce-content-body .CodeMirror {
+ pointer-events: none;
+}
/**
* Dark Mode Overrides