-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[4.0] Fix Codemirror as a custom elements #20684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
eafa453
fa9621e
5bf5f44
c8da110
7471c5e
fb68b6a
1bfe982
af85063
f663276
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,147 +1,150 @@ | ||
|
|
||
| customElements.define('joomla-editor-codemirror', class extends HTMLElement { | ||
| static get observedAttributes() { | ||
| return ['options']; | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
| } | ||
|
|
||
| constructor() { | ||
| super(); | ||
|
|
||
| this.instance = ''; | ||
| this.cm = ''; | ||
| this.file = document.currentScript; | ||
| this.element = this.querySelector('textarea'); | ||
| this.host = window.location.origin; | ||
|
|
||
| // Append the editor script | ||
| if (!document.head.querySelector('#cm-editor')) { | ||
| const cmPath = this.getAttribute('editor'); | ||
| const script1 = document.createElement('script'); | ||
|
|
||
| script1.src = `${this.host}/${cmPath}`; | ||
| script1.id = 'cm-editor'; | ||
| script1.setAttribute('async', false); | ||
| document.head.insertBefore(script1, this.file); | ||
| } | ||
|
|
||
| this.toggleFullScreen = this.toggleFullScreen.bind(this); | ||
| this.closeFullScreen = this.closeFullScreen.bind(this); | ||
| } | ||
|
|
||
| connectedCallback() { | ||
| const buttons = [].slice.call(this.querySelectorAll('.editor-xtd-buttons .xtd-button')); | ||
| this.checkElement('CodeMirror') | ||
| .then(() => { | ||
| // Append the addons script | ||
| if (!document.head.querySelector('#cm-addons')) { | ||
| const addonsPath = this.getAttribute('addons'); | ||
| const script2 = document.createElement('script'); | ||
|
|
||
| script2.src = `${this.host}/${addonsPath}`; | ||
| script2.id = 'cm-addons'; | ||
| script2.setAttribute('async', false); | ||
| document.head.insertBefore(script2, this.file) | ||
| } | ||
|
|
||
| this.checkElement('CodeMirror', 'findModeByName') | ||
| .then(() => { | ||
| window.CodeMirror.keyMap.default["Ctrl-Q"] = this.toggleFullScreen; | ||
| window.CodeMirror.keyMap.default[this.getAttribute('fs-combo')] = this.toggleFullScreen; | ||
| window.CodeMirror.keyMap.default["Esc"] = this.closeFullScreen; | ||
|
|
||
| // 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 || ''); | ||
|
|
||
| if (mode) { | ||
| window.CodeMirror.autoLoadMode(editor, mode.mode); | ||
| editor.setOption('mode', mode.mime); | ||
| } else { | ||
| window.CodeMirror.autoLoadMode(editor, editor.options.mode); | ||
| } | ||
|
|
||
| // Handle gutter clicks (place or remove a marker). | ||
| editor.on("gutterClick", function (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 : this.makeMarker()); | ||
| }); | ||
|
|
||
| // Some browsers do something weird with the fieldset which doesn't work well with CodeMirror. Fix it. | ||
| if (this.parentNode.tagName.toLowerCase() === 'fieldset') { | ||
| this.parentNode.style.minWidth = 0; | ||
| } | ||
| }); | ||
|
|
||
| /** Register Editor */ | ||
| this.instance = window.CodeMirror.fromTextArea(this.element, this.options); | ||
| Joomla.editors.instances[this.element.id] = this.instance; | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| disconnectedCallback() { | ||
| // Remove from the Joomla API | ||
| delete Joomla.editors.instances[this.element.id]; | ||
| } | ||
|
|
||
| refresh(element) { | ||
| this.instance = window.CodeMirror.fromTextArea(element, this.options); | ||
| } | ||
|
|
||
| rafAsync() { | ||
| return new Promise(resolve => { | ||
| requestAnimationFrame(resolve); | ||
| }); | ||
| } | ||
|
|
||
| async checkElement(string1, string2) { | ||
| if (string2) { | ||
| while (typeof window[string1][string2] !== 'function') { | ||
| await this.rafAsync() | ||
| } | ||
| } else { | ||
| while (typeof window[string1] !== 'function') { | ||
| await this.rafAsync() | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| toggleFullScreen() { | ||
| this.instance.setOption("fullScreen", !this.instance.getOption("fullScreen")); | ||
| } | ||
|
|
||
| closeFullScreen() { | ||
| this.instance.getOption("fullScreen") && this.instance.setOption("fullScreen", false); | ||
| } | ||
|
|
||
| makeMarker() { | ||
| const marker = document.createElement("div"); | ||
| marker.className = "CodeMirror-markergutter-mark"; | ||
| return marker; | ||
| } | ||
| constructor() { | ||
| super(); | ||
|
|
||
| this.instance = ''; | ||
| this.cm = ''; | ||
| this.host = window.location.origin; | ||
| this.element = this.querySelector('textarea'); | ||
| this.refresh = this.refresh.bind(this); | ||
| this.toggleFullScreen = this.toggleFullScreen.bind(this); | ||
| this.closeFullScreen = this.closeFullScreen.bind(this); | ||
|
|
||
| // Append the editor script | ||
| if (!document.head.querySelector('#cm-editor')) { | ||
| const cmPath = this.getAttribute('editor'); | ||
| const script1 = document.createElement('script'); | ||
|
|
||
| script1.src = `${this.host}/${cmPath}`; | ||
| script1.id = 'cm-editor'; | ||
| script1.setAttribute('async', false); | ||
| document.head.insertBefore(script1, this.file); | ||
| } | ||
| } | ||
|
|
||
| static get observedAttributes() { | ||
| return ['options']; | ||
| } | ||
|
|
||
| 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 | ||
| } | ||
| } | ||
|
|
||
| connectedCallback() { | ||
| const that = this; | ||
| this.checkElement('CodeMirror') | ||
| .then(() => { | ||
| // Append the addons script | ||
| if (!document.head.querySelector('#cm-addons')) { | ||
| const addonsPath = this.getAttribute('addons'); | ||
| const script2 = document.createElement('script'); | ||
|
|
||
| script2.src = `${this.host}/${addonsPath}`; | ||
| script2.id = 'cm-addons'; | ||
| script2.setAttribute('async', false); | ||
| document.head.insertBefore(script2, this.file); | ||
| } | ||
|
|
||
| this.checkElement('CodeMirror', 'findModeByName') | ||
| .then(() => { | ||
| // 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(that.options.mode || ''); | ||
|
|
||
| if (mode) { | ||
| window.CodeMirror.autoLoadMode(editor, mode.mode); | ||
| editor.setOption('mode', mode.mime); | ||
| } else { | ||
| window.CodeMirror.autoLoadMode(editor, that.options.mode); | ||
| } | ||
|
|
||
| const map = { | ||
| 'Ctrl-Q': that.toggleFullScreen, | ||
| [that.getAttribute('fs-combo')]: that.toggleFullScreen, | ||
| Esc: that.closeFullScreen, | ||
| }; | ||
|
|
||
| editor.addKeyMap(map); | ||
|
|
||
| // 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 : this.makeMarker()); | ||
| }); | ||
|
|
||
| /* Some browsers do something weird with the fieldset which doesn't | ||
| work well with CodeMirror. Fix it. */ | ||
| if (this.parentNode.tagName.toLowerCase() === 'fieldset') { | ||
| this.parentNode.style.minWidth = 0; | ||
| } | ||
| }); | ||
|
|
||
| // Register Editor | ||
| this.instance = window.CodeMirror.fromTextArea(this.element, this.options); | ||
| Joomla.editors.instances[this.element.id] = this.instance; | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| disconnectedCallback() { | ||
| // Remove from the Joomla API | ||
| delete Joomla.editors.instances[this.element.id]; | ||
| } | ||
|
|
||
| refresh(element) { | ||
| this.instance = window.CodeMirror.fromTextArea(element, this.options); | ||
| } | ||
|
|
||
| rafAsync() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expected 'this' to be used by class method 'rafAsync' class-methods-use-this |
||
| return new Promise(resolve => requestAnimationFrame(resolve)); | ||
| } | ||
|
|
||
| async checkElement(string1, string2) { | ||
| if (string2) { | ||
| while (typeof window[string1][string2] !== 'function') { | ||
| await this.rafAsync(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unexpected There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unexpected |
||
| } | ||
| } else { | ||
| while (typeof window[string1] !== 'function') { | ||
| await this.rafAsync(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unexpected There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unexpected |
||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| toggleFullScreen() { | ||
| this.instance.setOption('fullScreen', !this.instance.getOption('fullScreen')); | ||
| } | ||
|
|
||
| closeFullScreen() { | ||
| this.instance.getOption('fullScreen'); | ||
| this.instance.setOption('fullScreen', false); | ||
| } | ||
|
|
||
| static makeMarker() { | ||
| const marker = document.createElement('div'); | ||
| marker.className = 'CodeMirror-markergutter-mark'; | ||
| return marker; | ||
| } | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expected 'this' to be used by class method 'rafAsync' class-methods-use-this