diff --git a/administrator/language/en-GB/plg_editors_codemirror.ini b/administrator/language/en-GB/plg_editors_codemirror.ini index 8b9d426177497..d3028b388d99f 100644 --- a/administrator/language/en-GB/plg_editors_codemirror.ini +++ b/administrator/language/en-GB/plg_editors_codemirror.ini @@ -3,42 +3,49 @@ ; License GNU General Public License version 2 or later; see LICENSE.txt ; Note : All ini files need to be saved as UTF-8 -PLG_CODEMIRROR_FIELD_ACTIVELINE_COLOR_LABEL="Active Line Colour" -PLG_CODEMIRROR_FIELD_ACTIVELINE_LABEL="Highlight Active Line" PLG_CODEMIRROR_FIELD_AUTOCLOSEBRACKET_LABEL="Bracket Completion" -PLG_CODEMIRROR_FIELD_AUTOCLOSETAGS_LABEL="Tag Completion" PLG_CODEMIRROR_FIELD_CODEFOLDING_LABEL="Code Folding" -PLG_CODEMIRROR_FIELD_FONT_FAMILY_LABEL="Font" -PLG_CODEMIRROR_FIELD_FONT_SIZE_LABEL="Font Size (px)" +PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_LABEL="Custom Extensions" +PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_METHOD_DESCR="Method name, provided by module for extension initialisation. Or multiple, separated by comma. Eg: bracketMatching (from module @codemirror/language)." +PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_METHOD_LABEL="Initialisation Method(s)" +PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_MODULE_DESCR="Relative path to the module file, eg: media/my-assets/js/my-codemirror-module.js. Or module name, eg: @codemirror/language." +PLG_CODEMIRROR_FIELD_CUSTOM_EXTENSIONS_MODULE_LABEL="Module File or Module Name" PLG_CODEMIRROR_FIELD_FULLSCREEN_LABEL="Toggle Fullscreen" PLG_CODEMIRROR_FIELD_FULLSCREEN_MOD_LABEL="Toggle Fullscreen Modifier" -PLG_CODEMIRROR_FIELD_HIGHLIGHT_MATCH_COLOR_LABEL="Matching Tag Colour" PLG_CODEMIRROR_FIELD_KEYMAP_DESC="Make CodeMirror work like other popular editors." PLG_CODEMIRROR_FIELD_KEYMAP_EMACS="Emacs" PLG_CODEMIRROR_FIELD_KEYMAP_LABEL="Key Map" +PLG_CODEMIRROR_FIELD_LINENUMBERS_LABEL="Line Numbers" +PLG_CODEMIRROR_FIELD_LINEWRAPPING_LABEL="Line Wrapping" +PLG_CODEMIRROR_FIELD_SELECTIONMATCHES_LABEL="Highlight Selection Matches" +PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_ALT="Alt" +PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_CMD="Command" +PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_CTRL="Control" +PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_SHIFT="Shift" +PLG_CODEMIRROR_FIELDSET_TOOLBAR_OPTIONS_LABEL="Toolbar Options" +PLG_CODEMIRROR_TOGGLE_FULL_SCREEN="Press %s to toggle Full Screen editing." +PLG_CODEMIRROR_XML_DESCRIPTION="This plugin loads the CodeMirror editor." +PLG_EDITORS_CODEMIRROR="Editor - CodeMirror" + +; Deprecated, will be removed with 6.0 +PLG_CODEMIRROR_FIELDSET_APPEARANCE_OPTIONS_LABEL="Appearance Options" +PLG_CODEMIRROR_FIELD_ACTIVELINE_COLOR_LABEL="Active Line Colour" +PLG_CODEMIRROR_FIELD_ACTIVELINE_LABEL="Highlight Active Line" +PLG_CODEMIRROR_FIELD_AUTOCLOSETAGS_LABEL="Tag Completion" +PLG_CODEMIRROR_FIELD_FONT_FAMILY_LABEL="Font" +PLG_CODEMIRROR_FIELD_FONT_SIZE_LABEL="Font Size (px)" +PLG_CODEMIRROR_FIELD_HIGHLIGHT_MATCH_COLOR_LABEL="Matching Tag Colour" PLG_CODEMIRROR_FIELD_KEYMAP_SUBLIME="Sublime Text" PLG_CODEMIRROR_FIELD_KEYMAP_VIM="Vim" PLG_CODEMIRROR_FIELD_LINE_HEIGHT_LABEL="Line Height (em)" -PLG_CODEMIRROR_FIELD_LINENUMBERS_LABEL="Line Numbers" -PLG_CODEMIRROR_FIELD_LINEWRAPPING_LABEL="Line Wrapping" PLG_CODEMIRROR_FIELD_MARKERGUTTER_LABEL="Gutters" PLG_CODEMIRROR_FIELD_MATCHBRACKETS_LABEL="Match Brackets" PLG_CODEMIRROR_FIELD_MATCHTAGS_LABEL="Match Tags" PLG_CODEMIRROR_FIELD_PREVIEW_LABEL="Preview" -PLG_CODEMIRROR_FIELD_SELECTIONMATCHES_LABEL="Highlight Selection Matches" PLG_CODEMIRROR_FIELD_THEME_LABEL="Theme" PLG_CODEMIRROR_FIELD_VALUE_FONT_FAMILY_DEFAULT="Browser Default" -PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_ALT="Alt" -PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_CMD="Command" -PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_CTRL="Control" -PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_SHIFT="Shift" PLG_CODEMIRROR_FIELD_VALUE_SCROLLBARSTYLE_DEFAULT="Default" PLG_CODEMIRROR_FIELD_VALUE_SCROLLBARSTYLE_LABEL="Scrollbar Style" PLG_CODEMIRROR_FIELD_VALUE_SCROLLBARSTYLE_OVERLAY="Overlay" PLG_CODEMIRROR_FIELD_VALUE_SCROLLBARSTYLE_SIMPLE="Simple" PLG_CODEMIRROR_FIELD_VIM_KEYBINDING_LABEL="Vim Keybinding" -PLG_CODEMIRROR_FIELDSET_APPEARANCE_OPTIONS_LABEL="Appearance Options" -PLG_CODEMIRROR_FIELDSET_TOOLBAR_OPTIONS_LABEL="Toolbar Options" -PLG_CODEMIRROR_TOGGLE_FULL_SCREEN="Press %s to toggle Full Screen editing." -PLG_CODEMIRROR_XML_DESCRIPTION="This plugin loads the CodeMirror editor." -PLG_EDITORS_CODEMIRROR="Editor - CodeMirror" diff --git a/build/build-modules-js/init/common/resolve-package.es6.js b/build/build-modules-js/init/common/resolve-package.es6.js new file mode 100644 index 0000000000000..c69e25e5cef3e --- /dev/null +++ b/build/build-modules-js/init/common/resolve-package.es6.js @@ -0,0 +1,49 @@ +const { existsSync, readdirSync } = require('fs-extra'); + +/** + * Find full path for package file. + * Replacement for require.resolve(), as it is broken for packages with "exports" property. + * + * @param {string} relativePath Relative path to the file to resolve, in format packageName/file-name.js + * @returns {string|boolean} + */ +module.exports.resolvePackageFile = (relativePath) => { + for (let i = 0, l = module.paths.length; i < l; i += 1) { + const path = module.paths[i]; + const fullPath = `${path}/${relativePath}`; + if (existsSync(fullPath)) { + return fullPath; + } + } + + return false; +}; + +/** + * Find a list of modules under given scope, + * eg: @foobar will look for all submodules @foobar/foo, @foobar/bar + * + * @param scope + * @returns {[]} + */ +module.exports.getPackagesUnderScope = (scope) => { + const cmModules = []; + + // Get the scope roots + const roots = []; + module.paths.forEach((path) => { + const fullPath = `${path}/${scope}`; + if (existsSync(fullPath)) { + roots.push(fullPath); + } + }); + + // List of modules + roots.forEach((rootPath) => { + readdirSync(rootPath).forEach((subModule) => { + cmModules.push(`${scope}/${subModule}`); + }); + }); + + return cmModules; +}; diff --git a/build/build-modules-js/init/exemptions/codemirror.es6.js b/build/build-modules-js/init/exemptions/codemirror.es6.js deleted file mode 100644 index 1040c51a51a64..0000000000000 --- a/build/build-modules-js/init/exemptions/codemirror.es6.js +++ /dev/null @@ -1,72 +0,0 @@ -const { - existsSync, readFile, writeFile, mkdir, -} = require('fs-extra'); -const { join } = require('path'); - -const { concatFiles } = require('../common/concat-files.es6.js'); -const { copyAllFiles } = require('../common/copy-all-files.es6.js'); - -const RootPath = process.cwd(); -const xmlVersionStr = /()(.+)(<\/version>)/; - -/** - * Codemirror needs special treatment - */ -module.exports.codeMirror = async (packageName, version) => { - const itemvendorPath = join(RootPath, `media/vendor/${packageName}`); - if (!await existsSync(itemvendorPath)) { - await mkdir(itemvendorPath, { recursive: true, mode: 0o755 }); - await mkdir(join(itemvendorPath, 'addon'), { mode: 0o755 }); - await mkdir(join(itemvendorPath, 'lib'), { mode: 0o755 }); - await mkdir(join(itemvendorPath, 'mode'), { mode: 0o755 }); - await mkdir(join(itemvendorPath, 'keymap'), { mode: 0o755 }); - await mkdir(join(itemvendorPath, 'theme'), { mode: 0o755 }); - } - - await copyAllFiles('addon', 'codemirror', 'addon'); - await copyAllFiles('lib', 'codemirror', 'lib'); - await copyAllFiles('mode', 'codemirror', 'mode'); - await copyAllFiles('keymap', 'codemirror', 'keymap'); - await copyAllFiles('theme', 'codemirror', 'theme'); - - await concatFiles( - [ - 'media/vendor/codemirror/addon/display/fullscreen.js', - 'media/vendor/codemirror/addon/display/panel.js', - 'media/vendor/codemirror/addon/edit/closebrackets.js', - 'media/vendor/codemirror/addon/edit/closetag.js', - 'media/vendor/codemirror/addon/edit/matchbrackets.js', - 'media/vendor/codemirror/addon/edit/matchtags.js', - 'media/vendor/codemirror/addon/fold/brace-fold.js', - 'media/vendor/codemirror/addon/fold/foldcode.js', - 'media/vendor/codemirror/addon/fold/foldgutter.js', - 'media/vendor/codemirror/addon/fold/xml-fold.js', - 'media/vendor/codemirror/addon/mode/loadmode.js', - 'media/vendor/codemirror/addon/mode/multiplex.js', - 'media/vendor/codemirror/addon/scroll/annotatescrollbar.js', - 'media/vendor/codemirror/addon/scroll/simplescrollbars.js', - 'media/vendor/codemirror/addon/search/match-highlighter.js', - 'media/vendor/codemirror/addon/search/matchesonscrollbar.js', - 'media/vendor/codemirror/addon/search/search.js', - 'media/vendor/codemirror/addon/search/searchcursor.js', - 'media/vendor/codemirror/addon/selection/active-line.js', - 'media/vendor/codemirror/mode/meta.js', - ], - 'media/vendor/codemirror/lib/addons.js', - ); - - await concatFiles( - [ - 'media/vendor/codemirror/addon/display/fullscreen.css', - 'media/vendor/codemirror/addon/fold/foldgutter.css', - 'media/vendor/codemirror/addon/scroll/simplescrollbars.css', - 'media/vendor/codemirror/addon/search/matchesonscrollbar.css', - ], - 'media/vendor/codemirror/lib/addons.css', - ); - - // Update the XML file for Codemirror - let codemirrorXml = await readFile(`${RootPath}/plugins/editors/codemirror/codemirror.xml`, { encoding: 'utf8' }); - codemirrorXml = codemirrorXml.replace(xmlVersionStr, `$1${version}$3`); - await writeFile(`${RootPath}/plugins/editors/codemirror/codemirror.xml`, codemirrorXml, { encoding: 'utf8', mode: 0o644 }); -}; diff --git a/build/build-modules-js/init/localise-packages.es6.js b/build/build-modules-js/init/localise-packages.es6.js index 6f99e50ed4acc..e72229a3a52e1 100644 --- a/build/build-modules-js/init/localise-packages.es6.js +++ b/build/build-modules-js/init/localise-packages.es6.js @@ -2,30 +2,11 @@ const { existsSync, copy, writeFile, mkdir, mkdirs, ensureDir, } = require('fs-extra'); const { dirname, join } = require('path'); -const { codeMirror } = require('./exemptions/codemirror.es6.js'); const { tinyMCE } = require('./exemptions/tinymce.es6.js'); +const { resolvePackageFile } = require('./common/resolve-package.es6.js'); const RootPath = process.cwd(); -/** - * Find full path for package file. - * Replacement for require.resolve(), as it is broken for packages with "exports" property. - * - * @param {string} relativePath Relative path to the file to resolve, in format packageName/file-name.js - * @returns {string|boolean} - */ -const resolvePackageFile = (relativePath) => { - for (let i = 0, l = module.paths.length; i < l; i += 1) { - const path = module.paths[i]; - const fullPath = `${path}/${relativePath}`; - if (existsSync(fullPath)) { - return fullPath; - } - } - - return false; -}; - /** * * @param {object} files the object of files map, eg {"src.js": "js/src.js"} @@ -65,9 +46,7 @@ const resolvePackage = async (vendor, packageName, mediaVendorPath, options, reg const promises = []; - if (packageName === 'codemirror') { - promises.push(codeMirror(packageName, moduleOptions.version)); - } else if (packageName === 'tinymce') { + if (packageName === 'tinymce') { promises.push(tinyMCE(packageName, moduleOptions.version)); } else { await mkdirs(join(mediaVendorPath, vendorName)); diff --git a/build/build-modules-js/javascript/build-codemirror.es6.js b/build/build-modules-js/javascript/build-codemirror.es6.js new file mode 100644 index 0000000000000..dc1d72466a25d --- /dev/null +++ b/build/build-modules-js/javascript/build-codemirror.es6.js @@ -0,0 +1,138 @@ +/** + * Build codemirror modules + */ +/* eslint-disable import/no-extraneous-dependencies, global-require, import/no-dynamic-require */ + +const { readFileSync, writeFile } = require('fs-extra'); +const cliProgress = require('cli-progress'); +const rollup = require('rollup'); +const { nodeResolve } = require('@rollup/plugin-node-resolve'); +const replace = require('@rollup/plugin-replace'); +const { minify } = require('terser'); +const { resolvePackageFile, getPackagesUnderScope } = require('../init/common/resolve-package.es6.js'); + +// Build the module +const buildModule = async (module, externalModules, destFile) => { + const build = await rollup.rollup({ + input: module, + external: externalModules || [], + plugins: [ + nodeResolve(), + replace({ + preventAssignment: true, + 'process.env.NODE_ENV': '"production"', + }), + ], + }); + + await build.write({ + format: 'es', + sourcemap: false, + file: destFile, + }); + await build.close(); +}; + +// Minify a js file +const createMinified = async (filePath) => { + const destFile = filePath.replace('.js', '.min.js'); + // Read source + const src = readFileSync(filePath, { encoding: 'utf8' }); + // Minify + const min = await minify(src, { sourceMap: false, format: { comments: false } }); + // Save result + await writeFile(destFile, min.code, { encoding: 'utf8', mode: 0o644 }); +}; + +// Update joomla.asset.json for codemirror +const updateAssetRegistry = async (modules, externalModules) => { + const srcPath = 'build/media_source/plg_editors_codemirror/joomla.asset.json'; + const destPath = 'media/plg_editors_codemirror/joomla.asset.json'; + + // Get base JSON and update + const registry = JSON.parse(readFileSync(srcPath, { encoding: 'utf8' })); + + // Add dependencies to base codemirror asset + registry.assets.forEach((asset) => { + if (asset.name === 'codemirror' && asset.type === 'script') { + asset.dependencies = externalModules; + } + }); + + // Create asset for each module + modules.forEach((module) => { + const packageName = module.package; + const modulePathJson = resolvePackageFile(`${packageName}/package.json`); + const moduleOptions = require(modulePathJson); + const asset = { + type: 'script', + name: module.package, + uri: module.uri.replace('.js', '.min.js'), + importmap: true, + version: moduleOptions.version, + }; + + registry.assets.push(asset); + }); + + // Write assets registry + await writeFile( + destPath, + JSON.stringify(registry, null, 2), + { encoding: 'utf8', mode: 0o644 }, + ); +}; + +module.exports.compileCodemirror = async () => { + // eslint-disable-next-line no-console + console.log('Building Codemirror Components...'); + + const cmModules = getPackagesUnderScope('@codemirror'); + const lModules = getPackagesUnderScope('@lezer'); + const externalModules = [...cmModules, ...lModules]; + const destBasePath = 'media/vendor/codemirror/js'; + const assets = []; + const tasks = []; + + const progressBar = new cliProgress.SingleBar({ + stopOnComplete: true, + format: '{bar} {percentage}% | {value}/{total} files done', + }, cliProgress.Presets.shades_classic); + const totalSteps = (cmModules.length + lModules.length) * 2; + progressBar.start(totalSteps, 0); + + // Prepare @codemirror modules + cmModules.forEach((module) => { + const destFile = `${module.replace('@codemirror/', 'codemirror-')}.js`; + const destPath = `${destBasePath}/${destFile}`; + assets.push({ package: module, uri: destPath }); + + const task = buildModule(module, externalModules, destPath).then(() => { + progressBar.increment(); + return createMinified(destPath).then(() => { + progressBar.increment(); + }); + }); + tasks.push(task); + }); + + // Prepare @lezer modules which @codemirror depends on + lModules.forEach((module) => { + const destFile = `${module.replace('@lezer/', 'lezer-')}.js`; + const destPath = `${destBasePath}/${destFile}`; + assets.push({ package: module, uri: destPath }); + + const task2 = buildModule(module, externalModules, destPath).then(() => { + progressBar.increment(); + return createMinified(destPath).then(() => { + progressBar.increment(); + }); + }); + tasks.push(task2); + }); + + return Promise.all(tasks).then(() => { + progressBar.stop(); + return updateAssetRegistry(assets, externalModules); + }); +}; diff --git a/build/build-modules-js/javascript/compile-to-es2017.es6.js b/build/build-modules-js/javascript/compile-to-es2017.es6.js index ca28bb52cccaa..3549141e57888 100644 --- a/build/build-modules-js/javascript/compile-to-es2017.es6.js +++ b/build/build-modules-js/javascript/compile-to-es2017.es6.js @@ -1,3 +1,5 @@ +/* eslint-disable import/no-extraneous-dependencies, global-require, import/no-dynamic-require */ + const { access, writeFile } = require('fs').promises; const { constants } = require('fs'); const Autoprefixer = require('autoprefixer'); @@ -10,6 +12,7 @@ const { babel } = require('@rollup/plugin-babel'); const Postcss = require('postcss'); const { renderSync } = require('sass-embedded'); const { minifyJsCode } = require('./minify.es6.js'); +const { getPackagesUnderScope } = require('../init/common/resolve-package.es6.js'); const getWcMinifiedCss = async (file) => { let scssFileExists = false; @@ -42,6 +45,30 @@ const getWcMinifiedCss = async (file) => { return ''; }; +// List of external modules that should not be resolved by rollup +const externalModules = []; +const collectExternals = () => { + if (externalModules.length) { + return; + } + + // Joomla modules + externalModules.push( + 'cropper-module', + 'codemirror', + ); + + // Codemirror modules + const cmModules = getPackagesUnderScope('@codemirror'); + if (cmModules) { + externalModules.push(...cmModules); + } + const lezerModules = getPackagesUnderScope('@lezer'); + if (lezerModules) { + externalModules.push(...lezerModules); + } +}; + /** * Compiles es6 files to es5. * @@ -50,12 +77,14 @@ const getWcMinifiedCss = async (file) => { module.exports.handleESMFile = async (file) => { const newPath = file.replace(/\.w-c\.es6\.js$/, '').replace(/\.es6\.js$/, '').replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`); const minifiedCss = await getWcMinifiedCss(file); + + // Make sure externals are collected + collectExternals(); + const bundle = await rollup.rollup({ input: resolve(file), plugins: [ - nodeResolve({ - preferBuiltins: false, - }), + nodeResolve({ preferBuiltins: false }), replace({ preventAssignment: true, CSS_CONTENTS_PLACEHOLDER: minifiedCss, @@ -87,7 +116,7 @@ module.exports.handleESMFile = async (file) => { ], }), ], - external: [], + external: externalModules, }); bundle.write({ diff --git a/build/build-modules-js/settings.json b/build/build-modules-js/settings.json index abb5dbe5cbb76..a7fdae9ef780f 100644 --- a/build/build-modules-js/settings.json +++ b/build/build-modules-js/settings.json @@ -617,7 +617,7 @@ ], "licenseFilename": "LICENSE.md" }, - "codemirror": { + "@codemirror/view": { "name": "codemirror", "licenseFilename": "LICENSE" }, diff --git a/build/build.js b/build/build.js index c258932384a3d..637514ef8f28b 100644 --- a/build/build.js +++ b/build/build.js @@ -34,6 +34,7 @@ const { mediaManager, watchMediaManager } = require('./build-modules-js/javascri const { compressFiles } = require('./build-modules-js/compress.es6.js'); const { versioning } = require('./build-modules-js/versioning.es6.js'); const { Timer } = require('./build-modules-js/utils/timer.es6.js'); +const { compileCodemirror } = require('./build-modules-js/javascript/build-codemirror.es6.js'); // The settings const options = require('../package.json'); @@ -68,6 +69,7 @@ Program .option('--compile-js, --compile-js path', 'Handles ES6, ES5 and web component scripts') .option('--compile-css, --compile-css path', 'Compiles all the scss files to css') .option('--compile-bs', 'Compiles all the Bootstrap component scripts.') + .option('--compile-codemirror', 'Compiles all the codemirror modules.') .option('--watch', 'Watch file changes and re-compile (ATM only works for the js in the media_source).') .option('--com-media', 'Compile the Media Manager client side App.') .option('--watch-com-media', 'Watch and Compile the Media Manager client side App.') @@ -125,6 +127,11 @@ if (cliOptions.compileBs) { bootstrapJs(); } +// Compile codemirror +if (cliOptions.compileCodemirror) { + compileCodemirror(); +} + // Gzip js/css files if (cliOptions.gzip) { compressFiles(); @@ -161,6 +168,7 @@ if (cliOptions.prepare) { .then(() => scripts(options, Program.args[0])) .then(() => mediaManager()) .then(() => bootstrapJs()) + .then(() => compileCodemirror()) .then(() => bench.stop('Build')) .then(() => { process.exit(0); }) .catch((err) => { diff --git a/build/media_source/com_templates/js/admin-template-toggle-switch.es6.js b/build/media_source/com_templates/js/admin-template-toggle-switch.es6.js index 564f8080abbe3..d1cde8cab63ed 100644 --- a/build/media_source/com_templates/js/admin-template-toggle-switch.es6.js +++ b/build/media_source/com_templates/js/admin-template-toggle-switch.es6.js @@ -61,10 +61,6 @@ fieldset.classList.add('options-grid-form-half'); } - if (Joomla.editors.instances.jform_core) { - Joomla.editors.instances.jform_core.refresh(); - } - if (typeof Storage !== 'undefined') { localStorage.setItem('coreSwitchState', 'checked'); } diff --git a/build/media_source/plg_editors_codemirror/css/codemirror.css b/build/media_source/plg_editors_codemirror/css/codemirror.css index 80be033806a53..0b0c2b53db1d1 100644 --- a/build/media_source/plg_editors_codemirror/css/codemirror.css +++ b/build/media_source/plg_editors_codemirror/css/codemirror.css @@ -3,42 +3,32 @@ * @license GNU General Public License version 2 or later; see LICENSE.txt */ -.CodeMirror { - margin-bottom: 15px; +joomla-editor-codemirror { + display: block; + background-color: #fff; +} +.cm-editor { + margin-bottom: 16px; border: 1px solid #ccc; } - -/* In order to hide the Joomla menu */ -.CodeMirror-fullscreen { - z-index: 1040; +.cm-editor .cm-scroller { + min-height: 100px; + overflow: auto; } -/* Make the fold marker a little more visible/nice */ -.CodeMirror-foldmarker { - background: rgb(255,128,0); - background: rgba(255,128,0,.5); - box-shadow: inset 0 0 2px rgba(255,255,255,.5); - font-family: serif; - font-size: 90%; - border-radius: 1em; - padding: 0 1em; - vertical-align: middle; - color: white; - text-shadow: none; -} - -.CodeMirror-foldgutter, -.CodeMirror-markergutter { - width: 1.2em; - text-align: center; -} -.CodeMirror-markergutter { - cursor: pointer; +/* Full screen mode */ +joomla-editor-codemirror.fullscreen { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1038; } -.CodeMirror-markergutter-mark { - cursor: pointer; - text-align: center; -} -.CodeMirror-markergutter-mark:after { - content: "\25CF"; +joomla-editor-codemirror.fullscreen .cm-editor { + width: auto !important; + height: 90vh !important; } + + + diff --git a/build/media_source/plg_editors_codemirror/joomla.asset.json b/build/media_source/plg_editors_codemirror/joomla.asset.json new file mode 100644 index 0000000000000..141661091216f --- /dev/null +++ b/build/media_source/plg_editors_codemirror/joomla.asset.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json", + "name": "plg_editors_codemirror", + "version": "5.0.0", + "description": "Joomla CMS", + "license": "GPL-2.0-or-later", + "assets": [ + { + "name": "plg_editors_codemirror", + "type": "style", + "uri": "plg_editors_codemirror/codemirror.css" + }, + { + "name": "codemirror", + "type": "script", + "uri": "plg_editors_codemirror/codemirror.min.js", + "importmap": true + }, + { + "name": "webcomponent.editor-codemirror", + "type": "script", + "uri": "plg_editors_codemirror/joomla-editor-codemirror.min.js", + "dependencies": [ + "core", + "codemirror", + "wcpolyfill" + ], + "attributes": { + "type": "module" + } + } + ] +} diff --git a/build/media_source/plg_editors_codemirror/js/codemirror.es6.js b/build/media_source/plg_editors_codemirror/js/codemirror.es6.js new file mode 100644 index 0000000000000..78332f10b9173 --- /dev/null +++ b/build/media_source/plg_editors_codemirror/js/codemirror.es6.js @@ -0,0 +1,169 @@ +/** + * @copyright (C) 2023 Open Source Matters, Inc. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +import { + EditorView, + lineNumbers, + highlightActiveLineGutter, + highlightSpecialChars, + drawSelection, + highlightActiveLine, + keymap, +} from '@codemirror/view'; +import { EditorState, Compartment } from '@codemirror/state'; +import { + foldGutter, + syntaxHighlighting, + defaultHighlightStyle, +} from '@codemirror/language'; +import { + history, defaultKeymap, historyKeymap, emacsStyleKeymap, +} from '@codemirror/commands'; +import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'; +import { closeBrackets } from '@codemirror/autocomplete'; + +const minimalSetup = (() => [ + highlightSpecialChars(), + history(), + drawSelection(), + syntaxHighlighting(defaultHighlightStyle, { fallback: true }), +]); + +/** + * Configure and return list of extensions for given options + * + * @param {Object} options + * @returns {Promise<[]>} + */ +const optionsToExtensions = async (options) => { + const extensions = []; + const q = []; + + // Load the language for syntax mode + if (options.mode) { + const { mode } = options; + const modeOptions = options[mode] || {}; + + // eslint-disable-next-line consistent-return + q.push(import(`@codemirror/lang-${options.mode}`).then((modeMod) => { + // For html and php we need to configure selfClosingTags, to make code folding work correctly with + if (mode === 'php') { + return import('@codemirror/lang-html').then(({ html }) => { + const htmlOptions = options.html || { selfClosingTags: true }; + extensions.push(modeMod.php({ baseLanguage: html(htmlOptions).language })); + }); + } + if (mode === 'html') { + modeOptions.selfClosingTags = true; + } + extensions.push(modeMod[options.mode](modeOptions)); + }).catch((error) => { + // eslint-disable-next-line no-console + console.error(`Cannot creat an extension for "${options.mode}" syntax mode.`, error); + })); + } + + if (options.lineNumbers) { + extensions.push(lineNumbers()); + } + + if (options.lineWrapping) { + extensions.push(EditorView.lineWrapping); + } + + if (options.activeLine) { + extensions.push(highlightActiveLineGutter(), highlightActiveLine()); + } + + if (options.highlightSelection) { + extensions.push(highlightSelectionMatches()); + } + + if (options.autoCloseBrackets) { + extensions.push(closeBrackets()); + } + + if (options.foldGutter) { + extensions.push(foldGutter()); + } + + // Keymaps + switch (options.keyMap) { + case 'emacs': + extensions.push(keymap.of([...emacsStyleKeymap, ...historyKeymap])); + break; + default: + extensions.push(keymap.of([...defaultKeymap, ...searchKeymap, ...historyKeymap])); + break; + } + + // Configurable read only + const readOnly = new Compartment(); + // Set a custom name so later on we can retrieve this Compartment from view.state.config.compartments + readOnly.$j_name = 'readOnly'; + extensions.push(readOnly.of(EditorState.readOnly.of(!!options.readOnly))); + + // Check for custom extensions, + // in format [['module1 name or URL', ['init method2']], ['module2 name or URL', ['init method2']], () => ] + if (options.customExtensions && options.customExtensions.length) { + options.customExtensions.forEach((extInfo) => { + // Check whether we have a callable + if (extInfo instanceof Function) { + extensions.push(extInfo()); + return; + } + // Import the module + const [module, methods] = extInfo; + q.push(import(module).then((modObject) => { + // Call each method + methods.forEach((method) => { + extensions.push(modObject[method]()); + }); + })); + }); + } + + return Promise.all(q).then(() => extensions); +}; + +/** + * Create an editor instance for given textarea + * + * @param {HTMLTextAreaElement} textarea + * @param {Object} options + * @returns {Promise} + */ +async function createFromTextarea(textarea, options) { + const extensions = [minimalSetup(), await optionsToExtensions(options)]; + const view = new EditorView({ + doc: textarea.value, + extensions, + }); + textarea.parentNode.insertBefore(view.dom, textarea); + textarea.style.display = 'none'; + if (textarea.form) { + textarea.form.addEventListener('submit', () => { + textarea.value = view.state.doc.toString(); + }); + } + + // Set up sizing + if (options.width) { + view.dom.style.width = options.width; + } + if (options.height) { + view.dom.style.height = options.height; + } + + return view; +} + +export { + minimalSetup, + createFromTextarea, + optionsToExtensions, + EditorState, + EditorView, + keymap, +}; diff --git a/build/media_source/plg_editors_codemirror/js/joomla-editor-codemirror.w-c.es6.js b/build/media_source/plg_editors_codemirror/js/joomla-editor-codemirror.w-c.es6.js index 5ca43dc8b9dc4..a6fc0fa6d6b09 100644 --- a/build/media_source/plg_editors_codemirror/js/joomla-editor-codemirror.w-c.es6.js +++ b/build/media_source/plg_editors_codemirror/js/joomla-editor-codemirror.w-c.es6.js @@ -1,153 +1,103 @@ +/** + * @copyright (C) 2023 Open Source Matters, Inc. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +// eslint-disable-next-line import/no-unresolved +import { createFromTextarea, EditorState, keymap } from 'codemirror'; + class CodemirrorEditor extends HTMLElement { constructor() { super(); - this.instance = ''; - this.host = window.location.origin; - this.element = this.querySelector('textarea'); - this.refresh = this.refresh.bind(this); - - // Observer instance to refresh the Editor when it become visible, eg after Tab switching - this.intersectionObserver = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting && this.instance) { - this.instance.refresh(); + this.toggleFullScreen = () => { + if (!this.classList.contains('fullscreen')) { + this.classList.add('fullscreen'); + document.documentElement.scrollTop = 0; + document.documentElement.style.overflow = 'hidden'; + } else { + this.closeFullScreen(); } - }, { threshold: 0 }); - } + }; - static get observedAttributes() { - return ['options']; + this.closeFullScreen = () => { + this.classList.remove('fullscreen'); + document.documentElement.style.overflow = ''; + }; } get options() { return JSON.parse(this.getAttribute('options')); } - set options(value) { this.setAttribute('options', value); } - - attributeChangedCallback(attr, oldValue, newValue) { - switch (attr) { - case 'options': - if (oldValue && newValue !== oldValue) { - this.refresh(this.element); - } - break; - default: - // Do nothing - } - } + get fsCombo() { return this.getAttribute('fs-combo'); } async connectedCallback() { - const cmPath = this.getAttribute('editor'); - const addonsPath = this.getAttribute('addons'); - - await import(`${this.host}/${cmPath}`); - - if (this.options.keyMapUrl) { - await import(`${this.host}/${this.options.keyMapUrl}`); + const { options } = this; + + // Configure full screen feature + if (this.fsCombo) { + options.customExtensions = options.customExtensions || []; + options.customExtensions.push(() => keymap.of([ + { key: this.fsCombo, run: this.toggleFullScreen }, + { key: 'Escape', run: this.closeFullScreen }, + ])); + + // Relocate BS modals, to resolve z-index issue in full screen + this.bsModals = this.querySelectorAll('.joomla-modal.modal'); + this.bsModals.forEach((modal) => { + document.body.appendChild(modal); + }); } - await import(`${this.host}/${addonsPath}`); - - const that = this; - - // For mode autoloading. - window.CodeMirror.modeURL = this.getAttribute('mod-path'); - - // Fire this function any time an editor is created. - window.CodeMirror.defineInitHook((editor) => { - // Try to set up the mode - const mode = window.CodeMirror.findModeByName(editor.options.mode || '') - || window.CodeMirror.findModeByExtension(editor.options.mode || ''); - - window.CodeMirror.autoLoadMode(editor, typeof mode === 'object' ? mode.mode : editor.options.mode); - - if (mode && mode.mime) { - // Fix the x-php error - if (['text/x-php', 'application/x-httpd-php', 'application/x-httpd-php-open'].includes(mode.mime)) { - editor.setOption('mode', 'php'); - } else if (mode.mime === 'text/html') { - editor.setOption('mode', mode.mode); - } else { - editor.setOption('mode', mode.mime); - } - } - - const toggleFullScreen = () => { - that.instance.setOption('fullScreen', !that.instance.getOption('fullScreen')); - const header = document.getElementById('subhead'); - if (header) { - const header1 = document.getElementById('header'); - header1.classList.toggle('hidden'); - header.classList.toggle('hidden'); - that.instance.display.wrapper.style.top = `${header.getBoundingClientRect().height}px`; - } - }; - - const closeFullScreen = () => { - that.instance.getOption('fullScreen'); - that.instance.setOption('fullScreen', false); - if (!that.instance.getOption('fullScreen')) { - const header = document.getElementById('subhead'); - if (header) { - const header1 = document.getElementById('header'); - header.classList.toggle('hidden'); - header1.classList.toggle('hidden'); - that.instance.display.wrapper.style.top = `${header.getBoundingClientRect().height}px`; + // Create an editor instance + this.element = this.querySelector('textarea'); + const editor = await createFromTextarea(this.element, options); + this.instance = editor; + + // Register Editor for Joomla api + Joomla.editors.instances[this.element.id] = { + id: () => this.element.id, + element: () => this.element, + getValue: () => editor.state.doc.toString(), + setValue: (text) => { + editor.dispatch({ + changes: { from: 0, to: editor.state.doc.length, insert: text }, + }); + }, + getSelection: () => editor.state.sliceDoc( + editor.state.selection.main.from, + editor.state.selection.main.to, + ), + replaceSelection: (text) => { + const v = editor.state.replaceSelection(text); + editor.dispatch(v); + }, + disable: (disabled) => { + editor.state.config.compartments.forEach((facet, compartment) => { + if (compartment.$j_name === 'readOnly') { + editor.dispatch({ + effects: compartment.reconfigure(EditorState.readOnly.of(disabled)), + }); } - } - }; - - const map = { - 'Ctrl-Q': toggleFullScreen, - [that.getAttribute('fs-combo')]: toggleFullScreen, - Esc: closeFullScreen, - }; - - editor.addKeyMap(map); - - const makeMarker = () => { - const marker = document.createElement('div'); - marker.className = 'CodeMirror-markergutter-mark'; - - return marker; - }; - - // Handle gutter clicks (place or remove a marker). - editor.on('gutterClick', (ed, n, gutter) => { - if (gutter !== 'CodeMirror-markergutter') { - return; - } - - const info = ed.lineInfo(n); - const hasMarker = !!info.gutterMarkers && !!info.gutterMarkers['CodeMirror-markergutter']; - ed.setGutterMarker(n, 'CodeMirror-markergutter', hasMarker ? null : makeMarker()); - }); - - /* Some browsers do something weird with the fieldset which doesn't - work well with CodeMirror. Fix it. */ - if (that.parentNode.tagName.toLowerCase() === 'fieldset') { - that.parentNode.style.minWidth = 0; - } - }); - - // Register Editor - this.instance = window.CodeMirror.fromTextArea(this.element, this.options); - this.instance.disable = (disabled) => this.setOption('readOnly', disabled ? 'nocursor' : false); - Joomla.editors.instances[this.element.id] = this.instance; - - // Watch when the element in viewport, and refresh the editor - this.intersectionObserver.observe(this); + }); + }, + onSave: () => {}, + refresh: () => {}, + }; } disconnectedCallback() { + if (this.instance) { + this.element.style.display = ''; + this.instance.destroy(); + } // Remove from the Joomla API delete Joomla.editors.instances[this.element.id]; - // Remove from observer - this.intersectionObserver.unobserve(this); - } - - refresh(element) { - this.instance.fromTextArea(element, this.options); + // Restore modals + if (this.bsModals && this.bsModals.length) { + this.bsModals.forEach((modal) => { + this.appendChild(modal); + }); + } } } diff --git a/build/media_source/templates/administrator/atum/scss/vendor/_codemirror.scss b/build/media_source/templates/administrator/atum/scss/vendor/_codemirror.scss index ca4b3da34d16a..d4e29274c3a86 100644 --- a/build/media_source/templates/administrator/atum/scss/vendor/_codemirror.scss +++ b/build/media_source/templates/administrator/atum/scss/vendor/_codemirror.scss @@ -1,5 +1,19 @@ // Codemirror -.CodeMirror-fullscreen { +joomla-editor-codemirror.fullscreen { top: 133px; + + .cm-editor { + height: calc(90vh - 133px) !important; + } + + @include media-breakpoint-down(sm) { + top: 54px; + z-index: 890; + + .cm-editor { + height: 100vh !important; + } + } } + diff --git a/package-lock.json b/package-lock.json index f0781a4090dec..dff49c82ccf08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,19 @@ "license": "GPL-2.0-or-later", "dependencies": { "@claviska/jquery-minicolors": "^2.3.6", + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", "@fortawesome/fontawesome-free": "^6.4.0", "@joomla/joomla-a11y-checker": "^1.0.0", "@popperjs/core": "^2.11.8", @@ -20,7 +33,6 @@ "bootstrap": "~5.2.3", "choices.js": "^9.1.0", "chosen-js": "^1.8.7", - "codemirror": "^5.65.13", "cropperjs": "^1.5.13", "diff": "^5.1.0", "dotenv": "^16.3.1", @@ -57,6 +69,7 @@ "@vue/compiler-sfc": "^3.3.4", "autoprefixer": "^10.4.14", "chokidar": "^3.5.3", + "cli-progress": "^3.12.0", "commander": "^8.3.0", "core-js": "^3.31.0", "cssnano": "^5.1.15", @@ -1777,6 +1790,157 @@ "jquery": ">= 1.7.x" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.8.1.tgz", + "integrity": "sha512-HpphvDcTdOx+9R3eUw9hZK9JA77jlaBF0kOt2McbyfvY0rX9pnMoO8rkkZc0GzSbzhIY4m5xJ0uHHgjfqHNmXQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.6.0", + "@lezer/common": "^1.0.0" + }, + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.4.tgz", + "integrity": "sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.0.tgz", + "integrity": "sha512-oyIdJM29AyRPM3+PPq1I2oIk8NpUfEN3kAM05XWDDs6o3gSneIKaVJifT2P+fqONLou2uIgXynFyMUDQvo/szA==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.5.tgz", + "integrity": "sha512-dUCSxkIw2G+chaUfw3Gfu5kkN83vJQN8gfQDp9iEHsIZluMJA0YJveT12zg/28BJx+uPsbQ6VimKCgx3oJrZxA==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.2.2", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.9.tgz", + "integrity": "sha512-z3jdkcqOEBT2txn2a87A0jSy6Te3679wg/U8QzMeftFt+4KA6QooMwfdFzJiuC3L6fXKfTXZcDocoaxMYfGz0w==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.1.tgz", + "integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.2.tgz", + "integrity": "sha512-JQYZjHL2LAfpiZI2/qZ/qzDuSqmGKMwyApYmEUUCTxLM4MWS7sATUEfIguZQr9Zjx/7gcdnewb039smF6nC2zw==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.8.0.tgz", + "integrity": "sha512-r1paAyWOZkfY0RaYEZj3Kul+MiQTEbDvYqf8gPGaRvNneHXCmfSaAVFjwRUPlgxS8yflMxw2CTu6uCMp8R8A2g==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.3.0.tgz", + "integrity": "sha512-tzxOVQNoDhhwFNfcTO2IB74wQoWarARcH6gv3YufPpiJ9yhcb7zD6JCkO5+FWARskqRFc8GFa6E+wUyOvADl5A==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.0.tgz", + "integrity": "sha512-64/M40YeJPToKvGO6p3fijo2vwUEj4nACEAXElCaYQ50HrXSvRaK+NHEhSh73WFBGdvIdhrV+lL9PdJy2RfCYA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz", + "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==" + }, + "node_modules/@codemirror/view": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.14.0.tgz", + "integrity": "sha512-I263FPs4In42MNmrdwN2DfmYPFMVMXgT7o/mxdGp4jv5LPs8i0FOxzmxF5yeeQdYSTztb2ZhmPIu0ahveInVTg==", + "dependencies": { + "@codemirror/state": "^6.1.4", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -2073,6 +2237,82 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@lezer/common": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.3.tgz", + "integrity": "sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA==" + }, + "node_modules/@lezer/css": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.2.tgz", + "integrity": "sha512-5TKMAReXukfEmIiZprDlGfZVfOOCyEStFi1YLzxclm9H3G/HHI49/2wzlRT6bQw5r7PoZVEtjTItEkb/UuZQyg==", + "dependencies": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz", + "integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.4.tgz", + "integrity": "sha512-HdJYMVZcT4YsMo7lW3ipL4NoyS2T67kMPuSVS5TgLGqmaCjEU/D6xv7zsa1ktvTK5lwk7zzF1e3eU6gBZIPm5g==", + "dependencies": { + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.3.tgz", + "integrity": "sha512-k7Eo9z9B1supZ5cCD4ilQv/RZVN30eUQL+gGbr6ybrEY3avBAL5MDiYi2aa23Aj0A79ry4rJRvPAwE2TM8bd+A==", + "dependencies": { + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz", + "integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==", + "dependencies": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.7.tgz", + "integrity": "sha512-ssHKb3p0MxhJXT2i7UBmgAY1BIM3Uq/D772Qutu3EVmxWIyNMU12nQ0rL3Fhu+MiFtiTzyTmd3xGwEf3ON5PSA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/php": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.1.tgz", + "integrity": "sha512-aqdCQJOXJ66De22vzdwnuC502hIaG9EnPK2rSi+ebXyUd+j7GAX1mRjWZOVOmf3GST1YUfUCu6WXDiEgDGOVwA==", + "dependencies": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-jMDXrV953sDAUEMI25VNrI9dz94Ai96FfeglytFINhhwQ867HKlCE2jt3AwZTCT7M528WxdDWv/Ty8e9wizwmQ==", + "dependencies": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3284,6 +3524,18 @@ "node": ">=8" } }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", @@ -3315,11 +3567,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/codemirror": { - "version": "5.65.13", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.13.tgz", - "integrity": "sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==" - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3456,6 +3703,11 @@ "node": ">=10" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cropperjs": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.13.tgz", @@ -9064,6 +9316,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-mod": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.3.tgz", + "integrity": "sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==" + }, "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -9978,6 +10235,11 @@ "vuex": ">=2.5" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11323,6 +11585,151 @@ "integrity": "sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==", "requires": {} }, + "@codemirror/autocomplete": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.8.1.tgz", + "integrity": "sha512-HpphvDcTdOx+9R3eUw9hZK9JA77jlaBF0kOt2McbyfvY0rX9pnMoO8rkkZc0GzSbzhIY4m5xJ0uHHgjfqHNmXQ==", + "requires": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.6.0", + "@lezer/common": "^1.0.0" + } + }, + "@codemirror/commands": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.4.tgz", + "integrity": "sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==", + "requires": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "@codemirror/lang-css": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.0.tgz", + "integrity": "sha512-oyIdJM29AyRPM3+PPq1I2oIk8NpUfEN3kAM05XWDDs6o3gSneIKaVJifT2P+fqONLou2uIgXynFyMUDQvo/szA==", + "requires": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.0.0" + } + }, + "@codemirror/lang-html": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.5.tgz", + "integrity": "sha512-dUCSxkIw2G+chaUfw3Gfu5kkN83vJQN8gfQDp9iEHsIZluMJA0YJveT12zg/28BJx+uPsbQ6VimKCgx3oJrZxA==", + "requires": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.2.2", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.0" + } + }, + "@codemirror/lang-javascript": { + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.9.tgz", + "integrity": "sha512-z3jdkcqOEBT2txn2a87A0jSy6Te3679wg/U8QzMeftFt+4KA6QooMwfdFzJiuC3L6fXKfTXZcDocoaxMYfGz0w==", + "requires": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "requires": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "@codemirror/lang-php": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.1.tgz", + "integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==", + "requires": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, + "@codemirror/lang-xml": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.2.tgz", + "integrity": "sha512-JQYZjHL2LAfpiZI2/qZ/qzDuSqmGKMwyApYmEUUCTxLM4MWS7sATUEfIguZQr9Zjx/7gcdnewb039smF6nC2zw==", + "requires": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "@codemirror/language": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.8.0.tgz", + "integrity": "sha512-r1paAyWOZkfY0RaYEZj3Kul+MiQTEbDvYqf8gPGaRvNneHXCmfSaAVFjwRUPlgxS8yflMxw2CTu6uCMp8R8A2g==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "@codemirror/lint": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.3.0.tgz", + "integrity": "sha512-tzxOVQNoDhhwFNfcTO2IB74wQoWarARcH6gv3YufPpiJ9yhcb7zD6JCkO5+FWARskqRFc8GFa6E+wUyOvADl5A==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "@codemirror/search": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.0.tgz", + "integrity": "sha512-64/M40YeJPToKvGO6p3fijo2vwUEj4nACEAXElCaYQ50HrXSvRaK+NHEhSh73WFBGdvIdhrV+lL9PdJy2RfCYA==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "@codemirror/state": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz", + "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==" + }, + "@codemirror/view": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.14.0.tgz", + "integrity": "sha512-I263FPs4In42MNmrdwN2DfmYPFMVMXgT7o/mxdGp4jv5LPs8i0FOxzmxF5yeeQdYSTztb2ZhmPIu0ahveInVTg==", + "requires": { + "@codemirror/state": "^6.1.4", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -11551,6 +11958,82 @@ } } }, + "@lezer/common": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.3.tgz", + "integrity": "sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA==" + }, + "@lezer/css": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.2.tgz", + "integrity": "sha512-5TKMAReXukfEmIiZprDlGfZVfOOCyEStFi1YLzxclm9H3G/HHI49/2wzlRT6bQw5r7PoZVEtjTItEkb/UuZQyg==", + "requires": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "@lezer/highlight": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz", + "integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==", + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/html": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.4.tgz", + "integrity": "sha512-HdJYMVZcT4YsMo7lW3ipL4NoyS2T67kMPuSVS5TgLGqmaCjEU/D6xv7zsa1ktvTK5lwk7zzF1e3eU6gBZIPm5g==", + "requires": { + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "@lezer/javascript": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.3.tgz", + "integrity": "sha512-k7Eo9z9B1supZ5cCD4ilQv/RZVN30eUQL+gGbr6ybrEY3avBAL5MDiYi2aa23Aj0A79ry4rJRvPAwE2TM8bd+A==", + "requires": { + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "@lezer/json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz", + "integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==", + "requires": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.7.tgz", + "integrity": "sha512-ssHKb3p0MxhJXT2i7UBmgAY1BIM3Uq/D772Qutu3EVmxWIyNMU12nQ0rL3Fhu+MiFtiTzyTmd3xGwEf3ON5PSA==", + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/php": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.1.tgz", + "integrity": "sha512-aqdCQJOXJ66De22vzdwnuC502hIaG9EnPK2rSi+ebXyUd+j7GAX1mRjWZOVOmf3GST1YUfUCu6WXDiEgDGOVwA==", + "requires": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "@lezer/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-jMDXrV953sDAUEMI25VNrI9dz94Ai96FfeglytFINhhwQ867HKlCE2jt3AwZTCT7M528WxdDWv/Ty8e9wizwmQ==", + "requires": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -12449,6 +12932,15 @@ "restore-cursor": "^3.1.0" } }, + "cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "requires": { + "string-width": "^4.2.3" + } + }, "cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", @@ -12469,11 +12961,6 @@ "string-width": "^4.2.0" } }, - "codemirror": { - "version": "5.65.13", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.13.tgz", - "integrity": "sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==" - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -12589,6 +13076,11 @@ "yaml": "^1.10.0" } }, + "crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "cropperjs": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.13.tgz", @@ -16661,6 +17153,11 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "style-mod": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.3.tgz", + "integrity": "sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==" + }, "style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -17372,6 +17869,11 @@ "flatted": "^3.0.5" } }, + "w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 4a9b9fb8fe524..2c0a8d3f2e00a 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,19 @@ ], "dependencies": { "@claviska/jquery-minicolors": "^2.3.6", + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", "@fortawesome/fontawesome-free": "^6.4.0", "@joomla/joomla-a11y-checker": "^1.0.0", "@popperjs/core": "^2.11.8", @@ -47,7 +60,6 @@ "bootstrap": "~5.2.3", "choices.js": "^9.1.0", "chosen-js": "^1.8.7", - "codemirror": "^5.65.13", "cropperjs": "^1.5.13", "diff": "^5.1.0", "dotenv": "^16.3.1", @@ -84,6 +96,7 @@ "@vue/compiler-sfc": "^3.3.4", "autoprefixer": "^10.4.14", "chokidar": "^3.5.3", + "cli-progress": "^3.12.0", "commander": "^8.3.0", "core-js": "^3.31.0", "cssnano": "^5.1.15", diff --git a/plugins/editors/codemirror/codemirror.xml b/plugins/editors/codemirror/codemirror.xml index d249b1d1067e9..69214bac8f946 100644 --- a/plugins/editors/codemirror/codemirror.xml +++ b/plugins/editors/codemirror/codemirror.xml @@ -1,7 +1,7 @@ plg_editors_codemirror - 5.65.13 + 6.0.0 28 March 2011 Marijn Haverbeke marijnh@gmail.com @@ -11,7 +11,6 @@ PLG_CODEMIRROR_XML_DESCRIPTION Joomla\Plugin\Editors\CodeMirror - fonts.json layouts services src @@ -49,18 +48,6 @@ - - - - - JON - - - - - - - - - - - - - - - - - PLG_CODEMIRROR_FIELD_VALUE_FULLSCREEN_MOD_ALT - -
- - - - - - - - +
+ +
+ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - [].forEach.call(document.getElementsByClassName('hello'), function (el) {el.innerHtml = 'Hello World';}); - - - - -
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam a ornare lectus, quis semper urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus interdum metus id elit rutrum sollicitudin. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam in fermentum risus, id facilisis nulla. Phasellus gravida erat sed ullamcorper accumsan. Donec blandit sem eget sem congue, a varius sapien semper.

-

Integer euismod tempor convallis. Nullam porttitor et ex ac fringilla. Quisque facilisis est ac erat condimentum malesuada. Aenean commodo quam odio, tincidunt ultricies mauris suscipit et.

- -
    -
  • Vivamus ultrices ligula a odio lacinia pellentesque.
  • -
  • Curabitur iaculis arcu pharetra, mollis turpis id, commodo erat.
  • -
  • Etiam consequat enim quis faucibus interdum.
  • -
  • Morbi in ipsum pulvinar, eleifend lorem sit amet, euismod magna.
  • -
  • Donec consectetur lacus vitae eros euismod porta.
  • -
-
-]]> -
-
-
diff --git a/plugins/editors/codemirror/fonts.json b/plugins/editors/codemirror/fonts.json deleted file mode 100644 index 45c4c5198ba5e..0000000000000 --- a/plugins/editors/codemirror/fonts.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "anonymous_pro": { - "name": "Anonymous Pro", - "url": "https://fonts.googleapis.com/css?family=Anonymous+Pro", - "css": "'Anonymous Pro', monospace" - }, - "cousine": { - "name": "Cousine", - "url": "https://fonts.googleapis.com/css?family=Cousine", - "css": "Cousine, monospace" - }, - "cutive_mono": { - "name": "Cutive Mono", - "url": "https://fonts.googleapis.com/css?family=Cutive+Mono", - "css": "'Cutive Mono', monospace" - }, - "droid_sans_mono": { - "name": "Droid Sans Mono", - "url": "https://fonts.googleapis.com/css?family=Droid+Sans+Mono", - "css": "'Droid Sans Mono', monospace" - }, - "fira_mono": { - "name": "Fira Mono", - "url": "https://fonts.googleapis.com/css?family=Fira+Mono", - "css": "'Fira Mono', monospace" - }, - "ibm_plex_mono": { - "name": "IBM Plex Mono", - "url": "https://fonts.googleapis.com/css?family=IBM+Plex+Mono", - "css": "'IBM Plex Mono', monospace;" - }, - "inconsolata": { - "name": "Inconsolata", - "url": "https://fonts.googleapis.com/css?family=Inconsolata", - "css": "Inconsolata, monospace" - }, - "lekton": { - "name": "Lekton", - "url": "https://fonts.googleapis.com/css?family=Lekton", - "css": "Lekton, monospace" - }, - "nanum_gothic_coding": { - "name": "Nanum Gothic Coding", - "url": "https://fonts.googleapis.com/css?family=Nanum+Gothic+Coding", - "css": "'Nanum Gothic Coding', monospace" - }, - "nova_mono": { - "name": "Nova Mono", - "url": "https://fonts.googleapis.com/css?family=Nova+Mono", - "css": "'Nova Mono', monospace" - }, - "overpass_mono": { - "name": "Overpass Mono", - "url": "https://fonts.googleapis.com/css?family=Overpass+Mono", - "css": "'Overpass Mono', monospace" - }, - "oxygen_mono": { - "name": "Oxygen Mono", - "url": "https://fonts.googleapis.com/css?family=Oxygen+Mono", - "css": "'Oxygen Mono', monospace" - }, - "press_start_2p": { - "name": "Press Start 2P", - "url": "https://fonts.googleapis.com/css?family=Press+Start+2P", - "css": "'Press Start 2P', monospace" - }, - "pt_mono": { - "name": "PT Mono", - "url": "https://fonts.googleapis.com/css?family=PT+Mono", - "css": "'PT Mono', monospace" - }, - "roboto_mono": { - "name": "Roboto Mono", - "url": "https://fonts.googleapis.com/css?family=Roboto+Mono", - "css": "'Roboto Mono', monospace" - }, - "rubik_mono_one": { - "name": "Rubik Mono One", - "url": "https://fonts.googleapis.com/css?family=Rubik+Mono+One", - "css": "'Rubik Mono One', monospace" - }, - "share_tech_mono": { - "name": "Share Tech Mono", - "url": "https://fonts.googleapis.com/css?family=Share+Tech+Mono", - "css": "'Share Tech Mono', monospace" - }, - "source_code_pro": { - "name": "Source Code Pro", - "url": "https://fonts.googleapis.com/css?family=Source+Code+Pro", - "css": "'Source Code Pro', monospace" - }, - "space_mono": { - "name": "Space Mono", - "url": "https://fonts.googleapis.com/css?family=Space+Mono", - "css": "'Space Mono', monospace" - }, - "ubuntu_mono": { - "name": "Ubuntu Mono", - "url": "https://fonts.googleapis.com/css?family=Ubuntu+Mono", - "css": "'Ubuntu Mono', monospace" - }, - "vt323": { - "name": "VT323", - "url": "https://fonts.googleapis.com/css?family=VT323", - "css": "'VT323', monospace" - } -} diff --git a/plugins/editors/codemirror/layouts/editors/codemirror/codemirror.php b/plugins/editors/codemirror/layouts/editors/codemirror/codemirror.php new file mode 100644 index 0000000000000..2cb2f34430067 --- /dev/null +++ b/plugins/editors/codemirror/layouts/editors/codemirror/codemirror.php @@ -0,0 +1,66 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +// No direct access +defined('_JEXEC') or die; + +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\Registry\Registry; + +extract($displayData); + +/** + * Layout variables + * + * @var array $options JS options for editor + * @var Registry $params Plugin parameters + * @var string $id The id of the input + * @var string $name The name of the input + * @var integer $cols Textarea cols attribute + * @var integer $rows Textarea rows attribute + * @var string $content The value + * @var string $buttons Editor XTD buttons + */ + +$option = ' options="' . $this->escape(json_encode($options)) . '"'; +$style = ''; + +if ($options->width) { + $style .= 'width:' . $options->width . ';'; +} +if ($options->height) { + $style .= 'height:' . $options->height . ';'; +} + +// Fullscreen combo +$fsCombo = ''; +if (empty($options->readOnly)) { + $fskeys = $params->get('fullScreenMod', []); + $fskeys[] = $params->get('fullScreen', 'F10'); + $fullScreenCombo = implode('-', $fskeys); + $fsCombo = ' fs-combo=' . $this->escape($fullScreenCombo); +} + +/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */ +$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); +$wa->getRegistry()->addExtensionRegistryFile('plg_editors_codemirror'); +$wa->useStyle('plg_editors_codemirror') + ->useScript('webcomponent.editor-codemirror'); +?> +> +' . $content . ''; ?> + +

+ +

+ + +
diff --git a/plugins/editors/codemirror/layouts/editors/codemirror/element.php b/plugins/editors/codemirror/layouts/editors/codemirror/element.php deleted file mode 100644 index a89c0dfb8b332..0000000000000 --- a/plugins/editors/codemirror/layouts/editors/codemirror/element.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @license GNU General Public License version 2 or later; see LICENSE.txt - */ - -// No direct access -defined('_JEXEC') or die; - -use Joomla\CMS\Factory; -use Joomla\CMS\HTML\HTMLHelper; -use Joomla\CMS\Language\Text; -use Joomla\CMS\Uri\Uri; - -$options = $displayData->options; -$params = $displayData->params; -$name = $displayData->name; -$id = $displayData->id; -$cols = $displayData->cols; -$rows = $displayData->rows; -$content = $displayData->content; -$extJS = JDEBUG ? '.js' : '.min.js'; -$modifier = $params->get('fullScreenMod', []) ? implode(' + ', $params->get('fullScreenMod', [])) . ' + ' : ''; -$basePath = $displayData->basePath; -$modePath = $displayData->modePath; -$modPath = 'mod-path="' . Uri::root() . $modePath . $extJS . '"'; -$fskeys = $params->get('fullScreenMod', []); -$fskeys[] = $params->get('fullScreen', 'F10'); -$fullScreenCombo = implode('-', $fskeys); -$fsCombo = 'fs-combo=' . json_encode($fullScreenCombo); -$option = 'options=\'' . json_encode($options) . '\''; -$mediaVersion = Factory::getDocument()->getMediaVersion(); -$editor = 'editor="' . ltrim(HTMLHelper::_('script', $basePath . 'lib/codemirror' . $extJS, ['version' => 'auto', 'pathOnly' => true]), '/') . '?' . $mediaVersion . '"'; -$addons = 'addons="' . ltrim(HTMLHelper::_('script', $basePath . 'lib/addons' . $extJS, ['version' => 'auto', 'pathOnly' => true]), '/') . '?' . $mediaVersion . '"'; - -// Remove the fullscreen message and option if readonly not null. -if (isset($options->readOnly)) { - $fsCombo = ''; -} - -Factory::getDocument()->getWebAssetManager() - ->registerAndUseStyle('codemirror.lib.main', $basePath . 'lib/codemirror.css') - ->registerAndUseStyle('codemirror.lib.addons', $basePath . 'lib/addons.css', [], [], ['codemirror.lib.main']) - ->registerAndUseScript( - 'webcomponent.editor-codemirror', - 'plg_editors_codemirror/joomla-editor-codemirror.min.js', - [], - ['type' => 'module'], - ['wcpolyfill'] - ); -?> -> -', $content, ''; ?> - -

- -

- -
-buttons; ?> diff --git a/plugins/editors/codemirror/layouts/editors/codemirror/styles.php b/plugins/editors/codemirror/layouts/editors/codemirror/styles.php deleted file mode 100644 index 2f272e2e51f21..0000000000000 --- a/plugins/editors/codemirror/layouts/editors/codemirror/styles.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @license GNU General Public License version 2 or later; see LICENSE.txt - */ - -// No direct access -defined('_JEXEC') or die; - -use Joomla\CMS\Factory; - -$params = $displayData->params; -$fontFamily = $displayData->fontFamily ?? 'monospace'; -$fontSize = $params->get('fontSize', 13) . 'px;'; -$lineHeight = $params->get('lineHeight', 1.2) . 'em;'; - -// Set the active line color. -$color = $params->get('activeLineColor', '#a4c2eb'); -$r = hexdec($color[1] . $color[2]); -$g = hexdec($color[3] . $color[4]); -$b = hexdec($color[5] . $color[6]); -$activeLineColor = 'rgba(' . $r . ', ' . $g . ', ' . $b . ', .5)'; - -// Set the color for matched tags. -$color = $params->get('highlightMatchColor', '#fa542f'); -$r = hexdec($color[1] . $color[2]); -$g = hexdec($color[3] . $color[4]); -$b = hexdec($color[5] . $color[6]); -$highlightMatchColor = 'rgba(' . $r . ', ' . $g . ', ' . $b . ', .5)'; - -/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */ -$wa = Factory::getApplication()->getDocument()->getWebAssetManager(); -$wa->registerAndUseStyle('plg_editors_codemirror', 'plg_editors_codemirror/codemirror.css'); -$wa->addInlineStyle( - <<getApplication()->getDocument(); - - // Codemirror shall have its own group of plugins to modify and extend its behavior - PluginHelper::importPlugin('editors_codemirror'); - - // At this point, params can be modified by a plugin before going to the layout renderer. - $this->getApplication()->triggerEvent('onCodeMirrorBeforeInit', [&$this->params, &$this->basePath, &$this->modePath]); - - $displayData = (object) ['params' => $this->params]; - $font = $this->params->get('fontFamily', '0'); - $fontInfo = $this->getFontInfo($font); - - if (isset($fontInfo)) { - if (isset($fontInfo->url)) { - $doc->addStyleSheet($fontInfo->url); - } - - if (isset($fontInfo->css)) { - $displayData->fontFamily = $fontInfo->css . '!important'; - } - } - - // We need to do output buffering here because layouts may actually 'echo' things which we do not want. - ob_start(); - LayoutHelper::render('editors.codemirror.styles', $displayData, JPATH_PLUGINS . '/editors/codemirror/layouts'); - ob_end_clean(); - - $this->getApplication()->triggerEvent('onCodeMirrorAfterInit', [&$this->params, &$this->basePath, &$this->modePath]); - } - /** * Display the editor area. * @@ -137,135 +56,80 @@ public function onDisplay( $author = null, $params = [] ) { - // True if a CodeMirror already has autofocus. Prevent multiple autofocuses. - static $autofocused; + $this->loadLanguage(); $id = empty($id) ? $name : $id; // Must pass the field id to the buttons in this editor. $buttons = $this->displayButtons($id, $buttons, $asset, $author); - // Only add "px" to width and height if they are not given as a percentage. - $width .= is_numeric($width) ? 'px' : ''; - $height .= is_numeric($height) ? 'px' : ''; - // Options for the CodeMirror constructor. - $options = new \stdClass(); - $keyMapUrl = ''; + $options = new \stdClass(); // Is field readonly? if (!empty($params['readonly'])) { - $options->readOnly = 'nocursor'; - } - - // Should we focus on the editor on load? - if (!$autofocused) { - $options->autofocus = isset($params['autofocus']) ? (bool) $params['autofocus'] : false; - $autofocused = $options->autofocus; - } - // Set autorefresh to true - fixes issue when editor is not loaded in a focused tab - $options->autoRefresh = true; - - $options->lineWrapping = (bool) $this->params->get('lineWrapping', 1); - - // Add styling to the active line. - $options->styleActiveLine = (bool) $this->params->get('activeLine', 1); - - // Do we highlight selection matches? - if ($this->params->get('selectionMatches', 1)) { - $options->highlightSelectionMatches = [ - 'showToken' => true, - 'annotateScrollbar' => true, - ]; + $options->readOnly = true; } - // Do we use line numbering? - if ($options->lineNumbers = (bool) $this->params->get('lineNumbers', 1)) { - $options->gutters[] = 'CodeMirror-linenumbers'; - } - - // Do we use code folding? - if ($options->foldGutter = (bool) $this->params->get('codeFolding', 1)) { - $options->gutters[] = 'CodeMirror-foldgutter'; - } + // Only add "px" to width and height if they are not given as a percentage. + $options->width = is_numeric($width) ? $width . 'px' : $width; + $options->height = is_numeric($height) ? $height . 'px' : $height; - // Do we use a marker gutter? - if ($options->markerGutter = (bool) $this->params->get('markerGutter', $this->params->get('marker-gutter', 1))) { - $options->gutters[] = 'CodeMirror-markergutter'; - } + $options->lineNumbers = (bool) $this->params->get('lineNumbers', 1); + $options->foldGutter = (bool) $this->params->get('codeFolding', 1); + $options->lineWrapping = (bool) $this->params->get('lineWrapping', 1); + $options->activeLine = (bool) $this->params->get('activeLine', 1); + $options->highlightSelection = (bool) $this->params->get('selectionMatches', 1); // Load the syntax mode. - $syntax = !empty($params['syntax']) - ? $params['syntax'] - : $this->params->get('syntax', 'html'); - $options->mode = $this->modeAlias[$syntax] ?? $syntax; - - // Load the theme if specified. - if ($theme = $this->params->get('theme')) { - $options->theme = $theme; - - $this->getApplication()->getDocument()->getWebAssetManager() - ->registerAndUseStyle('codemirror.theme', $this->basePath . 'theme/' . $theme . '.css'); - } - - // Special options for tagged modes (xml/html). - if (in_array($options->mode, ['xml', 'html', 'php'])) { - // Autogenerate closing tags (html/xml only). - $options->autoCloseTags = (bool) $this->params->get('autoCloseTags', 1); - - // Highlight the matching tag when the cursor is in a tag (html/xml only). - $options->matchTags = (bool) $this->params->get('matchTags', 1); - } + $modeAlias = [ + 'scss' => 'css', + 'sass' => 'css', + 'less' => 'css', + ]; + $options->mode = !empty($params['syntax']) ? $params['syntax'] : $this->params->get('syntax', 'html'); + $options->mode = $modeAlias[$options->mode] ?? $options->mode; // Special options for non-tagged modes. if (!in_array($options->mode, ['xml', 'html'])) { // Autogenerate closing brackets. $options->autoCloseBrackets = (bool) $this->params->get('autoCloseBrackets', 1); - - // Highlight the matching bracket. - $options->matchBrackets = (bool) $this->params->get('matchBrackets', 1); } - $options->scrollbarStyle = $this->params->get('scrollbarStyle', 'native'); - // KeyMap settings. - $options->keyMap = $this->params->get('keyMap', false); - - // Support for older settings. - if ($options->keyMap === false) { - $options->keyMap = $this->params->get('vimKeyBinding', 0) ? 'vim' : 'default'; - } - - if ($options->keyMap !== 'default') { - $keyMapUrl = HTMLHelper::_('script', $this->basePath . 'keymap/' . $options->keyMap . '.min.js', ['relative' => false, 'pathOnly' => true]); - $keyMapUrl .= '?' . $this->getApplication()->getDocument()->getMediaVersion(); - } + $options->keyMap = $this->params->get('keyMap', ''); - $options->keyMapUrl = $keyMapUrl; + // Check for custom extensions + $customExtensions = $this->params->get('customExtensions', []); + $options->customExtensions = []; - $displayData = (object) [ - 'options' => $options, - 'params' => $this->params, - 'name' => $name, - 'id' => $id, - 'cols' => $col, - 'rows' => $row, - 'content' => $content, - 'buttons' => $buttons, - 'basePath' => $this->basePath, - 'modePath' => $this->modePath, - ]; + if ($customExtensions) { + foreach ($customExtensions as $item) { + $methods = array_filter(array_map('trim', explode(',', $item->methods ?? ''))); - // At this point, displayData can be modified by a plugin before going to the layout renderer. - $results = $this->getApplication()->triggerEvent('onCodeMirrorBeforeDisplay', [&$displayData]); + if (empty($item->module) || !$methods) { + continue; + } - $results[] = LayoutHelper::render('editors.codemirror.element', $displayData, JPATH_PLUGINS . '/editors/codemirror/layouts'); + // Prepend root path if we have a file + $module = str_ends_with($item->module, '.js') ? Uri::root(true) . '/' . $item->module : $item->module; - foreach ($this->getApplication()->triggerEvent('onCodeMirrorAfterDisplay', [&$displayData]) as $result) { - $results[] = $result; + $options->customExtensions[] = [$module, $methods]; + } } - return implode("\n", $results); + $displayData = [ + 'options' => $options, + 'params' => $this->params, + 'name' => $name, + 'id' => $id, + 'cols' => $col, + 'rows' => $row, + 'content' => $content, + 'buttons' => $buttons, + ]; + + return LayoutHelper::render('editors.codemirror.codemirror', $displayData, JPATH_PLUGINS . '/editors/codemirror/layouts'); } /** @@ -295,22 +159,4 @@ protected function displayButtons($name, $buttons, $asset, $author) return LayoutHelper::render('joomla.editors.buttons', $buttons); } } - - /** - * Gets font info from the json data file - * - * @param string $font A key from the $fonts array. - * - * @return object - */ - protected function getFontInfo($font) - { - static $fonts; - - if (!$fonts) { - $fonts = json_decode(file_get_contents(JPATH_PLUGINS . '/editors/codemirror/fonts.json'), true); - } - - return isset($fonts[$font]) ? (object) $fonts[$font] : null; - } } diff --git a/plugins/editors/codemirror/src/Field/FontsField.php b/plugins/editors/codemirror/src/Field/FontsField.php deleted file mode 100644 index 1f49fd42c2b48..0000000000000 --- a/plugins/editors/codemirror/src/Field/FontsField.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @license GNU General Public License version 2 or later; see LICENSE.txt - */ - -namespace Joomla\Plugin\Editors\CodeMirror\Field; - -use Joomla\CMS\Form\Field\ListField; -use Joomla\CMS\HTML\HTMLHelper; - -// phpcs:disable PSR1.Files.SideEffects -\defined('_JEXEC') or die; -// phpcs:enable PSR1.Files.SideEffects - -/** - * Supports an HTML select list of fonts - * - * @package Joomla.Plugin - * @subpackage Editors.codemirror - * @since 3.4 - */ -class FontsField extends ListField -{ - /** - * The form field type. - * - * @var string - * @since 3.4 - */ - protected $type = 'Fonts'; - - /** - * Method to get the list of fonts field options. - * - * @return array The field option objects. - * - * @since 3.4 - */ - protected function getOptions() - { - $fonts = json_decode(file_get_contents(JPATH_PLUGINS . '/editors/codemirror/fonts.json')); - $options = []; - - foreach ($fonts as $key => $info) { - $options[] = HTMLHelper::_('select.option', $key, $info->name); - } - - // Merge any additional options in the XML definition. - return array_merge(parent::getOptions(), $options); - } -}