X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/572037ef1fd1e778c33c609ef295c50de33a0652..refs/pull/5601/head:/resources/js/code/index.mjs diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs index 6cad052f4..a2850c06b 100644 --- a/resources/js/code/index.mjs +++ b/resources/js/code/index.mjs @@ -1,29 +1,58 @@ -import {EditorView} from "@codemirror/view" -import Clipboard from "clipboard/dist/clipboard.min"; +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,138 +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 cmInstance + * Highlight all code blocks within the given parent element + * @param {HTMLElement} parent */ -function addCopyIcon(cmInstance) { - // TODO - // const copyIcon = ``; - // const copyButton = document.createElement('div'); - // copyButton.classList.add('CodeMirror-copy'); - // copyButton.innerHTML = copyIcon; - // cmInstance.display.wrapper.appendChild(copyButton); - // - // const clipboard = new Clipboard(copyButton, { - // text: function(trigger) { - // return cmInstance.getValue() - // } - // }); - // - // clipboard.on('success', event => { - // 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; } /** @@ -182,37 +193,31 @@ export function updateLayout(cmInstance) { * @param {HTMLElement} elem * @param {function} onChange * @param {object} domEventHandlers - * @returns {*} + * @param {Array} keyBindings + * @returns {EditorView} */ -export function markdownEditor(elem, onChange, domEventHandlers) { +export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) { const content = elem.textContent; - - // TODO - Change to pass something else that's useful, probably extension array? - // window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {config}); - - const ev = createView({ - parent: elem.parentNode, + const config = { + 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), ], - }); + }; + // Emit a pre-event public event to allow tweaking of the configure before view creation. + window.$events.emitPublic(elem, 'editor-markdown-cm6::pre-init', {editorViewConfig: config}); + + // Create editor view, hide original input + const ev = createView('markdown-editor', config); + (new SimpleEditorInterface(ev)).setMode('markdown', ''); elem.style.display = 'none'; return ev; } - -/** - * Get the 'meta' key dependent on the user's system. - * @returns {string} - */ -export function getMetaKey() { - // TODO - Redo, Is needed? No CodeMirror instance to use. - const mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; - return mac ? "Cmd" : "Ctrl"; -} \ No newline at end of file