first commit
This commit is contained in:
148
media/plg_editors_codemirror/js/codemirror.js
Normal file
148
media/plg_editors_codemirror/js/codemirror.js
Normal file
@ -0,0 +1,148 @@
|
||||
import { highlightSpecialChars, drawSelection, lineNumbers, EditorView, highlightActiveLineGutter, highlightActiveLine, keymap } from '@codemirror/view';
|
||||
export { EditorView, keymap } from '@codemirror/view';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
export { EditorState } from '@codemirror/state';
|
||||
import { syntaxHighlighting, defaultHighlightStyle, foldGutter } from '@codemirror/language';
|
||||
import { history, defaultKeymap, historyKeymap, emacsStyleKeymap } from '@codemirror/commands';
|
||||
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search';
|
||||
import { closeBrackets } from '@codemirror/autocomplete';
|
||||
|
||||
/**
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
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 <jdoc:include />
|
||||
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']], () => <return extension>]
|
||||
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<EditorView>}
|
||||
*/
|
||||
async function createFromTextarea(textarea, options) {
|
||||
const extensions = [minimalSetup(), await optionsToExtensions(options)];
|
||||
const view = new EditorView({
|
||||
doc: textarea.value,
|
||||
root: options.root || null,
|
||||
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 { createFromTextarea, minimalSetup, optionsToExtensions };
|
||||
Reference in New Issue
Block a user