diff --git a/administrator/language/en-GB/plg_editors_tinymce.ini b/administrator/language/en-GB/plg_editors_tinymce.ini index 62b3f80f25246..6c2df01727fa8 100644 --- a/administrator/language/en-GB/plg_editors_tinymce.ini +++ b/administrator/language/en-GB/plg_editors_tinymce.ini @@ -55,6 +55,7 @@ PLG_TINY_FIELD_SKIN_ADMIN_LABEL="Administrator Skin" PLG_TINY_FIELD_SKIN_INFO_DESC="Copy your new skins to: /media/editors/tinymce/skins/ui." PLG_TINY_FIELD_SKIN_INFO_LABEL="For customised skins go to: Skin Creator" PLG_TINY_FIELD_SKIN_LABEL="Site Skin" +PLG_TINY_FIELD_SOURCECODE_LABEL="Source Code Highlighting" PLG_TINY_FIELD_TEXTPATTERN_DESC="Use Markdown syntax to compose content with links, lists, and other styles." ; Do not translate the word Markdown PLG_TINY_FIELD_TEXTPATTERN_LABEL="Markdown" PLG_TINY_FIELD_TOOLBAR_MODE_LABEL="Toolbar Mode" diff --git a/build/build-modules-js/init/exemptions/tinymce.es6.js b/build/build-modules-js/init/exemptions/tinymce.es6.js index 4382d856979c4..ab74b5225ca1c 100644 --- a/build/build-modules-js/init/exemptions/tinymce.es6.js +++ b/build/build-modules-js/init/exemptions/tinymce.es6.js @@ -1,6 +1,10 @@ const { existsSync, copy, readFile, writeFile, mkdir, } = require('fs-extra'); +const CssNano = require('cssnano'); +const Postcss = require('postcss'); +const { minify } = require('terser'); + const { join } = require('path'); const { copyAllFiles } = require('../common/copy-all-files.es6.js'); @@ -65,6 +69,27 @@ module.exports.tinyMCE = async (packageName, version) => { tinyWrongMap = tinyWrongMap.replace('/*# sourceMappingURL=skin.min.css.map */', ''); await writeFile(`${RootPath}/media/vendor/tinymce/skins/ui/oxide/skin.min.css`, tinyWrongMap, { encoding: 'utf8', mode: 0o644 }); + /* Create the Highlighter plugin */ + // Get the css + let cssContent = await readFile('build/media_source/plg_editors_tinymce/js/plugins/highlighter/source.css', { encoding: 'utf8' }); + cssContent = await Postcss([CssNano()]).process(cssContent, { from: undefined }); + // Get the JS + let jsContent = await readFile('build/media_source/plg_editors_tinymce/js/plugins/highlighter/source.es5.js', { encoding: 'utf8' }); + jsContent = await minify(jsContent, { sourceMap: false, format: { comments: false } }); + // Write the HTML file + const htmlContent = ` + +
+ + + + + + +`; + + await writeFile('media/plg_editors_tinymce/js/plugins/highlighter/source.html', htmlContent, { encoding: 'utf8', mode: 0o644 }); + // Restore our code on the vendor folders await copy(join(RootPath, 'build/media_source/vendor/tinymce/templates'), join(RootPath, 'media/vendor/tinymce/templates'), { preserveTimestamps: true }); }; diff --git a/build/media_source/plg_editors_tinymce/js/plugins/highlighter/plugin.es5.js b/build/media_source/plg_editors_tinymce/js/plugins/highlighter/plugin.es5.js new file mode 100644 index 0000000000000..910370e37e2d7 --- /dev/null +++ b/build/media_source/plg_editors_tinymce/js/plugins/highlighter/plugin.es5.js @@ -0,0 +1,87 @@ +/** + * plugin.js + * + * Original code by Arjan Haverkamp + * Copyright 2013-2015 Arjan Haverkamp (arjan@webgear.nl) + * + * Adapted for use in Joomla by Dimitrios Grammatikogiannis + */ + /* eslint-disable no-undef */ + tinymce.PluginManager.add('highlightPlus', function(editor, url) { + function showSourceEditor() { + editor.focus(); + editor.selection.collapse(true); + + if (!editor.settings.codemirror) editor.settings.codemirror = {}; + + // Insert caret marker + if (editor.settings.codemirror && editor.settings.codemirror.saveCursorPosition) { + editor.selection.setContent(''); + } + + let codemirrorWidth = 800; + if (editor.settings.codemirror.width) { + codemirrorWidth = editor.settings.codemirror.width; + } + + let codemirrorHeight = 550; + if (editor.settings.codemirror.height) { + codemirrorHeight = editor.settings.codemirror.height; + } + + const buttonsConfig = [ + { + type: 'custom', + text: 'Ok', + name: 'codemirrorOk', + primary: true + }, + { + type: 'cancel', + text: 'Cancel', + name: 'codemirrorCancel' + } + ] + + const config = { + title: 'Source code', + url: url + '/source.html', + width: codemirrorWidth, + height: codemirrorHeight, + resizable: true, + maximizable: true, + fullScreen: editor.settings.codemirror.fullscreen, + saveCursorPosition: false, + buttons: buttonsConfig + } + + config.onAction = function (dialogApi, actionData) { + if (actionData.name === 'codemirrorOk') { + const doc = document.querySelectorAll('.tox-dialog__body-iframe iframe')[0]; + doc.contentWindow.tinymceHighlighterSubmit(); + editor.undoManager.add(); + win.close(); + } + } + + const win = editor.windowManager.openUrl(config); + + if (editor.settings.codemirror.fullscreen) { + win.fullscreen(true); + } + } + + 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' + }); + }); diff --git a/build/media_source/plg_editors_tinymce/js/plugins/highlighter/source.css b/build/media_source/plg_editors_tinymce/js/plugins/highlighter/source.css new file mode 100644 index 0000000000000..084efeda12509 --- /dev/null +++ b/build/media_source/plg_editors_tinymce/js/plugins/highlighter/source.css @@ -0,0 +1,17 @@ +html,body { height:100%; } +body { + margin: 0; +} +.CodeMirror { + height: 100%; + font-size: 12px; + line-height: 18px; +} +.CodeMirror-activeline-background { + background: #e8f2ff !important; +} +.cm-trailingspace { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==); + background-position: bottom left; + background-repeat: repeat-x; +} diff --git a/build/media_source/plg_editors_tinymce/js/plugins/highlighter/source.es5.js b/build/media_source/plg_editors_tinymce/js/plugins/highlighter/source.es5.js new file mode 100644 index 0000000000000..29fa2b4b07fbc --- /dev/null +++ b/build/media_source/plg_editors_tinymce/js/plugins/highlighter/source.es5.js @@ -0,0 +1,279 @@ +/** + * source.js + * + * Original code by Arjan Haverkamp + * Copyright 2013-2015 Arjan Haverkamp (arjan@webgear.nl) + * + * Adapted for use in Joomla by Dimitrios Grammatikogiannis + */ + +// CodeMirror settings +const CMsettings = { + path: '../../../../vendor/codemirror', + indentOnInit: true, + config: { + mode: 'htmlmixed', + theme: 'default', + lineNumbers: true, + lineWrapping: true, + indentUnit: 2, + tabSize: 2, + indentWithTabs: true, + matchBrackets: true, + saveCursorPosition: true, + styleActiveLine: true, + }, + jsFiles: [// Default JS files + 'lib/codemirror.min.js', + 'addon/edit/matchbrackets.min.js', + 'mode/xml/xml.min.js', + 'mode/javascript/javascript.min.js', + 'mode/css/css.min.js', + 'mode/htmlmixed/htmlmixed.min.js', + 'addon/dialog/dialog.min.js', + 'addon/search/searchcursor.min.js', + 'addon/search/search.min.js', + 'addon/selection/active-line.min.js', + ], + cssFiles: [// Default CSS files + 'lib/codemirror.css', + 'addon/dialog/dialog.css', + ], +}; + +// Global vars: +let tinymce; // Reference to TinyMCE +let editor; // Reference to TinyMCE editor +let codemirror; // CodeMirror instance +let chr = 0; // Unused utf-8 character, placeholder for cursor +const isMac = /macintosh|mac os/i.test(navigator.userAgent); + +/** + * Find the depth level + */ +const findDepth = (haystack, needle) => { + const idx = haystack.indexOf(needle); + let depth = 0; + for (let x = idx -1; x >= 0; x--) { + switch(haystack.charAt(x)) { + case '<': depth--; break; + case '>': depth++; break; + case '&': depth++; break; + } + } + return depth; +} + +/** + * This function is called by plugin.js, when user clicks 'Ok' button + */ +window.tinymceHighlighterSubmit = () => { + const cc = ''; + const isDirty = codemirror.isDirty; + const doc = codemirror.doc; + + if (doc.somethingSelected()) { + // Clear selection: + doc.setCursor(doc.getCursor()); + } + + // Insert cursor placeholder () + doc.replaceSelection(cc); + + var pos = codemirror.getCursor(), + curLineHTML = doc.getLine(pos.line); + + if (findDepth(curLineHTML, cc) !== 0) { + // Cursor is inside a