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 };
|
||||
1
media/plg_editors_codemirror/js/codemirror.min.js
vendored
Normal file
1
media/plg_editors_codemirror/js/codemirror.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
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";const minimalSetup=()=>[highlightSpecialChars(),history(),drawSelection(),syntaxHighlighting(defaultHighlightStyle,{fallback:!0})],optionsToExtensions=async e=>{const t=[],o=[];if(e.mode){const{mode:i}=e,r=e[i]||{};o.push(import(`@codemirror/lang-${e.mode}`).then((o=>{if("php"===i)return import("@codemirror/lang-html").then((({html:i})=>{const r=e.html||{selfClosingTags:!0};t.push(o.php({baseLanguage:i(r).language}))}));"html"===i&&(r.selfClosingTags=!0),t.push(o[e.mode](r))})).catch((t=>{console.error(`Cannot creat an extension for "${e.mode}" syntax mode.`,t)})))}if(e.lineNumbers&&t.push(lineNumbers()),e.lineWrapping&&t.push(EditorView.lineWrapping),e.activeLine&&t.push(highlightActiveLineGutter(),highlightActiveLine()),e.highlightSelection&&t.push(highlightSelectionMatches()),e.autoCloseBrackets&&t.push(closeBrackets()),e.foldGutter&&t.push(foldGutter()),"emacs"===e.keyMap)t.push(keymap.of([...emacsStyleKeymap,...historyKeymap]));else t.push(keymap.of([...defaultKeymap,...searchKeymap,...historyKeymap]));const i=new Compartment;return i.$j_name="readOnly",t.push(i.of(EditorState.readOnly.of(!!e.readOnly))),e.customExtensions&&e.customExtensions.length&&e.customExtensions.forEach((e=>{if(e instanceof Function)return void t.push(e());const[i,r]=e;o.push(import(i).then((e=>{r.forEach((o=>{t.push(e[o]())}))})))})),Promise.all(o).then((()=>t))};async function createFromTextarea(e,t){const o=[minimalSetup(),await optionsToExtensions(t)],i=new EditorView({doc:e.value,root:t.root||null,extensions:o});return e.parentNode.insertBefore(i.dom,e),e.style.display="none",e.form&&e.form.addEventListener("submit",(()=>{e.value=i.state.doc.toString()})),t.width&&(i.dom.style.width=t.width),t.height&&(i.dom.style.height=t.height),i}export{createFromTextarea,minimalSetup,optionsToExtensions};
|
||||
BIN
media/plg_editors_codemirror/js/codemirror.min.js.gz
Normal file
BIN
media/plg_editors_codemirror/js/codemirror.min.js.gz
Normal file
Binary file not shown.
138
media/plg_editors_codemirror/js/joomla-editor-codemirror.js
Normal file
138
media/plg_editors_codemirror/js/joomla-editor-codemirror.js
Normal file
@ -0,0 +1,138 @@
|
||||
import { JoomlaEditor, JoomlaEditorDecorator } from 'editor-api';
|
||||
import { keymap, createFromTextarea, EditorState } from 'codemirror';
|
||||
|
||||
/**
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Codemirror Decorator for JoomlaEditor
|
||||
*/
|
||||
// eslint-disable-next-line max-classes-per-file
|
||||
class CodemirrorDecorator extends JoomlaEditorDecorator {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getValue() {
|
||||
return this.instance.state.doc.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} value
|
||||
* @returns {CodemirrorDecorator}
|
||||
*/
|
||||
setValue(value) {
|
||||
const editor = this.instance;
|
||||
editor.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: editor.state.doc.length,
|
||||
insert: value
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getSelection() {
|
||||
const {
|
||||
state
|
||||
} = this.instance;
|
||||
return state.sliceDoc(state.selection.main.from, state.selection.main.to);
|
||||
}
|
||||
replaceSelection(value) {
|
||||
const v = this.instance.state.replaceSelection(value);
|
||||
this.instance.dispatch(v);
|
||||
return this;
|
||||
}
|
||||
disable(enable) {
|
||||
const editor = this.instance;
|
||||
editor.state.config.compartments.forEach((facet, compartment) => {
|
||||
if (compartment.$j_name === 'readOnly') {
|
||||
editor.dispatch({
|
||||
effects: compartment.reconfigure(EditorState.readOnly.of(!enable))
|
||||
});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class CodemirrorEditor extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.toggleFullScreen = () => {
|
||||
if (!this.classList.contains('fullscreen')) {
|
||||
this.classList.add('fullscreen');
|
||||
document.documentElement.scrollTop = 0;
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
} else {
|
||||
this.closeFullScreen();
|
||||
}
|
||||
};
|
||||
this.closeFullScreen = () => {
|
||||
this.classList.remove('fullscreen');
|
||||
document.documentElement.style.overflow = '';
|
||||
};
|
||||
this.interactionCallback = () => {
|
||||
JoomlaEditor.setActive(this.element.id);
|
||||
};
|
||||
}
|
||||
get options() {
|
||||
return JSON.parse(this.getAttribute('options'));
|
||||
}
|
||||
get fsCombo() {
|
||||
return this.getAttribute('fs-combo');
|
||||
}
|
||||
async connectedCallback() {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
// Create and register the Editor
|
||||
this.element = this.querySelector('textarea');
|
||||
this.instance = await createFromTextarea(this.element, options);
|
||||
this.jEditor = new CodemirrorDecorator(this.instance, 'codemirror', this.element.id);
|
||||
JoomlaEditor.register(this.jEditor);
|
||||
|
||||
// Find out when editor is interacted
|
||||
this.addEventListener('click', this.interactionCallback);
|
||||
}
|
||||
disconnectedCallback() {
|
||||
if (this.instance) {
|
||||
this.element.style.display = '';
|
||||
this.instance.destroy();
|
||||
}
|
||||
// Remove from the Joomla API
|
||||
JoomlaEditor.unregister(this.element.id);
|
||||
this.removeEventListener('click', this.interactionCallback);
|
||||
|
||||
// Restore modals
|
||||
if (this.bsModals && this.bsModals.length) {
|
||||
this.bsModals.forEach(modal => {
|
||||
this.appendChild(modal);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-editor-codemirror', CodemirrorEditor);
|
||||
1
media/plg_editors_codemirror/js/joomla-editor-codemirror.min.js
vendored
Normal file
1
media/plg_editors_codemirror/js/joomla-editor-codemirror.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{JoomlaEditor,JoomlaEditorDecorator}from"editor-api";import{keymap,createFromTextarea,EditorState}from"codemirror";class CodemirrorDecorator extends JoomlaEditorDecorator{getValue(){return this.instance.state.doc.toString()}setValue(t){const e=this.instance;return e.dispatch({changes:{from:0,to:e.state.doc.length,insert:t}}),this}getSelection(){const{state:t}=this.instance;return t.sliceDoc(t.selection.main.from,t.selection.main.to)}replaceSelection(t){const e=this.instance.state.replaceSelection(t);return this.instance.dispatch(e),this}disable(t){const e=this.instance;return e.state.config.compartments.forEach(((s,o)=>{"readOnly"===o.$j_name&&e.dispatch({effects:o.reconfigure(EditorState.readOnly.of(!t))})})),this}}class CodemirrorEditor extends HTMLElement{constructor(){super(),this.toggleFullScreen=()=>{this.classList.contains("fullscreen")?this.closeFullScreen():(this.classList.add("fullscreen"),document.documentElement.scrollTop=0,document.documentElement.style.overflow="hidden")},this.closeFullScreen=()=>{this.classList.remove("fullscreen"),document.documentElement.style.overflow=""},this.interactionCallback=()=>{JoomlaEditor.setActive(this.element.id)}}get options(){return JSON.parse(this.getAttribute("options"))}get fsCombo(){return this.getAttribute("fs-combo")}async connectedCallback(){const{options:t}=this;this.fsCombo&&(t.customExtensions=t.customExtensions||[],t.customExtensions.push((()=>keymap.of([{key:this.fsCombo,run:this.toggleFullScreen},{key:"Escape",run:this.closeFullScreen}]))),this.bsModals=this.querySelectorAll(".joomla-modal.modal"),this.bsModals.forEach((t=>{document.body.appendChild(t)}))),this.element=this.querySelector("textarea"),this.instance=await createFromTextarea(this.element,t),this.jEditor=new CodemirrorDecorator(this.instance,"codemirror",this.element.id),JoomlaEditor.register(this.jEditor),this.addEventListener("click",this.interactionCallback)}disconnectedCallback(){this.instance&&(this.element.style.display="",this.instance.destroy()),JoomlaEditor.unregister(this.element.id),this.removeEventListener("click",this.interactionCallback),this.bsModals&&this.bsModals.length&&this.bsModals.forEach((t=>{this.appendChild(t)}))}}customElements.define("joomla-editor-codemirror",CodemirrorEditor);
|
||||
Binary file not shown.
Reference in New Issue
Block a user