X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/2b3726702d6dc4d0d0262e00f161ca06835cd42d..refs/pull/4317/head:/resources/js/wysiwyg/plugin-codeeditor.js diff --git a/resources/js/wysiwyg/plugin-codeeditor.js b/resources/js/wysiwyg/plugin-codeeditor.js index 12b2c25fb..2fe2ac26a 100644 --- a/resources/js/wysiwyg/plugin-codeeditor.js +++ b/resources/js/wysiwyg/plugin-codeeditor.js @@ -9,9 +9,16 @@ function elemIsCodeBlock(elem) { * @param {function(string, string)} callback (Receives (code: string,language: string) */ function showPopup(editor, code, language, callback) { - window.components.first('code-editor').open(code, language, (newCode, newLang) => { - callback(newCode, newLang) - editor.focus() + /** @var {CodeEditor} codeEditor * */ + const codeEditor = window.$components.first('code-editor'); + const bookMark = editor.selection.getBookmark(); + codeEditor.open(code, language, (newCode, newLang) => { + callback(newCode, newLang); + editor.focus(); + editor.selection.moveToBookmark(bookMark); + }, () => { + editor.focus(); + editor.selection.moveToBookmark(bookMark); }); } @@ -36,23 +43,30 @@ function defineCodeBlockCustomElement(editor) { const win = doc.defaultView; class CodeBlockElement extends win.HTMLElement { + + /** + * @type {?SimpleEditorInterface} + */ + editor = null; + constructor() { super(); this.attachShadow({mode: 'open'}); - const linkElem = document.createElement('link'); - linkElem.setAttribute('rel', 'stylesheet'); - linkElem.setAttribute('href', window.baseUrl('/dist/styles.css')); + + const stylesToCopy = document.head.querySelectorAll('link[rel="stylesheet"]:not([media="print"]),style'); + const copiedStyles = Array.from(stylesToCopy).map(styleEl => styleEl.cloneNode(true)); const cmContainer = document.createElement('div'); cmContainer.style.pointerEvents = 'none'; cmContainer.contentEditable = 'false'; cmContainer.classList.add('CodeMirrorContainer'); + cmContainer.classList.toggle('dark-mode', document.documentElement.classList.contains('dark-mode')); - this.shadowRoot.append(linkElem, cmContainer); + this.shadowRoot.append(...copiedStyles, cmContainer); } getLanguage() { - const getLanguageFromClassList = (classes) => { + const getLanguageFromClassList = classes => { const langClasses = classes.split(' ').filter(cssClass => cssClass.startsWith('language-')); return (langClasses[0] || '').replace('language-', ''); }; @@ -63,11 +77,9 @@ function defineCodeBlockCustomElement(editor) { } setContent(content, language) { - if (this.cm) { - importVersioned('code').then(Code => { - Code.setContent(this.cm, content); - Code.setMode(this.cm, language, content); - }); + if (this.editor) { + this.editor.setContent(content); + this.editor.setMode(language, content); } let pre = this.querySelector('pre'); @@ -86,40 +98,78 @@ function defineCodeBlockCustomElement(editor) { getContent() { const code = this.querySelector('code') || this.querySelector('pre'); const tempEl = document.createElement('pre'); - tempEl.innerHTML = code.innerHTML.replace().replace(//gi ,'\n').replace(/\ufeff/g, ''); + tempEl.innerHTML = code.innerHTML.replace(/\ufeff/g, ''); + + const brs = tempEl.querySelectorAll('br'); + for (const br of brs) { + br.replaceWith('\n'); + } + return tempEl.textContent; } connectedCallback() { - if (this.cm) { + const connectedTime = Date.now(); + if (this.editor) { return; } + this.cleanChildContent(); + const content = this.getContent(); + const lines = content.split('\n').length; + const height = (lines * 19.2) + 18 + 24; + this.style.height = `${height}px`; + const container = this.shadowRoot.querySelector('.CodeMirrorContainer'); - importVersioned('code').then(Code => { - this.cm = Code.wysiwygView(container, this.getContent(), this.getLanguage()); + const renderEditor = Code => { + this.editor = Code.wysiwygView(container, this.shadowRoot, content, this.getLanguage()); + setTimeout(() => { + this.style.height = null; + }, 12); + }; + + window.importVersioned('code').then(Code => { + const timeout = (Date.now() - connectedTime < 20) ? 20 : 0; + setTimeout(() => renderEditor(Code), timeout); }); } + + cleanChildContent() { + const pre = this.querySelector('pre'); + if (!pre) return; + + for (const preChild of pre.childNodes) { + if (preChild.nodeName === '#text' && preChild.textContent === '') { + preChild.remove(); + } + } + } + } win.customElements.define('code-block', CodeBlockElement); } - /** * @param {Editor} editor - * @param {String} url */ -function register(editor, url) { - - editor.ui.registry.addIcon('codeblock', '') +function register(editor) { + editor.ui.registry.addIcon('codeblock', ''); editor.ui.registry.addButton('codeeditor', { tooltip: 'Insert code block', icon: 'codeblock', onAction() { editor.execCommand('codeeditor'); - } + }, + }); + + editor.ui.registry.addButton('editcodeeditor', { + tooltip: 'Edit code block', + icon: 'edit-block', + onAction() { + editor.execCommand('codeeditor'); + }, }); editor.addCommand('codeeditor', () => { @@ -130,30 +180,28 @@ function register(editor, url) { } else { const textContent = editor.selection.getContent({format: 'text'}); showPopup(editor, textContent, '', (newCode, newLang) => { - const wrap = doc.createElement('code-block'); const pre = doc.createElement('pre'); const code = doc.createElement('code'); code.classList.add(`language-${newLang}`); code.innerText = newCode; pre.append(code); - wrap.append(pre); - editor.insertContent(wrap.outerHTML); + editor.insertContent(pre.outerHTML); }); } }); - editor.on('dblclick', event => { - let selectedNode = editor.selection.getNode(); + editor.on('dblclick', () => { + const selectedNode = editor.selection.getNode(); if (elemIsCodeBlock(selectedNode)) { showPopupForCodeBlock(editor, selectedNode); } }); editor.on('PreInit', () => { - editor.parser.addNodeFilter('pre', function(elms) { + editor.parser.addNodeFilter('pre', elms => { for (const el of elms) { - const wrapper = new tinymce.html.Node.create('code-block', { + const wrapper = window.tinymce.html.Node.create('code-block', { contenteditable: 'false', }); @@ -166,28 +214,36 @@ function register(editor, url) { } }); - editor.parser.addNodeFilter('code-block', function(elms) { + editor.parser.addNodeFilter('code-block', elms => { for (const el of elms) { - el.attr('content-editable', 'false'); + el.attr('contenteditable', 'false'); } }); - editor.serializer.addNodeFilter('code-block', function(elms) { + editor.serializer.addNodeFilter('code-block', elms => { for (const el of elms) { el.unwrap(); } }); }); + editor.ui.registry.addContextToolbar('codeeditor', { + predicate(node) { + return node.nodeName.toLowerCase() === 'code-block'; + }, + items: 'editcodeeditor', + position: 'node', + scope: 'node', + }); + editor.on('PreInit', () => { defineCodeBlockCustomElement(editor); }); } /** - * @param {WysiwygConfigOptions} options * @return {register} */ -export function getPlugin(options) { +export function getPlugin() { return register; -} \ No newline at end of file +}