/** * @copyright (C) 2023 Open Source Matters, Inc. * @license GNU General Public License version 2 or later; see LICENSE.txt */ window.tinymce.PluginManager.add('joomlaHighlighter', editor => { const setContent = html => { editor.focus(); editor.undoManager.transact(() => { editor.setContent(html); }); editor.selection.setCursorLocation(); editor.nodeChanged(); }; const getContent = () => editor.getContent({ source_view: true }); let running = false; const showSourceEditor = () => { if (running) { return; } running = true; // Create the dialog let cmEditor; const dialogConfig = { title: 'Source code', body: { type: 'panel', classes: ['joomla-highlighter-dialog'], items: [{ type: 'textarea', name: 'textarea', inputMode: 'text', maximized: true }] }, size: 'large', buttons: [{ type: 'cancel', name: 'cancel', text: 'Cancel' }, { type: 'submit', name: 'save', text: 'Save', buttonType: 'primary' }], onSubmit: dialogApi => { setContent(cmEditor.state.doc.toString()); dialogApi.close(); }, onClose: () => { cmEditor.destroy(); cmEditor = null; running = false; } }; // Import codemirror and open the dialog // eslint-disable-next-line import/no-unresolved Promise.all([import('codemirror'), import('@codemirror/view'), import('@codemirror/commands')]).then(([{ createFromTextarea }, { keymap }, { indentMore }]) => { editor.windowManager.open(dialogConfig); // Find textarea and move it to shadow DOM to isolate from TinyMCE styling const textarea = document.querySelector('.joomla-highlighter-dialog textarea'); const wrapper = textarea.parentElement; const shadow = wrapper.attachShadow({ mode: 'open' }); textarea.value = getContent(); shadow.appendChild(textarea); // Move focus out of the codemirror const escapeTabTrap = (view, event) => { event.preventDefault(); // Find a Save button const dialogEl = wrapper.closest('[role="dialog"]'); const btnEl = dialogEl.querySelector('.tox-dialog__footer [type="button"]:not(.tox-button--secondary)'); if (btnEl) { btnEl.focus(); } else { dialogEl.focus(); } }; const cmOptions = { mode: 'html', lineNumbers: true, lineWrapping: true, activeLine: true, highlightSelection: true, foldGutter: true, width: '100%', height: '100%', root: shadow, customExtensions: [ // Enable Tab trapping () => keymap.of([{ key: 'Tab', run: indentMore, shift: escapeTabTrap }])] }; const wrapperheight = wrapper.scrollHeight; createFromTextarea(textarea, cmOptions).then(cmView => { cmEditor = cmView; cmEditor.focus(); cmEditor.dom.style.maxHeight = `${wrapperheight}px`; }); }); }; editor.ui.registry.addButton('code', { icon: 'sourcecode', title: 'Source code+', tooltip: 'Source code+', onAction: showSourceEditor }); editor.ui.registry.addMenuItem('code', { icon: 'sourcecode', text: 'Source code+', onAction: showSourceEditor, context: 'tools' }); editor.addShortcut('Alt+U', 'Opens the code editor', showSourceEditor); return { getMetadata: () => ({ name: 'Source Code Editor (Joomla)', url: 'https://www.joomla.org/' }) }; });