X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/9874a53206ffbaa6ac108bf6dc63fbe85cc370b2..HEAD:/resources/js/code/index.mjs diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs index 1a3429d58..a2850c06b 100644 --- a/resources/js/code/index.mjs +++ b/resources/js/code/index.mjs @@ -1,29 +1,58 @@ -import {EditorView, keymap} from "@codemirror/view"; -import {copyTextToClipboard} from "../services/clipboard.js" +import {EditorView, keymap} from '@codemirror/view'; -// Modes -import {viewer, editor} from "./setups.js"; -import {createView, updateViewLanguage} from "./views.js"; +import {copyTextToClipboard} from '../services/clipboard.ts'; +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); + + 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)); + }); } /** - * Highlight all code blocks within the given parent element - * @param {HTMLElement} parent + * @param {HTMLElement} codeElem + * @returns {String} */ -export function highlightWithin(parent) { - const codeBlocks = parent.querySelectorAll('pre'); - for (const codeBlock of codeBlocks) { - highlightElem(codeBlock); +function getDirectionFromCodeBlock(codeElem) { + let dir = ''; + const innerCodeElem = codeElem.querySelector('code'); + + if (innerCodeElem && innerCodeElem.hasAttribute('dir')) { + dir = innerCodeElem.getAttribute('dir'); + } else if (codeElem.hasAttribute('dir')) { + dir = codeElem.getAttribute('dir'); } + + return dir; } /** @@ -32,7 +61,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 = ''; @@ -43,133 +72,120 @@ function highlightElem(elem) { const wrapper = document.createElement('div'); elem.parentNode.insertBefore(wrapper, elem); - const ev = createView({ + const direction = getDirectionFromCodeBlock(elem); + if (direction) { + wrapper.setAttribute('dir', direction); + } + + const ev = createView('content-code-block', { parent: wrapper, doc: content, - extensions: viewer(), + extensions: viewerExtensions(wrapper), }); - setMode(ev, langName, content); - elem.remove(); + 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 copyButton = document.createElement('button'); - copyButton.setAttribute('type', 'button') - copyButton.classList.add('cm-copy-button'); - copyButton.innerHTML = copyIcon; - editorView.dom.appendChild(copyButton); - - copyButton.addEventListener('click', event => { - copyTextToClipboard(editorView.state.doc.toString()); - copyButton.classList.add('success'); - setTimeout(() => { - copyButton.classList.remove('success'); - }, 240); - }); +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() { - 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 {{wrap: Element, editor: *}} + * @returns {SimpleEditorInterface} */ -export function wysiwygView(cmContainer, content, language) { - return CodeMirror(cmContainer, { - value: content, - mode: getMode(language, content), - lineNumbers: true, - lineWrapping: false, - theme: getTheme(), - readOnly: true +export function wysiwygView(cmContainer, shadowRoot, content, language) { + const ev = createView('content-code-block', { + parent: cmContainer, + doc: content, + extensions: viewerExtensions(cmContainer), + root: shadowRoot, }); -} + const editor = new SimpleEditorInterface(ev); + editor.setMode(language, content); + + 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 {CodeMirror3} + * @returns {SimpleEditorInterface} */ export function inlineEditor(textArea, mode) { - return CodeMirror.fromTextArea(textArea, { - mode: getMode(mode, textArea.value), - lineNumbers: true, - lineWrapping: false, - theme: getTheme(), - }); -} - -/** - * 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); -} + const content = textArea.value; + const config = { + parent: textArea.parentElement, + doc: content, + extensions: [ + ...editorExtensions(textArea.parentElement), + EditorView.updateListener.of(v => { + if (v.docChanged) { + textArea.value = v.state.doc.toString(); + } + }), + ], + }; -/** - * Set the content of a cm instance. - * @param cmInstance - * @param codeContent - */ -export function setContent(cmInstance, codeContent) { - cmInstance.setValue(codeContent); - setTimeout(() => { - updateLayout(cmInstance); - }, 10); -} + // Create editor view, hide original input + const ev = createView('code-input', config); + const editor = new SimpleEditorInterface(ev); + editor.setMode(mode, content); + textArea.style.display = 'none'; -/** - * Update the layout (codemirror refresh) of a cm instance. - * @param cmInstance - */ -export function updateLayout(cmInstance) { - cmInstance.refresh(); + return editor; } /** @@ -178,30 +194,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('markdown'), - 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 +}