X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/74b4751a1c110b4c824b14369d3a5eea3ad5816a..refs/pull/4904/head:/resources/js/code/index.mjs diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs index 07dcd53c2..d252f8f41 100644 --- a/resources/js/code/index.mjs +++ b/resources/js/code/index.mjs @@ -1,28 +1,41 @@ -import {EditorView, keymap} from "@codemirror/view"; +import {EditorView, keymap} from '@codemirror/view'; -import {copyTextToClipboard} from "../services/clipboard.js" -import {viewer, editor} from "./setups.js"; -import {createView, updateViewLanguage} from "./views.js"; +import {copyTextToClipboard} from '../services/clipboard'; +import {viewerExtensions, editorExtensions} from './setups'; +import {createView} from './views'; +import {SimpleEditorInterface} from './simple-editor-interface'; /** - * Highlight pre elements on a page + * Add a button to a CodeMirror instance which copies the contents to the clipboard upon click. + * @param {EditorView} editorView */ -export function highlight() { - const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre'); - for (const codeBlock of codeBlocks) { - highlightElem(codeBlock); - } -} +function addCopyIcon(editorView) { + const copyIcon = ''; + const checkIcon = ''; + const copyButton = document.createElement('button'); + copyButton.setAttribute('type', 'button'); + copyButton.classList.add('cm-copy-button'); + copyButton.innerHTML = copyIcon; + editorView.dom.appendChild(copyButton); -/** - * Highlight all code blocks within the given parent element - * @param {HTMLElement} parent - */ -export function highlightWithin(parent) { - const codeBlocks = parent.querySelectorAll('pre'); - for (const codeBlock of codeBlocks) { - highlightElem(codeBlock); - } + const notifyTime = 620; + const transitionTime = 60; + copyButton.addEventListener('click', () => { + copyTextToClipboard(editorView.state.doc.toString()); + copyButton.classList.add('success'); + + setTimeout(() => { + copyButton.innerHTML = checkIcon; + }, transitionTime / 2); + + setTimeout(() => { + copyButton.classList.remove('success'); + }, notifyTime); + + setTimeout(() => { + copyButton.innerHTML = copyIcon; + }, notifyTime + (transitionTime / 2)); + }); } /** @@ -31,7 +44,7 @@ export function highlightWithin(parent) { */ function highlightElem(elem) { const innerCodeElem = elem.querySelector('code[class^=language-]'); - elem.innerHTML = elem.innerHTML.replace(//gi ,'\n'); + elem.innerHTML = elem.innerHTML.replace(//gi, '\n'); const content = elem.textContent.trimEnd(); let langName = ''; @@ -42,116 +55,106 @@ function highlightElem(elem) { const wrapper = document.createElement('div'); elem.parentNode.insertBefore(wrapper, elem); - const ev = createView({ + const direction = innerCodeElem.getAttribute('dir') || elem.getAttribute('dir') || ''; + if (direction) { + wrapper.setAttribute('dir', direction); + } + + const ev = createView('content-code-block', { parent: wrapper, doc: content, - extensions: viewer(wrapper), + extensions: viewerExtensions(wrapper), }); - setMode(ev, langName, content); + const editor = new SimpleEditorInterface(ev); + editor.setMode(langName, content); + elem.remove(); addCopyIcon(ev); } /** - * Add a button to a CodeMirror instance which copies the contents to the clipboard upon click. - * @param {EditorView} editorView + * Highlight all code blocks within the given parent element + * @param {HTMLElement} parent */ -function addCopyIcon(editorView) { - const copyIcon = ``; - const checkIcon = ``; - const copyButton = document.createElement('button'); - copyButton.setAttribute('type', 'button') - copyButton.classList.add('cm-copy-button'); - copyButton.innerHTML = copyIcon; - editorView.dom.appendChild(copyButton); - - const notifyTime = 620; - const transitionTime = 60; - copyButton.addEventListener('click', event => { - copyTextToClipboard(editorView.state.doc.toString()); - copyButton.classList.add('success'); - - setTimeout(() => { - copyButton.innerHTML = checkIcon; - }, transitionTime / 2); - - setTimeout(() => { - copyButton.classList.remove('success'); - }, notifyTime); - - setTimeout(() => { - copyButton.innerHTML = copyIcon; - }, notifyTime + (transitionTime / 2)); - }); +export function highlightWithin(parent) { + const codeBlocks = parent.querySelectorAll('pre'); + for (const codeBlock of codeBlocks) { + highlightElem(codeBlock); + } } /** - * Ge the theme to use for CodeMirror instances. - * @returns {*|string} + * Highlight pre elements on a page */ -function getTheme() { - // TODO - Remove - const darkMode = document.documentElement.classList.contains('dark-mode'); - return window.codeTheme || (darkMode ? 'darcula' : 'default'); +export function highlight() { + const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre'); + for (const codeBlock of codeBlocks) { + highlightElem(codeBlock); + } } /** * Create a CodeMirror instance for showing inside the WYSIWYG editor. - * Manages a textarea element to hold code content. + * Manages a textarea element to hold code content. * @param {HTMLElement} cmContainer + * @param {ShadowRoot} shadowRoot * @param {String} content * @param {String} language - * @returns {EditorView} + * @returns {SimpleEditorInterface} */ -export function wysiwygView(cmContainer, content, language) { - const ev = createView({ +export function wysiwygView(cmContainer, shadowRoot, content, language) { + const ev = createView('content-code-block', { parent: cmContainer, doc: content, - extensions: viewer(cmContainer), + extensions: viewerExtensions(cmContainer), + root: shadowRoot, }); - setMode(ev, language, content); + const editor = new SimpleEditorInterface(ev); + editor.setMode(language, content); - return ev; + return editor; } - /** * Create a CodeMirror instance to show in the WYSIWYG pop-up editor * @param {HTMLElement} elem * @param {String} modeSuggestion - * @returns {*} + * @returns {SimpleEditorInterface} */ export function popupEditor(elem, modeSuggestion) { const content = elem.textContent; + const config = { + parent: elem.parentElement, + doc: content, + extensions: [ + ...editorExtensions(elem.parentElement), + ], + }; - return CodeMirror(function(elt) { - elem.parentNode.insertBefore(elt, elem); - elem.style.display = 'none'; - }, { - value: content, - mode: getMode(modeSuggestion, content), - lineNumbers: true, - lineWrapping: false, - theme: getTheme() - }); + // Create editor, hide original input + const editor = new SimpleEditorInterface(createView('code-editor', config)); + editor.setMode(modeSuggestion, content); + elem.style.display = 'none'; + + return editor; } /** * Create an inline editor to replace the given textarea. * @param {HTMLTextAreaElement} textArea * @param {String} mode - * @returns {EditorView} + * @returns {SimpleEditorInterface} */ export function inlineEditor(textArea, mode) { const content = textArea.value; const config = { - parent: textArea.parentNode, + parent: textArea.parentElement, doc: content, extensions: [ - ...editor(textArea.parentElement), - EditorView.updateListener.of((v) => { + ...editorExtensions(textArea.parentElement), + EditorView.updateListener.of(v => { if (v.docChanged) { textArea.value = v.state.doc.toString(); } @@ -160,42 +163,12 @@ export function inlineEditor(textArea, mode) { }; // Create editor view, hide original input - const ev = createView(config); - setMode(ev, mode, content); + const ev = createView('code-input', config); + const editor = new SimpleEditorInterface(ev); + editor.setMode(mode, content); textArea.style.display = 'none'; - return ev; -} - -/** - * Set the language mode of a codemirror EditorView. - * - * @param {EditorView} ev - * @param {string} modeSuggestion - * @param {string} content - */ -export function setMode(ev, modeSuggestion, content) { - updateViewLanguage(ev, modeSuggestion, content); -} - -/** - * Set the content of a cm instance. - * @param cmInstance - * @param codeContent - */ -export function setContent(cmInstance, codeContent) { - cmInstance.setValue(codeContent); - setTimeout(() => { - updateLayout(cmInstance); - }, 10); -} - -/** - * Update the layout (codemirror refresh) of a cm instance. - * @param cmInstance - */ -export function updateLayout(cmInstance) { - cmInstance.refresh(); + return editor; } /** @@ -204,30 +177,30 @@ export function updateLayout(cmInstance) { * @param {function} onChange * @param {object} domEventHandlers * @param {Array} keyBindings - * @returns {*} + * @returns {EditorView} */ export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) { const content = elem.textContent; const config = { - parent: elem.parentNode, + parent: elem.parentElement, doc: content, extensions: [ - ...editor(elem.parentElement), - EditorView.updateListener.of((v) => { + keymap.of(keyBindings), + ...editorExtensions(elem.parentElement), + EditorView.updateListener.of(v => { onChange(v); }), EditorView.domEventHandlers(domEventHandlers), - keymap.of(keyBindings), ], }; // Emit a pre-event public event to allow tweaking of the configure before view creation. - window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {cmEditorViewConfig: config}); + window.$events.emitPublic(elem, 'editor-markdown-cm6::pre-init', {editorViewConfig: config}); // Create editor view, hide original input - const ev = createView(config); - setMode(ev, 'markdown', ''); + const ev = createView('markdown-editor', config); + (new SimpleEditorInterface(ev)).setMode('markdown', ''); elem.style.display = 'none'; return ev; -} \ No newline at end of file +}