mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-05 17:16:52 +03:00
Split out codemirror JS to its own module
Added a cache-compatible module loading system/pattern to the codebase.
This commit is contained in:
parent
130dc05517
commit
a2bcf765a8
@ -4,9 +4,9 @@
|
|||||||
"build:css:dev": "sass ./resources/sass:./public/dist",
|
"build:css:dev": "sass ./resources/sass:./public/dist",
|
||||||
"build:css:watch": "sass ./resources/sass:./public/dist --watch",
|
"build:css:watch": "sass ./resources/sass:./public/dist --watch",
|
||||||
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
|
"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: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",
|
"build": "npm-run-all --parallel build:*:dev",
|
||||||
"production": "npm-run-all --parallel build:*:production",
|
"production": "npm-run-all --parallel build:*:production",
|
||||||
"dev": "npm-run-all --parallel watch livereload",
|
"dev": "npm-run-all --parallel watch livereload",
|
||||||
|
@ -6,6 +6,12 @@ window.baseUrl = function(path) {
|
|||||||
return basePath + '/' + 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
|
// Set events and http services on window
|
||||||
import events from "./services/events"
|
import events from "./services/events"
|
||||||
import httpInstance from "./services/http"
|
import httpInstance from "./services/http"
|
@ -98,7 +98,7 @@ const modeMap = {
|
|||||||
/**
|
/**
|
||||||
* Highlight pre elements on a page
|
* Highlight pre elements on a page
|
||||||
*/
|
*/
|
||||||
function highlight() {
|
export function highlight() {
|
||||||
const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
|
const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
|
||||||
for (const codeBlock of codeBlocks) {
|
for (const codeBlock of codeBlocks) {
|
||||||
highlightElem(codeBlock);
|
highlightElem(codeBlock);
|
||||||
@ -109,7 +109,7 @@ function highlight() {
|
|||||||
* Highlight all code blocks within the given parent element
|
* Highlight all code blocks within the given parent element
|
||||||
* @param {HTMLElement} parent
|
* @param {HTMLElement} parent
|
||||||
*/
|
*/
|
||||||
function highlightWithin(parent) {
|
export function highlightWithin(parent) {
|
||||||
const codeBlocks = parent.querySelectorAll('pre');
|
const codeBlocks = parent.querySelectorAll('pre');
|
||||||
for (const codeBlock of codeBlocks) {
|
for (const codeBlock of codeBlocks) {
|
||||||
highlightElem(codeBlock);
|
highlightElem(codeBlock);
|
||||||
@ -207,7 +207,7 @@ function getTheme() {
|
|||||||
* @param {HTMLElement} elem
|
* @param {HTMLElement} elem
|
||||||
* @returns {{wrap: Element, editor: *}}
|
* @returns {{wrap: Element, editor: *}}
|
||||||
*/
|
*/
|
||||||
function wysiwygView(elem) {
|
export function wysiwygView(elem) {
|
||||||
const doc = elem.ownerDocument;
|
const doc = elem.ownerDocument;
|
||||||
const codeElem = elem.querySelector('code');
|
const codeElem = elem.querySelector('code');
|
||||||
|
|
||||||
@ -261,7 +261,7 @@ function getLanguageFromCssClasses(classes) {
|
|||||||
* @param {String} modeSuggestion
|
* @param {String} modeSuggestion
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
function popupEditor(elem, modeSuggestion) {
|
export function popupEditor(elem, modeSuggestion) {
|
||||||
const content = elem.textContent;
|
const content = elem.textContent;
|
||||||
|
|
||||||
return CodeMirror(function(elt) {
|
return CodeMirror(function(elt) {
|
||||||
@ -281,7 +281,7 @@ function popupEditor(elem, modeSuggestion) {
|
|||||||
* @param cmInstance
|
* @param cmInstance
|
||||||
* @param modeSuggestion
|
* @param modeSuggestion
|
||||||
*/
|
*/
|
||||||
function setMode(cmInstance, modeSuggestion, content) {
|
export function setMode(cmInstance, modeSuggestion, content) {
|
||||||
cmInstance.setOption('mode', getMode(modeSuggestion, content));
|
cmInstance.setOption('mode', getMode(modeSuggestion, content));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ function setMode(cmInstance, modeSuggestion, content) {
|
|||||||
* @param cmInstance
|
* @param cmInstance
|
||||||
* @param codeContent
|
* @param codeContent
|
||||||
*/
|
*/
|
||||||
function setContent(cmInstance, codeContent) {
|
export function setContent(cmInstance, codeContent) {
|
||||||
cmInstance.setValue(codeContent);
|
cmInstance.setValue(codeContent);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
updateLayout(cmInstance);
|
updateLayout(cmInstance);
|
||||||
@ -301,7 +301,7 @@ function setContent(cmInstance, codeContent) {
|
|||||||
* Update the layout (codemirror refresh) of a cm instance.
|
* Update the layout (codemirror refresh) of a cm instance.
|
||||||
* @param cmInstance
|
* @param cmInstance
|
||||||
*/
|
*/
|
||||||
function updateLayout(cmInstance) {
|
export function updateLayout(cmInstance) {
|
||||||
cmInstance.refresh();
|
cmInstance.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +310,7 @@ function updateLayout(cmInstance) {
|
|||||||
* @param {HTMLElement} elem
|
* @param {HTMLElement} elem
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
function markdownEditor(elem) {
|
export function markdownEditor(elem) {
|
||||||
const content = elem.textContent;
|
const content = elem.textContent;
|
||||||
const config = {
|
const config = {
|
||||||
value: content,
|
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}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function getMetaKey() {
|
export function getMetaKey() {
|
||||||
let mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
|
let mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
|
||||||
return mac ? "Cmd" : "Ctrl";
|
return mac ? "Cmd" : "Ctrl";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
|
||||||
highlight: highlight,
|
|
||||||
highlightWithin: highlightWithin,
|
|
||||||
wysiwygView: wysiwygView,
|
|
||||||
popupEditor: popupEditor,
|
|
||||||
setMode: setMode,
|
|
||||||
setContent: setContent,
|
|
||||||
updateLayout: updateLayout,
|
|
||||||
markdownEditor: markdownEditor,
|
|
||||||
getMetaKey: getMetaKey,
|
|
||||||
};
|
|
@ -1,4 +1,3 @@
|
|||||||
import Code from "../services/code";
|
|
||||||
import {onChildEvent, onEnterPress, onSelect} from "../services/dom";
|
import {onChildEvent, onEnterPress, onSelect} from "../services/dom";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,13 +62,17 @@ class CodeEditor {
|
|||||||
this.show();
|
this.show();
|
||||||
this.updateEditorMode(language);
|
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) {
|
if (!this.editor) {
|
||||||
this.editor = Code.popupEditor(this.editorInput, this.languageInput.value);
|
this.editor = Code.popupEditor(this.editorInput, this.languageInput.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadHistory();
|
this.loadHistory();
|
||||||
this.popup.components.popup.show(() => {
|
this.popup.components.popup.show(() => {
|
||||||
Code.updateLayout(this.editor);
|
Code.updateLayout(this.editor);
|
||||||
@ -84,7 +87,8 @@ class CodeEditor {
|
|||||||
this.addHistory();
|
this.addHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEditorMode(language) {
|
async updateEditorMode(language) {
|
||||||
|
const Code = await window.importVersioned('code');
|
||||||
Code.setMode(this.editor, language, this.editor.getValue());
|
Code.setMode(this.editor, language, this.editor.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import Code from "../services/code"
|
|
||||||
class CodeHighlighter {
|
class CodeHighlighter {
|
||||||
|
|
||||||
constructor(elem) {
|
constructor(elem) {
|
||||||
Code.highlightWithin(elem);
|
const codeBlocks = elem.querySelectorAll('pre');
|
||||||
|
if (codeBlocks.length > 0) {
|
||||||
|
window.importVersioned('code').then(Code => {
|
||||||
|
Code.highlightWithin(elem);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Code from "../services/code"
|
|
||||||
class DetailsHighlighter {
|
class DetailsHighlighter {
|
||||||
|
|
||||||
constructor(elem) {
|
constructor(elem) {
|
||||||
@ -10,7 +9,11 @@ class DetailsHighlighter {
|
|||||||
onToggle() {
|
onToggle() {
|
||||||
if (this.dealtWith) return;
|
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;
|
this.dealtWith = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import MarkdownIt from "markdown-it";
|
import MarkdownIt from "markdown-it";
|
||||||
import mdTasksLists from 'markdown-it-task-lists';
|
import mdTasksLists from 'markdown-it-task-lists';
|
||||||
import code from '../services/code';
|
|
||||||
import Clipboard from "../services/clipboard";
|
import Clipboard from "../services/clipboard";
|
||||||
import {debounce} from "../services/util";
|
import {debounce} from "../services/util";
|
||||||
|
|
||||||
@ -23,13 +22,20 @@ class MarkdownEditor {
|
|||||||
|
|
||||||
this.displayStylesLoaded = false;
|
this.displayStylesLoaded = false;
|
||||||
this.input = this.elem.querySelector('textarea');
|
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);
|
this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
|
||||||
|
|
||||||
const displayLoad = () => {
|
const displayLoad = () => {
|
||||||
this.displayDoc = this.display.contentDocument;
|
this.displayDoc = this.display.contentDocument;
|
||||||
this.init();
|
this.init(cmLoadPromise);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.display.contentDocument.readyState === 'complete') {
|
if (this.display.contentDocument.readyState === 'complete') {
|
||||||
@ -45,7 +51,7 @@ class MarkdownEditor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init(cmLoadPromise) {
|
||||||
|
|
||||||
let lastClick = 0;
|
let lastClick = 0;
|
||||||
|
|
||||||
@ -98,7 +104,15 @@ class MarkdownEditor {
|
|||||||
toolbarLabel.closest('.markdown-editor-wrap').classList.add('active');
|
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();
|
this.listenForBookStackEditorEvents();
|
||||||
|
|
||||||
// Scroll to text if needed.
|
// Scroll to text if needed.
|
||||||
@ -107,11 +121,6 @@ class MarkdownEditor {
|
|||||||
if (scrollText) {
|
if (scrollText) {
|
||||||
this.scrollToText(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.
|
// Update the input content and render the display.
|
||||||
@ -158,15 +167,14 @@ class MarkdownEditor {
|
|||||||
topElem.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth'});
|
topElem.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth'});
|
||||||
}
|
}
|
||||||
|
|
||||||
codeMirrorSetup() {
|
codeMirrorSetup(cm) {
|
||||||
const cm = this.cm;
|
|
||||||
const context = this;
|
const context = this;
|
||||||
|
|
||||||
// Text direction
|
// Text direction
|
||||||
// cm.setOption('direction', this.textDirection);
|
// 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.
|
cm.setOption('direction', 'ltr'); // Will force to remain as ltr for now due to issues when HTML is in editor.
|
||||||
// Custom key commands
|
// Custom key commands
|
||||||
let metaKey = code.getMetaKey();
|
let metaKey = this.Code.getMetaKey();
|
||||||
const extraKeys = {};
|
const extraKeys = {};
|
||||||
// Insert Image shortcut
|
// Insert Image shortcut
|
||||||
extraKeys[`${metaKey}-Alt-I`] = function(cm) {
|
extraKeys[`${metaKey}-Alt-I`] = function(cm) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import Clipboard from "clipboard/dist/clipboard.min";
|
import Clipboard from "clipboard/dist/clipboard.min";
|
||||||
import Code from "../services/code";
|
|
||||||
import * as DOM from "../services/dom";
|
import * as DOM from "../services/dom";
|
||||||
import {scrollAndHighlightElement} from "../services/util";
|
import {scrollAndHighlightElement} from "../services/util";
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ class PageDisplay {
|
|||||||
this.elem = elem;
|
this.elem = elem;
|
||||||
this.pageId = elem.getAttribute('page-display');
|
this.pageId = elem.getAttribute('page-display');
|
||||||
|
|
||||||
Code.highlight();
|
window.importVersioned('code').then(Code => Code.highlight());
|
||||||
this.setupPointer();
|
this.setupPointer();
|
||||||
this.setupNavHighlighting();
|
this.setupNavHighlighting();
|
||||||
this.setupDetailsCodeBlockRefresh();
|
this.setupDetailsCodeBlockRefresh();
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import Code from "../services/code";
|
|
||||||
|
|
||||||
function elemIsCodeBlock(elem) {
|
function elemIsCodeBlock(elem) {
|
||||||
return elem.className === 'CodeMirrorContainer';
|
return elem.className === 'CodeMirrorContainer';
|
||||||
}
|
}
|
||||||
@ -31,8 +29,10 @@ function showPopup(editor) {
|
|||||||
const editorElem = selectedNode.querySelector('.CodeMirror');
|
const editorElem = selectedNode.querySelector('.CodeMirror');
|
||||||
const cmInstance = editorElem.CodeMirror;
|
const cmInstance = editorElem.CodeMirror;
|
||||||
if (cmInstance) {
|
if (cmInstance) {
|
||||||
Code.setContent(cmInstance, code);
|
window.importVersioned('code').then(Code => {
|
||||||
Code.setMode(cmInstance, lang, code);
|
Code.setContent(cmInstance, code);
|
||||||
|
Code.setMode(cmInstance, lang, code);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const textArea = selectedNode.querySelector('textarea');
|
const textArea = selectedNode.querySelector('textarea');
|
||||||
if (textArea) textArea.textContent = code;
|
if (textArea) textArea.textContent = code;
|
||||||
@ -93,7 +93,7 @@ function register(editor, url) {
|
|||||||
showPopup(editor);
|
showPopup(editor);
|
||||||
});
|
});
|
||||||
|
|
||||||
function parseCodeMirrorInstances() {
|
function parseCodeMirrorInstances(Code) {
|
||||||
|
|
||||||
// Recover broken codemirror instances
|
// Recover broken codemirror instances
|
||||||
$('.CodeMirrorContainer').filter((index ,elem) => {
|
$('.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
|
// Parse code mirror instances on init, but delay a little so this runs after
|
||||||
// initial styles are fetched into the editor.
|
// initial styles are fetched into the editor.
|
||||||
editor.undoManager.transact(function () {
|
editor.undoManager.transact(function () {
|
||||||
parseCodeMirrorInstances();
|
parseCodeMirrorInstances(Code);
|
||||||
});
|
});
|
||||||
// Parsed code mirror blocks when content is set but wait before setting this handler
|
// Parsed code mirror blocks when content is set but wait before setting this handler
|
||||||
// to avoid any init 'SetContent' events.
|
// to avoid any init 'SetContent' events.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editor.on('SetContent', () => {
|
editor.on('SetContent', () => {
|
||||||
setTimeout(parseCodeMirrorInstances, 100);
|
setTimeout(() => parseCodeMirrorInstances(Code), 100);
|
||||||
});
|
});
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
@ -32,6 +32,10 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent scroll jumps on codemirror clicks
|
||||||
|
.page-content.mce-content-body .CodeMirror {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dark Mode Overrides
|
* Dark Mode Overrides
|
||||||
|
Loading…
x
Reference in New Issue
Block a user