mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-23 13:56:44 +00:00
Merge "CM6: Add jsdoc build step, fix JSDoc annotations, and add @stable tags"
This commit is contained in:
commit
f91f08947c
|
@ -1,2 +1,3 @@
|
|||
/resources/lib/
|
||||
/vendor
|
||||
/docs
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@
|
|||
/composer.lock
|
||||
.eslintcache
|
||||
.env
|
||||
docs/
|
||||
|
|
36
README.md
36
README.md
|
@ -1,6 +1,4 @@
|
|||
# mediawiki/extensions/CodeMirror
|
||||
|
||||
Homepage: https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:CodeMirror
|
||||
CodeMirror 6 homepage: [https://www.mediawiki.org/wiki/Extension:CodeMirror/6](https://www.mediawiki.org/wiki/Extension:CodeMirror/6)
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -24,6 +22,7 @@ _NOTE: Consider using [Fresh](https://gerrit.wikimedia.org/g/fresh/) to run thes
|
|||
You'll want to keep this running in a separate terminal during development.
|
||||
* `npm run build` to compile the production assets. You *must* run this step before
|
||||
sending the patch or CI will fail (so that sources and built assets are in sync).
|
||||
* `npm run doc` to generate the API documentation.
|
||||
* `npm test` to run the linting tools, JavaScript unit tests, and build checks.
|
||||
* `npm run test:lint` for linting of JS/LESS/CSS.
|
||||
* `npm run test:lint:js` for linting of just JavaScript.
|
||||
|
@ -31,34 +30,9 @@ _NOTE: Consider using [Fresh](https://gerrit.wikimedia.org/g/fresh/) to run thes
|
|||
* `npm run test:i18n` for linting of i18n messages with banana-checker.
|
||||
* `npm run test:unit` for the new Jest unit tests.
|
||||
* `npm run selenium-test` for the Selenium tests.
|
||||
|
||||
Older QUnit tests are in `resources/mode/mediawiki/tests/qunit/`. These will
|
||||
eventually be moved over to `tests/qunit` and rewritten for CodeMirror 6.
|
||||
* Older QUnit tests are in `resources/mode/mediawiki/tests/qunit/`. These have been
|
||||
replaced and will be removed after the CodeMirror 6 upgrade.
|
||||
|
||||
## CodeMirror 6 change log
|
||||
|
||||
This is a list of changes that either come by default with the CodeMirror 6 upgrade,
|
||||
or changes of our that we deem as reasonable improvements.
|
||||
Some may be removed pending user feedback:
|
||||
|
||||
### Upstream changes
|
||||
|
||||
* Bracket matching now highlights unmatched brackets in red
|
||||
|
||||
### New MediaWiki mode features
|
||||
|
||||
* Closing HTML tags that highlighted as an error now also highlight the closing '>'
|
||||
* Allow link titles to be both emboldened and italicized.
|
||||
* Wikitext syntax highlighting is shown on protected pages
|
||||
([T301615](https://phabricator.wikimedia.org/T301615))
|
||||
|
||||
### Deprecations and other changes
|
||||
|
||||
* The `.cm-mw-mnemonic` CSS class has been renamed to `.cm-mw-html-entity`
|
||||
* The `.cm-mw-template-name-mnemonic` class has been removed.
|
||||
Use `.cm-mw-template-ground.cm-html-entity` instead.
|
||||
* Line-level styling for `<nowiki>`, `<pre>`, and any tag without an associated
|
||||
TagMode has been removed.
|
||||
* The browser's native search functionality (ala Ctrl+F) has been replaced with
|
||||
search functionality built into CodeMirror. This is necessary to maintain
|
||||
performance (see [T303664](https://phabricator.wikimedia.org/T303664)).
|
||||
* See [Extension:CodeMirror/6](https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:CodeMirror/6#Differences_from_CodeMirror_5)
|
||||
|
|
52
jsdoc.json
Normal file
52
jsdoc.json
Normal file
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"opts": {
|
||||
"encoding": "utf8",
|
||||
"destination": "docs/js",
|
||||
"package": "package.json",
|
||||
"readme": "README.md",
|
||||
"recurse": true,
|
||||
"template": "node_modules/jsdoc-wmf-theme"
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown"
|
||||
],
|
||||
"source": {
|
||||
"include": [ "src" ],
|
||||
"includePattern": ".+\\.js$"
|
||||
},
|
||||
"tags": {},
|
||||
"templates": {
|
||||
"cleverLinks": true,
|
||||
"default": {
|
||||
"useLongnameInNav": true
|
||||
},
|
||||
"wmf": {
|
||||
"maintitle": "CodeMirror",
|
||||
"repository": "https://gerrit.wikimedia.org/g/mediawiki/extensions/CodeMirror",
|
||||
"linkMap": {
|
||||
"Decoration": "https://codemirror.net/docs/ref/#view.Decoration",
|
||||
"DecorationSet": "https://codemirror.net/docs/ref/#view.DecorationSet",
|
||||
"EditorState": "https://codemirror.net/docs/ref/#state.EditorState",
|
||||
"EditorView": "https://codemirror.net/docs/ref/#view.EditorView",
|
||||
"Error": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error",
|
||||
"Extension": "https://codemirror.net/docs/ref/#state.Extension",
|
||||
"HTMLTextAreaElement": "https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement",
|
||||
"jQuery": "https://api.jquery.com/Types/#jQuery",
|
||||
"jQuery.fn.textSelection": "https://doc.wikimedia.org/mediawiki-core/master/js/jQueryPlugins.html#.textSelection",
|
||||
"KeyBinding": "https://codemirror.net/docs/ref/#view.KeyBinding",
|
||||
"LanguageSupport": "https://codemirror.net/docs/ref/#language.LanguageSupport",
|
||||
"PluginSpec": "https://codemirror.net/docs/ref/#view.PluginSpec",
|
||||
"Promise": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise",
|
||||
"RangeSet": "https://codemirror.net/docs/ref/#state.RangeSet",
|
||||
"StreamParser": "https://codemirror.net/docs/ref/#language.StreamParser",
|
||||
"StringStream": "https://codemirror.net/docs/ref/#language.StringStream",
|
||||
"SyntaxNode": "https://lezer.codemirror.net/docs/ref/#common.SyntaxNode",
|
||||
"Tag": "https://lezer.codemirror.net/docs/ref/#highlight.Tag",
|
||||
"TagStyle": "https://codemirror.net/docs/ref/#language.TagStyle",
|
||||
"Tooltip": "https://codemirror.net/docs/ref/#view.Tooltip",
|
||||
"Tree": "https://lezer.codemirror.net/docs/ref/#common.Tree",
|
||||
"ViewUpdate": "https://codemirror.net/docs/ref/#view.ViewUpdate"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5145
package-lock.json
generated
5145
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "codemirror",
|
||||
"name": "CodeMirror",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "rollup -c --watch",
|
||||
|
@ -12,7 +12,8 @@
|
|||
"test:unit": "jest",
|
||||
"check-built-assets": "{ git status src/ | grep \"nothing to commit, working tree clean\"; } && { echo 'CHECKING BUILD SOURCES ARE COMMITTED' && npm run build && git status resources/dist/ | grep \"nothing to commit, working tree clean\" || { npm run node-debug; false; }; }",
|
||||
"node-debug": "node -v && npm -v && echo 'ERROR: Please ensure that production assets have been built with `npm run build` and commited, and that you are using the correct version of Node/NPM.'",
|
||||
"selenium-test": "wdio tests/selenium/wdio.conf.js"
|
||||
"selenium-test": "wdio tests/selenium/wdio.conf.js",
|
||||
"doc": "jsdoc -c jsdoc.json"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.17.0"
|
||||
|
@ -41,6 +42,8 @@
|
|||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jquery": "3.7.1",
|
||||
"jsdoc": "4.0.2",
|
||||
"jsdoc-wmf-theme": "0.0.13",
|
||||
"rollup": "4.13.0",
|
||||
"rollup-plugin-copy": "3.5.0",
|
||||
"stylelint-config-wikimedia": "0.16.1",
|
||||
|
|
|
@ -1 +1 @@
|
|||
"use strict";var e=require("ext.CodeMirror.v6.lib"),t=require("ext.CodeMirror.v6"),r=require("ext.CodeMirror.v6.mode.mediawiki"),i=function(t){function r(t,i){var o;return e._classCallCheck(this,r),(o=e._callSuper(this,r,[t])).langExtension=i,o.useCodeMirror=mw.user.options.get("usecodemirror")>0,o}return e._inherits(r,t),e._createClass(r,[{key:"setCodeMirrorPreference",value:function(t){this.useCodeMirror=t,e._get(e._getPrototypeOf(r.prototype),"setCodeMirrorPreference",this).call(this,t)}},{key:"enableCodeMirror",value:function(){var t=this;if(!this.view){var r=this.$textarea.prop("selectionStart"),i=this.$textarea.prop("selectionEnd"),o=this.$textarea.scrollTop(),s=this.$textarea.is(":focus"),a=[].concat(e._toConsumableArray(this.defaultExtensions),[this.langExtension,e.EditorView.domEventHandlers({blur:function(){return t.$textarea.triggerHandler("blur")},focus:function(){return t.$textarea.triggerHandler("focus")}}),e.EditorView.lineWrapping]);if(this.initialize(a),requestAnimationFrame((function(){t.view.scrollDOM.scrollTop=o})),0!==r||0!==i){var n=e.EditorSelection.range(r,i),d=e.EditorView.scrollIntoView(n);d.value.isSnapshot=!0,this.view.dispatch({selection:e.EditorSelection.create([n]),effects:d})}s&&this.view.focus(),mw.hook("ext.CodeMirror.switch").fire(!0,$(this.view.dom))}}},{key:"addCodeMirrorToWikiEditor",value:function(){var e=this,t=this.$textarea.data("wikiEditor-context"),r=t&&t.modules&&t.modules.toolbar;r&&(this.$textarea.wikiEditor("addToToolbar",{section:"main",groups:{codemirror:{tools:{CodeMirror:{label:mw.msg("codemirror-toggle-label"),type:"toggle",oouiIcon:"highlight",action:{type:"callback",execute:function(){return e.switchCodeMirror()}}}}}}}),r.$toolbar.find(".tool[rel=CodeMirror]").attr("id","mw-editbutton-codemirror"),this.readOnly&&this.$textarea.data("wikiEditor-context").$ui.addClass("ext-codemirror-readonly"),this.useCodeMirror&&this.enableCodeMirror(),this.updateToolbarButton(),this.logUsage({editor:"wikitext",enabled:this.useCodeMirror,toggled:!1,edit_start_ts_ms:1e3*parseInt($('input[name="wpStarttime"]').val(),10)||0}))}},{key:"updateToolbarButton",value:function(){var e=$("#mw-editbutton-codemirror");e.toggleClass("mw-editbutton-codemirror-active",this.useCodeMirror),e.data("setActive")&&e.data("setActive")(this.useCodeMirror)}},{key:"switchCodeMirror",value:function(){if(this.view){this.setCodeMirrorPreference(!1);var e=this.view.scrollDOM.scrollTop,t=this.view.hasFocus,r=this.view.state.selection.ranges[0],i=r.from,o=r.to;$(this.view.dom).textSelection("unregister"),this.$textarea.textSelection("unregister"),this.$textarea.val(this.view.state.doc.toString()),this.view.destroy(),this.view=null,this.$textarea.show(),t&&this.$textarea.trigger("focus"),this.$textarea.prop("selectionStart",Math.min(i,o)).prop("selectionEnd",Math.max(o,i)),this.$textarea.scrollTop(e),mw.hook("ext.CodeMirror.switch").fire(!1,this.$textarea)}else this.enableCodeMirror(),this.setCodeMirrorPreference(!0);this.updateToolbarButton(),this.logUsage({editor:"wikitext",enabled:this.useCodeMirror,toggled:!0,edit_start_ts_ms:1e3*parseInt($('input[name="wpStarttime"]').val(),10)||0})}}]),r}(t);mw.loader.getState("ext.wikiEditor")&&mw.hook("wikiEditor.toolbarReady").add((function(e){new i(e,r({bidiIsolation:"rtl"===e.attr("dir")})).addCodeMirrorToWikiEditor()}));
|
||||
"use strict";var e=require("ext.CodeMirror.v6.lib"),t=require("ext.CodeMirror.v6"),r=require("ext.CodeMirror.v6.mode.mediawiki"),i=function(t){function r(t,i){var o;return e._classCallCheck(this,r),(o=e._callSuper(this,r,[t])).langExtension=i,o.useCodeMirror=mw.user.options.get("usecodemirror")>0,o}return e._inherits(r,t),e._createClass(r,[{key:"setCodeMirrorPreference",value:function(t){this.useCodeMirror=t,e._get(e._getPrototypeOf(r.prototype),"setCodeMirrorPreference",this).call(this,t)}},{key:"enableCodeMirror",value:function(){var t=this;if(!this.view){var r=this.$textarea.prop("selectionStart"),i=this.$textarea.prop("selectionEnd"),o=this.$textarea.scrollTop(),s=this.$textarea.is(":focus"),a=[this.defaultExtensions,this.langExtension,e.EditorView.domEventHandlers({blur:function(){return t.$textarea.triggerHandler("blur")},focus:function(){return t.$textarea.triggerHandler("focus")}}),e.EditorView.lineWrapping];if(this.initialize(a),requestAnimationFrame((function(){t.view.scrollDOM.scrollTop=o})),0!==r||0!==i){var n=e.EditorSelection.range(r,i),d=e.EditorView.scrollIntoView(n);d.value.isSnapshot=!0,this.view.dispatch({selection:e.EditorSelection.create([n]),effects:d})}s&&this.view.focus(),mw.hook("ext.CodeMirror.switch").fire(!0,$(this.view.dom))}}},{key:"addCodeMirrorToWikiEditor",value:function(){var e=this,t=this.$textarea.data("wikiEditor-context"),r=t&&t.modules&&t.modules.toolbar;r&&(this.$textarea.wikiEditor("addToToolbar",{section:"main",groups:{codemirror:{tools:{CodeMirror:{label:mw.msg("codemirror-toggle-label"),type:"toggle",oouiIcon:"highlight",action:{type:"callback",execute:function(){return e.switchCodeMirror()}}}}}}}),r.$toolbar.find(".tool[rel=CodeMirror]").attr("id","mw-editbutton-codemirror"),this.readOnly&&this.$textarea.data("wikiEditor-context").$ui.addClass("ext-codemirror-readonly"),this.useCodeMirror&&this.enableCodeMirror(),this.updateToolbarButton(),this.logUsage({editor:"wikitext",enabled:this.useCodeMirror,toggled:!1,edit_start_ts_ms:1e3*parseInt($('input[name="wpStarttime"]').val(),10)||0}))}},{key:"updateToolbarButton",value:function(){var e=$("#mw-editbutton-codemirror");e.toggleClass("mw-editbutton-codemirror-active",this.useCodeMirror),e.data("setActive")&&e.data("setActive")(this.useCodeMirror)}},{key:"switchCodeMirror",value:function(){if(this.view){this.setCodeMirrorPreference(!1);var e=this.view.scrollDOM.scrollTop,t=this.view.hasFocus,r=this.view.state.selection.ranges[0],i=r.from,o=r.to;$(this.view.dom).textSelection("unregister"),this.$textarea.textSelection("unregister"),this.$textarea.val(this.view.state.doc.toString()),this.view.destroy(),this.view=null,this.$textarea.show(),t&&this.$textarea.trigger("focus"),this.$textarea.prop("selectionStart",Math.min(i,o)).prop("selectionEnd",Math.max(o,i)),this.$textarea.scrollTop(e),mw.hook("ext.CodeMirror.switch").fire(!1,this.$textarea)}else this.enableCodeMirror(),this.setCodeMirrorPreference(!0);this.updateToolbarButton(),this.logUsage({editor:"wikitext",enabled:this.useCodeMirror,toggled:!0,edit_start_ts_ms:1e3*parseInt($('input[name="wpStarttime"]').val(),10)||0})}}]),r}(t);mw.loader.getState("ext.wikiEditor")&&mw.hook("wikiEditor.toolbarReady").add((function(e){new i(e,r({bidiIsolation:"rtl"===e.attr("dir")})).addCodeMirrorToWikiEditor()}));
|
||||
|
|
|
@ -13,6 +13,7 @@ import { mwModeConfig } from './codemirror.mode.mediawiki.config';
|
|||
|
||||
/**
|
||||
* @type {Decoration}
|
||||
* @private
|
||||
*/
|
||||
const isolate = Decoration.mark( {
|
||||
class: 'cm-bidi-isolate',
|
||||
|
@ -22,6 +23,7 @@ const isolate = Decoration.mark( {
|
|||
/**
|
||||
* @param {EditorView} view
|
||||
* @return {RangeSet}
|
||||
* @private
|
||||
*/
|
||||
function computeIsolates( view ) {
|
||||
const set = new RangeSetBuilder();
|
||||
|
@ -55,17 +57,17 @@ function computeIsolates( view ) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @property {DecorationSet} isolates
|
||||
* @property {Tree} tree
|
||||
* @private
|
||||
*/
|
||||
class CodeMirrorBidiIsolation {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {EditorView} view
|
||||
* @param {EditorView} view The editor view.
|
||||
*/
|
||||
constructor( view ) {
|
||||
/** @type {DecorationSet} */
|
||||
this.isolates = computeIsolates( view );
|
||||
/** @type {Tree} */
|
||||
this.tree = syntaxTree( view.state );
|
||||
}
|
||||
|
||||
|
@ -84,6 +86,7 @@ class CodeMirrorBidiIsolation {
|
|||
|
||||
/**
|
||||
* @type {PluginSpec}
|
||||
* @private
|
||||
*/
|
||||
const bidiIsolationSpec = {
|
||||
provide: ( plugin ) => {
|
||||
|
@ -106,4 +109,14 @@ const bidiIsolationSpec = {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bidirectional isolation plugin for CodeMirror for use on RTL pages.
|
||||
* This ensures HTML and MediaWiki tags are always displayed left-to-right.
|
||||
*
|
||||
* Use this plugin by passing in `bidiIsolation: true` when instantiating
|
||||
* a [CodeMirrorModeMediaWiki]{@link CodeMirrorModeMediaWiki} object.
|
||||
*
|
||||
* @module CodeMirrorBidiIsolation
|
||||
* @see https://codemirror.net/examples/bidi/
|
||||
*/
|
||||
export default ViewPlugin.fromClass( CodeMirrorBidiIsolation, bidiIsolationSpec );
|
||||
|
|
|
@ -3,39 +3,71 @@ import { EditorView, drawSelection, lineNumbers, highlightSpecialChars, keymap }
|
|||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
||||
import { searchKeymap } from '@codemirror/search';
|
||||
import { bracketMatching } from '@codemirror/language';
|
||||
import CodemirrorTextSelection from './codemirror.textSelection';
|
||||
import CodeMirrorTextSelection from './codemirror.textSelection';
|
||||
|
||||
require( '../ext.CodeMirror.data.js' );
|
||||
|
||||
/**
|
||||
* @class CodeMirror
|
||||
* @property {jQuery} $textarea
|
||||
* @property {EditorView} view
|
||||
* @property {EditorState} state
|
||||
* @property {boolean} readOnly
|
||||
* @property {Function|null} editRecoveryHandler
|
||||
* @property {CodemirrorTextSelection} textSelection
|
||||
* Interface for the CodeMirror editor.
|
||||
*
|
||||
* @example
|
||||
* mw.loader.using( [
|
||||
* 'ext.CodeMirror.v6',
|
||||
* 'ext.CodeMirror.v6.mode.mediawiki'
|
||||
* ] ).then( ( require ) => {
|
||||
* const CodeMirror = require( 'ext.CodeMirror.v6' );
|
||||
* const mediawikiLang = require( 'ext.CodeMirror.v6.mode.mediawiki' );
|
||||
* const cm = new CodeMirror( myTextarea );
|
||||
* cm.initialize( [ cm.defaultExtensions, mediawikiLang() ] );
|
||||
* } );
|
||||
*/
|
||||
export default class CodeMirror {
|
||||
class CodeMirror {
|
||||
/**
|
||||
* @constructor
|
||||
* Instantiate a new CodeMirror instance.
|
||||
*
|
||||
* @param {HTMLTextAreaElement|jQuery|string} textarea Textarea to add syntax highlighting to.
|
||||
* @constructor
|
||||
*/
|
||||
constructor( textarea ) {
|
||||
/**
|
||||
* The textarea that CodeMirror is bound to.
|
||||
* @type {jQuery}
|
||||
*/
|
||||
this.$textarea = $( textarea );
|
||||
/**
|
||||
* The editor user interface.
|
||||
* @type {EditorView}
|
||||
*/
|
||||
this.view = null;
|
||||
/**
|
||||
* The editor state.
|
||||
* @type {EditorState}
|
||||
*/
|
||||
this.state = null;
|
||||
/**
|
||||
* Whether the textarea is read-only.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.readOnly = this.$textarea.prop( 'readonly' );
|
||||
/**
|
||||
* The [edit recovery]{@link https://www.mediawiki.org/wiki/Manual:Edit_Recovery} handler.
|
||||
* @type {Function|null}
|
||||
*/
|
||||
this.editRecoveryHandler = null;
|
||||
/**
|
||||
* jQuery.textSelection overrides for CodeMirror.
|
||||
* @type {CodeMirrorTextSelection}
|
||||
*/
|
||||
this.textSelection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default extensions used by CodeMirror.
|
||||
* Extensions here should be applicable to all theoretical uses of CodeMirror in MediaWiki.
|
||||
* Subclasses are safe to override this method if needed.
|
||||
*
|
||||
* @see https://codemirror.net/docs/ref/#state.Extension
|
||||
* @return {Extension[]}
|
||||
* @type {Extension|Extension[]}
|
||||
* @stable to call
|
||||
*/
|
||||
get defaultExtensions() {
|
||||
const extensions = [
|
||||
|
@ -77,8 +109,8 @@ export default class CodeMirror {
|
|||
* This extension sets the height of the CodeMirror editor to match the textarea.
|
||||
* Override this method to change the height of the editor.
|
||||
*
|
||||
* @return {Extension}
|
||||
* @stable
|
||||
* @type {Extension}
|
||||
* @stable to call and override
|
||||
*/
|
||||
get heightExtension() {
|
||||
return EditorView.theme( {
|
||||
|
@ -92,11 +124,12 @@ export default class CodeMirror {
|
|||
}
|
||||
|
||||
/**
|
||||
* This specifies which attributes get added to the .cm-content and .cm-editor elements.
|
||||
* This specifies which attributes get added to the `.cm-content` and `.cm-editor` elements.
|
||||
* Subclasses are safe to override this method, but attributes here are considered vital.
|
||||
*
|
||||
* @see https://codemirror.net/docs/ref/#view.EditorView^contentAttributes
|
||||
* @return {Extension}
|
||||
* @type {Extension}
|
||||
* @stable to call and override
|
||||
*/
|
||||
get contentAttributesExtension() {
|
||||
const classList = [];
|
||||
|
@ -140,7 +173,9 @@ export default class CodeMirror {
|
|||
* and we don't want localization to be overlooked by CodeMirror clients and subclasses.
|
||||
*
|
||||
* @see https://codemirror.net/examples/translate/
|
||||
* @return {Extension}
|
||||
* @type {Extension}
|
||||
* @stable to call. Instead of overriding, pass in an additional `EditorState.phrases.of()`
|
||||
* when calling `initialize()`.
|
||||
*/
|
||||
get phrasesExtension() {
|
||||
return EditorState.phrases.of( {
|
||||
|
@ -165,7 +200,8 @@ export default class CodeMirror {
|
|||
* which is the localization of 'codemirror-control-character' followed by the Unicode number.
|
||||
*
|
||||
* @see https://codemirror.net/docs/ref/#view.highlightSpecialChars
|
||||
* @return {Extension}
|
||||
* @type {Extension}
|
||||
* @stable to call
|
||||
*/
|
||||
get specialCharsExtension() {
|
||||
// Keys are the decimal unicode number, values are the messages.
|
||||
|
@ -220,10 +256,20 @@ export default class CodeMirror {
|
|||
/**
|
||||
* Setup CodeMirror and add it to the DOM. This will hide the original textarea.
|
||||
*
|
||||
* @param {Extension[]} extensions
|
||||
* @stable
|
||||
* @param {Extension|Extension[]} [extensions=this.defaultExtensions] Extensions to use.
|
||||
* @fires CodeMirror~'ext.CodeMirror.initialize'
|
||||
* @stable to call and override
|
||||
*/
|
||||
initialize( extensions = this.defaultExtensions ) {
|
||||
/**
|
||||
* Called just before CodeMirror is initialized.
|
||||
* This can be used to manipulate the DOM to suit CodeMirror
|
||||
* (i.e. if you manipulate WikiEditor's DOM, you may need this).
|
||||
*
|
||||
* @event CodeMirror~'ext.CodeMirror.initialize'
|
||||
* @param {jQuery} $textarea The textarea that CodeMirror is bound to.
|
||||
* @stable to use
|
||||
*/
|
||||
mw.hook( 'ext.CodeMirror.initialize' ).fire( this.$textarea );
|
||||
mw.hook( 'editRecovery.loadEnd' ).add( ( data ) => {
|
||||
this.editRecoveryHandler = data.fieldChangeHandler;
|
||||
|
@ -264,7 +310,7 @@ export default class CodeMirror {
|
|||
* Log usage of CodeMirror.
|
||||
*
|
||||
* @param {Object} data
|
||||
* @stable
|
||||
* @stable to call
|
||||
*/
|
||||
logUsage( data ) {
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -284,7 +330,7 @@ export default class CodeMirror {
|
|||
* Save CodeMirror enabled preference.
|
||||
*
|
||||
* @param {boolean} prefValue True, if CodeMirror should be enabled by default, otherwise false.
|
||||
* @stable
|
||||
* @stable to call and override
|
||||
*/
|
||||
setCodeMirrorPreference( prefValue ) {
|
||||
// Skip for unnamed users
|
||||
|
@ -298,12 +344,13 @@ export default class CodeMirror {
|
|||
/**
|
||||
* jQuery.textSelection overrides for CodeMirror.
|
||||
*
|
||||
* @see https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/jQuery.plugin.textSelection
|
||||
* @return {Object}
|
||||
* @see jQuery.fn.textSelection
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
get cmTextSelection() {
|
||||
if ( !this.textSelection ) {
|
||||
this.textSelection = new CodemirrorTextSelection( this.view );
|
||||
this.textSelection = new CodeMirrorTextSelection( this.view );
|
||||
}
|
||||
return {
|
||||
getContents: () => this.textSelection.getContents(),
|
||||
|
@ -317,3 +364,5 @@ export default class CodeMirror {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default CodeMirror;
|
||||
|
|
|
@ -4,12 +4,23 @@ import { TagStyle, StreamParser } from '@codemirror/language';
|
|||
/**
|
||||
* Configuration for the MediaWiki highlighting mode for CodeMirror.
|
||||
* This is a separate class mainly to keep static configuration out of
|
||||
* the logic in CodeMirrorModeMediaWiki.
|
||||
* the logic in {@link CodeMirrorModeMediaWiki}.
|
||||
*
|
||||
* @class CodeMirrorModeMediaWikiConfig
|
||||
* @module CodeMirrorModeMediaWikiConfig
|
||||
*
|
||||
* @example
|
||||
* // within MediaWiki:
|
||||
* import { mwModeConfig } from 'ext.CodeMirror.v6.mode.mediawiki';
|
||||
* // Reference tags by their constants in the tags property.
|
||||
* if ( tag === mwModeConfig.tags.htmlTagBracket ) {
|
||||
* // …
|
||||
* }
|
||||
*/
|
||||
class CodeMirrorModeMediaWikiConfig {
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor() {
|
||||
this.extHighlightStyles = [];
|
||||
this.tokenTable = this.defaultTokenTable;
|
||||
|
@ -23,6 +34,7 @@ class CodeMirrorModeMediaWikiConfig {
|
|||
* @see https://www.mediawiki.org/wiki/Extension:CodeMirror#Extension_integration
|
||||
* @param {string} tag
|
||||
* @param {Tag} [parent]
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
addTag( tag, parent = null ) {
|
||||
|
@ -39,6 +51,7 @@ class CodeMirrorModeMediaWikiConfig {
|
|||
*
|
||||
* @param {string} token
|
||||
* @param {Tag} [parent]
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
addToken( token, parent = null ) {
|
||||
|
@ -88,7 +101,8 @@ class CodeMirrorModeMediaWikiConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Mapping of MediaWiki-esque token identifiers to a standardized lezer highlighting tag.
|
||||
* Mapping of MediaWiki-esque token identifiers to
|
||||
* [standardized lezer highlighting tags]{@link https://lezer.codemirror.net/docs/ref/#highlight.tags}.
|
||||
* Values are one of the default highlighting tags. The idea is to use as many default tags as
|
||||
* possible so that theming (such as dark mode) can be applied with minimal effort. The
|
||||
* semantic meaning of the tag may not really match how it is used, but as per CodeMirror docs,
|
||||
|
@ -99,8 +113,9 @@ class CodeMirrorModeMediaWikiConfig {
|
|||
* in highlightStyle().
|
||||
*
|
||||
* @see https://lezer.codemirror.net/docs/ref/#highlight.tags
|
||||
* @member CodeMirrorModeMediaWikiConfig
|
||||
* @type {Object<string>}
|
||||
* @return {Object<string>}
|
||||
* @internal
|
||||
*/
|
||||
get tags() {
|
||||
return {
|
||||
|
@ -195,6 +210,7 @@ class CodeMirrorModeMediaWikiConfig {
|
|||
* @see https://codemirror.net/docs/ref/#language.StreamParser.tokenTable
|
||||
* @see https://lezer.codemirror.net/docs/ref/#highlight.Tag%5Edefine
|
||||
* @return {Object<Tag>}
|
||||
* @internal
|
||||
*/
|
||||
get defaultTokenTable() {
|
||||
return {
|
||||
|
@ -229,6 +245,7 @@ class CodeMirrorModeMediaWikiConfig {
|
|||
* @see https://codemirror.net/docs/ref/#language.TagStyle
|
||||
* @param {StreamParser} context
|
||||
* @return {TagStyle[]}
|
||||
* @internal
|
||||
*/
|
||||
getTagStyles( context ) {
|
||||
return [
|
||||
|
@ -491,4 +508,8 @@ class CodeMirrorModeMediaWikiConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @member CodeMirrorModeMediaWikiConfig
|
||||
* @type {CodeMirrorModeMediaWikiConfig}
|
||||
*/
|
||||
export const mwModeConfig = new CodeMirrorModeMediaWikiConfig();
|
||||
|
|
|
@ -12,13 +12,26 @@ import templateFoldingExtension from './codemirror.templateFolding';
|
|||
import bidiIsolationExtension from './codemirror.bidiIsolation';
|
||||
|
||||
/**
|
||||
* Adapted from the original CodeMirror 5 stream parser by Pavel Astakhov
|
||||
* MediaWiki language support for CodeMirror 6.
|
||||
* Adapted from the original CodeMirror 5 stream parser by Pavel Astakhov.
|
||||
*
|
||||
* @class CodeMirrorModeMediaWiki
|
||||
* @module CodeMirrorModeMediaWiki
|
||||
*
|
||||
* @example
|
||||
* mw.loader.using( [
|
||||
* 'ext.CodeMirror.v6',
|
||||
* 'ext.CodeMirror.v6.mode.mediawiki'
|
||||
* ] ).then( ( require ) => {
|
||||
* const CodeMirror = require( 'ext.CodeMirror.v6' );
|
||||
* const mediawikiLang = require( 'ext.CodeMirror.v6.mode.mediawiki' );
|
||||
* const cm = new CodeMirror( myTextarea );
|
||||
* cm.initialize( [ cm.defaultExtensions, mediawikiLang() ] );
|
||||
* } );
|
||||
*/
|
||||
class CodeMirrorModeMediaWiki {
|
||||
/**
|
||||
* @param {Object} config
|
||||
* @param {Object} config MediaWiki configuration as generated by DataScript.php
|
||||
* @internal
|
||||
*/
|
||||
constructor( config ) {
|
||||
this.config = config;
|
||||
|
@ -45,6 +58,8 @@ class CodeMirrorModeMediaWiki {
|
|||
* Register the ground tokens. These aren't referenced directly in the StreamParser, nor do
|
||||
* they have a parent Tag, so we don't need them as constants like we do for other tokens.
|
||||
* See this.makeLocalStyle() for how these tokens are used.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
registerGroundTokens() {
|
||||
[
|
||||
|
@ -735,6 +750,7 @@ class CodeMirrorModeMediaWiki {
|
|||
/**
|
||||
* @param {string} style
|
||||
* @return {string|Function}
|
||||
* @private
|
||||
*/
|
||||
eatWikiText( style ) {
|
||||
return ( stream, state ) => {
|
||||
|
@ -1026,6 +1042,7 @@ class CodeMirrorModeMediaWiki {
|
|||
* @see https://phabricator.wikimedia.org/T108455
|
||||
*
|
||||
* @param {StringStream} stream
|
||||
* @private
|
||||
*/
|
||||
prepareItalicForCorrection( stream ) {
|
||||
// See Parser::doQuotes() in MediaWiki Core, it works similarly.
|
||||
|
@ -1058,6 +1075,7 @@ class CodeMirrorModeMediaWiki {
|
|||
/**
|
||||
* @see https://codemirror.net/docs/ref/#language.StreamParser
|
||||
* @return {StreamParser}
|
||||
* @private
|
||||
*/
|
||||
get mediawiki() {
|
||||
return {
|
||||
|
@ -1067,6 +1085,7 @@ class CodeMirrorModeMediaWiki {
|
|||
* Initial State for the parser.
|
||||
*
|
||||
* @return {Object}
|
||||
* @private
|
||||
*/
|
||||
startState: () => {
|
||||
return {
|
||||
|
@ -1087,6 +1106,7 @@ class CodeMirrorModeMediaWiki {
|
|||
*
|
||||
* @param {Object} state
|
||||
* @return {Object}
|
||||
* @private
|
||||
*/
|
||||
copyState: ( state ) => {
|
||||
return {
|
||||
|
@ -1109,6 +1129,7 @@ class CodeMirrorModeMediaWiki {
|
|||
* @param {StringStream} stream
|
||||
* @param {Object} state
|
||||
* @return {string|null}
|
||||
* @private
|
||||
*/
|
||||
token: ( stream, state ) => {
|
||||
let style, p, t, f,
|
||||
|
@ -1195,6 +1216,10 @@ class CodeMirrorModeMediaWiki {
|
|||
return t.style;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} state
|
||||
* @private
|
||||
*/
|
||||
blankLine: ( state ) => {
|
||||
if ( state.extMode && state.extMode.blankLine ) {
|
||||
state.extMode.blankLine( state.extState );
|
||||
|
@ -1206,30 +1231,24 @@ class CodeMirrorModeMediaWiki {
|
|||
*
|
||||
* @see CodeMirrorModeMediaWikiConfig.defaultTokenTable
|
||||
* @return {Object<Tag>}
|
||||
* @private
|
||||
*/
|
||||
tokenTable: this.tokenTable
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} mediaWikiLangConfig
|
||||
* @property {boolean} [bidiIsolation=false] Enable bidi isolation around HTML tags.
|
||||
* This should generally always be enabled on RTL pages, but it comes with a performance cost.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets a LanguageSupport instance for the MediaWiki mode.
|
||||
*
|
||||
* @example
|
||||
* import CodeMirror from './codemirror';
|
||||
* import { mediaWikiLang } from './codemirror.mode.mediawiki';
|
||||
* const cm = new CodeMirror( textarea );
|
||||
* cm.initialize( [ ...cm.defaultExtensions, mediaWikiLang() ] );
|
||||
*
|
||||
* @param {mediaWikiLangConfig} [config] Configuration options for the MediaWiki mode.
|
||||
* @member CodeMirrorModeMediaWiki
|
||||
* @method
|
||||
* @param {Object} [config] Configuration options for the MediaWiki mode.
|
||||
* @param {boolean} [config.bidiIsolation=false] Enable bidi isolation around HTML tags.
|
||||
* This should generally always be enabled on RTL pages, but it comes with a performance cost.
|
||||
* @param {Object|null} [mwConfig] Ignore; used only by unit tests.
|
||||
* @return {LanguageSupport}
|
||||
* @stable to call
|
||||
*/
|
||||
export default ( config = { bidiIsolation: false }, mwConfig = null ) => {
|
||||
mwConfig = mwConfig || mw.config.get( 'extCodeMirrorConfig' );
|
||||
|
|
|
@ -8,25 +8,29 @@ import { mwModeConfig as modeConfig } from './codemirror.mode.mediawiki.config';
|
|||
* Check if a SyntaxNode is a template bracket (`{{` or `}}`)
|
||||
* @param {SyntaxNode} node The SyntaxNode to check
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
const isBracket = ( node ) => node.name.split( '_' ).includes( modeConfig.tags.templateBracket ),
|
||||
/**
|
||||
* Check if a SyntaxNode is a template delimiter (`|`)
|
||||
* @param {SyntaxNode} node The SyntaxNode to check
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
isDelimiter = ( node ) => node.name.split( '_' ).includes( modeConfig.tags.templateDelimiter ),
|
||||
/**
|
||||
* Check if a SyntaxNode is part of a template, except for the brackets
|
||||
* @param {SyntaxNode} node The SyntaxNode to check
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
isTemplate = ( node ) => /-template[a-z\d-]+ground/u.test( node.name ) && !isBracket( node ),
|
||||
/**
|
||||
* Update the stack of opening (+) or closing (-) brackets
|
||||
* @param {EditorState} state EditorState instance
|
||||
* @param {SyntaxNode} node The SyntaxNode of the bracket
|
||||
* @return {1|-1}
|
||||
* @return {number}
|
||||
* @private
|
||||
*/
|
||||
stackUpdate = ( state, node ) => state.sliceDoc( node.from, node.from + 1 ) === '{' ? 1 : -1;
|
||||
|
||||
|
@ -36,6 +40,7 @@ const isBracket = ( node ) => node.name.split( '_' ).includes( modeConfig.tags.t
|
|||
* @param {number|SyntaxNode} posOrNode Position or node
|
||||
* @param {Tree|null} [tree] Syntax tree
|
||||
* @return {{from: number, to: number}|null}
|
||||
* @private
|
||||
*/
|
||||
const foldable = ( state, posOrNode, tree ) => {
|
||||
if ( typeof posOrNode === 'number' ) {
|
||||
|
@ -109,6 +114,7 @@ const foldable = ( state, posOrNode, tree ) => {
|
|||
* Create a tooltip for folding a template
|
||||
* @param {EditorState} state EditorState instance
|
||||
* @return {Tooltip|null}
|
||||
* @private
|
||||
*/
|
||||
const create = ( state ) => {
|
||||
const { selection: { main: { head } } } = state,
|
||||
|
@ -146,7 +152,10 @@ const create = ( state ) => {
|
|||
return null;
|
||||
};
|
||||
|
||||
/** @type {KeyBinding[]} */
|
||||
/**
|
||||
* @type {KeyBinding[]}
|
||||
* @private
|
||||
*/
|
||||
const foldKeymap = [
|
||||
{
|
||||
// Fold the template at the selection/cursor
|
||||
|
@ -221,7 +230,14 @@ const foldKeymap = [
|
|||
{ key: 'Ctrl-Alt-]', run: unfoldAll }
|
||||
];
|
||||
|
||||
/** @type {Extension} */
|
||||
/**
|
||||
* CodeMirror extension providing
|
||||
* [template folding](https://www.mediawiki.org/wiki/Help:Extension:CodeMirror#Template_folding)
|
||||
* for the MediaWiki mode. This automatically applied when using {@link CodeMirrorModeMediaWiki}.
|
||||
*
|
||||
* @module CodeMirrorTemplateFolding
|
||||
* @type {Extension}
|
||||
*/
|
||||
export default [
|
||||
codeFolding( {
|
||||
placeholderDOM( view ) {
|
||||
|
|
|
@ -2,20 +2,26 @@ import { EditorView } from '@codemirror/view';
|
|||
import { EditorSelection } from '@codemirror/state';
|
||||
|
||||
/**
|
||||
* jQuery.textSelection implementation for CodeMirror.
|
||||
* [jQuery.textSelection]{@link jQuery.fn.textSelection} implementation for CodeMirror.
|
||||
* This is registered to both the textarea and the `.cm-editor` element.
|
||||
*
|
||||
* @see jQuery.fn.textSelection
|
||||
* @class CodemirrorTextSelection
|
||||
* @property {EditorView} view
|
||||
* @property {jQuery} $cmDom
|
||||
*/
|
||||
export default class CodemirrorTextSelection {
|
||||
class CodeMirrorTextSelection {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {EditorView} view
|
||||
*/
|
||||
constructor( view ) {
|
||||
/**
|
||||
* The CodeMirror view.
|
||||
* @type {EditorView}
|
||||
*/
|
||||
this.view = view;
|
||||
/**
|
||||
* The CodeMirror DOM.
|
||||
* @type {jQuery}
|
||||
*/
|
||||
this.$cmDom = $( view.dom );
|
||||
}
|
||||
|
||||
|
@ -23,6 +29,7 @@ export default class CodemirrorTextSelection {
|
|||
* Get the contents of the editor.
|
||||
*
|
||||
* @return {string}
|
||||
* @stable to call
|
||||
*/
|
||||
getContents() {
|
||||
return this.view.state.doc.toString();
|
||||
|
@ -33,6 +40,7 @@ export default class CodemirrorTextSelection {
|
|||
*
|
||||
* @param {string} content
|
||||
* @return {jQuery}
|
||||
* @stable to call
|
||||
*/
|
||||
setContents( content ) {
|
||||
this.view.dispatch( {
|
||||
|
@ -48,8 +56,11 @@ export default class CodemirrorTextSelection {
|
|||
/**
|
||||
* Get the current caret position.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.startAndEnd] Whether to return the start and end of the selection
|
||||
* instead of the caret position.
|
||||
* @return {number[]|number}
|
||||
* @stable to call
|
||||
*/
|
||||
getCaretPosition( options ) {
|
||||
if ( !options.startAndEnd ) {
|
||||
|
@ -65,6 +76,7 @@ export default class CodemirrorTextSelection {
|
|||
* Scroll the editor to the current caret position.
|
||||
*
|
||||
* @return {jQuery}
|
||||
* @stable to call
|
||||
*/
|
||||
scrollToCaretPosition() {
|
||||
const scrollEffect = EditorView.scrollIntoView( this.view.state.selection.main.head );
|
||||
|
@ -79,6 +91,7 @@ export default class CodemirrorTextSelection {
|
|||
* Get the selected text.
|
||||
*
|
||||
* @return {string}
|
||||
* @stable to call
|
||||
*/
|
||||
getSelection() {
|
||||
return this.view.state.sliceDoc(
|
||||
|
@ -91,7 +104,10 @@ export default class CodemirrorTextSelection {
|
|||
* Set the selected text.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {number} options.start The start of the selection.
|
||||
* @param {number} [options.end=options.start] The end of the selection.
|
||||
* @return {jQuery}
|
||||
* @stable to call
|
||||
*/
|
||||
setSelection( options ) {
|
||||
this.view.dispatch( {
|
||||
|
@ -106,6 +122,7 @@ export default class CodemirrorTextSelection {
|
|||
*
|
||||
* @param {string} value
|
||||
* @return {jQuery}
|
||||
* @stable to call
|
||||
*/
|
||||
replaceSelection( value ) {
|
||||
this.view.dispatch(
|
||||
|
@ -118,13 +135,23 @@ export default class CodemirrorTextSelection {
|
|||
* Encapsulate the selected text with the given values.
|
||||
*
|
||||
* This is intentionally a near-identical implementation to jQuery.textSelection,
|
||||
* except it uses CodeMirror's EditorState.changeByRange when there are multiple selections.
|
||||
* except it uses CodeMirror's
|
||||
* [EditorState.changeByRange](https://codemirror.net/docs/ref/#state.EditorState.changeByRange)
|
||||
* when there are multiple selections.
|
||||
*
|
||||
* @see jQuery.fn.textSelection.encapsulateSelection
|
||||
* @todo Add support for 'ownline', 'selectPeri' and 'splitlines' options.
|
||||
* @todo Add support for 'ownline' and 'splitlines' options.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} [options.pre] The text to insert before the cursor/selection.
|
||||
* @param {string} [options.post] The text to insert after the cursor/selection.
|
||||
* @param {string} [options.peri] Text to insert between pre and post and select afterwards.
|
||||
* @param {boolean} [options.replace=false] If there is a selection, replace it with peri
|
||||
* instead of leaving it alone.
|
||||
* @param {boolean} [options.selectPeri=true] Select the peri text if it was inserted.
|
||||
* @param {number} [options.selectionStart] Position to start selection at.
|
||||
* @param {number} [options.selectionEnd=options.selectionStart] Position to end selection at.
|
||||
* @return {jQuery}
|
||||
* @stable to call
|
||||
*/
|
||||
encapsulateSelection( options ) {
|
||||
let selectedText,
|
||||
|
@ -200,3 +227,5 @@ export default class CodemirrorTextSelection {
|
|||
return this.$cmDom;
|
||||
}
|
||||
}
|
||||
|
||||
export default CodeMirrorTextSelection;
|
||||
|
|
|
@ -4,19 +4,32 @@ import { EditorView } from '@codemirror/view';
|
|||
import { LanguageSupport } from '@codemirror/language';
|
||||
|
||||
/**
|
||||
* @class CodeMirrorWikiEditor
|
||||
* @property {LanguageSupport|Extension} langExtension
|
||||
* @property {boolean} useCodeMirror
|
||||
* CodeMirror integration with
|
||||
* [WikiEditor](https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:WikiEditor).
|
||||
*
|
||||
* Use this class if you want WikiEditor's toolbar. If you don't need the toolbar,
|
||||
* using {@link CodeMirror} directly will be considerably more efficient.
|
||||
*
|
||||
* @extends CodeMirror
|
||||
*/
|
||||
export default class CodeMirrorWikiEditor extends CodeMirror {
|
||||
class CodeMirrorWikiEditor extends CodeMirror {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {jQuery} $textarea
|
||||
* @param {LanguageSupport|Extension} langExtension
|
||||
* @param {jQuery} $textarea The textarea to replace with CodeMirror.
|
||||
* @param {LanguageSupport|Extension} langExtension Language support and its extension(s).
|
||||
* @stable to call and override
|
||||
*/
|
||||
constructor( $textarea, langExtension ) {
|
||||
super( $textarea );
|
||||
/**
|
||||
* Language support and its extension(s).
|
||||
* @type {LanguageSupport|Extension}
|
||||
*/
|
||||
this.langExtension = langExtension;
|
||||
/**
|
||||
* Whether CodeMirror is currently enabled.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.useCodeMirror = mw.user.options.get( 'usecodemirror' ) > 0;
|
||||
}
|
||||
|
||||
|
@ -30,7 +43,10 @@ export default class CodeMirrorWikiEditor extends CodeMirror {
|
|||
}
|
||||
|
||||
/**
|
||||
* Replaces the default textarea with CodeMirror
|
||||
* Replaces the default textarea with CodeMirror.
|
||||
*
|
||||
* @fires CodeMirrorWikiEditor~'ext.CodeMirror.switch'
|
||||
* @stable to call
|
||||
*/
|
||||
enableCodeMirror() {
|
||||
// If CodeMirror is already loaded, abort.
|
||||
|
@ -48,7 +64,7 @@ export default class CodeMirrorWikiEditor extends CodeMirror {
|
|||
* @see https://codemirror.net/docs/ref/#state.Extension
|
||||
*/
|
||||
const extensions = [
|
||||
...this.defaultExtensions,
|
||||
this.defaultExtensions,
|
||||
this.langExtension,
|
||||
EditorView.domEventHandlers( {
|
||||
blur: () => this.$textarea.triggerHandler( 'blur' ),
|
||||
|
@ -76,11 +92,22 @@ export default class CodeMirrorWikiEditor extends CodeMirror {
|
|||
this.view.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after CodeMirror is enabled or disabled in WikiEditor.
|
||||
*
|
||||
* @event CodeMirrorWikiEditor~'ext.CodeMirror.switch'
|
||||
* @param {boolean} enabled Whether CodeMirror is enabled.
|
||||
* @param {jQuery} $textarea The current "editor", either the
|
||||
* original textarea or the `.cm-editor` element.
|
||||
* @stable to use
|
||||
*/
|
||||
mw.hook( 'ext.CodeMirror.switch' ).fire( true, $( this.view.dom ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the CodeMirror button to WikiEditor
|
||||
* Adds the CodeMirror button to WikiEditor.
|
||||
*
|
||||
* @stable to call
|
||||
*/
|
||||
addCodeMirrorToWikiEditor() {
|
||||
const context = this.$textarea.data( 'wikiEditor-context' );
|
||||
|
@ -136,7 +163,9 @@ export default class CodeMirrorWikiEditor extends CodeMirror {
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates CodeMirror button on the toolbar according to the current state (on/off)
|
||||
* Updates CodeMirror button on the toolbar according to the current state (on/off).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateToolbarButton() {
|
||||
// eslint-disable-next-line no-jquery/no-global-selector
|
||||
|
@ -150,7 +179,10 @@ export default class CodeMirrorWikiEditor extends CodeMirror {
|
|||
}
|
||||
|
||||
/**
|
||||
* Enables or disables CodeMirror
|
||||
* Enables or disables CodeMirror.
|
||||
*
|
||||
* @fires CodeMirrorWikiEditor~'ext.CodeMirror.switch'
|
||||
* @stable to call
|
||||
*/
|
||||
switchCodeMirror() {
|
||||
if ( this.view ) {
|
||||
|
@ -186,3 +218,5 @@ export default class CodeMirrorWikiEditor extends CodeMirror {
|
|||
} );
|
||||
}
|
||||
}
|
||||
|
||||
export default CodeMirrorWikiEditor;
|
||||
|
|
Loading…
Reference in a new issue