CM6: add destroy method, fix removal of textSelection, add more hooks

CodeMirror is meant to be used without WikiEditor, so we add a destroy()
method that does much the same things as CodeMirrorWikiEditor.

The hooks added with this patch aren't used by anything yet, but offer
feature parity with CodeMirrorWikiEditor, which fires the
'ext.CodeMirror.switch' hook. That hook can't be moved for
backwards-compatibility reasons.

The actual fix for T360075 here was to nullify the `textSelection` class
property.

Bug: T360075
Change-Id: Ife6a3f1ef90df66cd1bf8bab33c9ef3b39ed17a0
This commit is contained in:
MusikAnimal 2024-03-19 20:58:50 -04:00
parent d652f3d2a2
commit 6be3bf62f0
4 changed files with 47 additions and 17 deletions

File diff suppressed because one or more lines are too long

View file

@ -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=[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(),a=this.$textarea.is(":focus"),s=[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(s),requestAnimationFrame((function(){t.view.scrollDOM.scrollTop=o})),0!==r||0!==i){var d=e.EditorSelection.range(r,i),n=e.EditorView.scrollIntoView(d);n.value.isSnapshot=!0,this.view.dispatch({selection:e.EditorSelection.create([d]),effects:n})}a&&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(){this.view?(this.setCodeMirrorPreference(!1),this.destroy(),mw.hook("ext.CodeMirror.switch").fire(!1,this.$textarea)):(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()}));

View file

@ -258,6 +258,7 @@ class CodeMirror {
*
* @param {Extension|Extension[]} [extensions=this.defaultExtensions] Extensions to use.
* @fires CodeMirror~'ext.CodeMirror.initialize'
* @fires CodeMirror~'ext.CodeMirror.ready'
* @stable to call and override
*/
initialize( extensions = this.defaultExtensions ) {
@ -304,6 +305,49 @@ class CodeMirror {
// Also override textSelection() functions for the "real" hidden textarea to route to
// CodeMirror. We unregister this when switching to normal textarea mode.
this.$textarea.textSelection( 'register', this.cmTextSelection );
/**
* Called just after CodeMirror is initialized.
*
* @event CodeMirror~'ext.CodeMirror.ready'
* @param {jQuery} $view The CodeMirror view.
* @stable to use
*/
mw.hook( 'ext.CodeMirror.ready' ).fire( $( this.view.dom ) );
}
/**
* Destroy the CodeMirror instance and revert to the original textarea.
*
* @fires CodeMirror~'ext.CodeMirror.destroy'
* @stable to call and override
*/
destroy() {
const scrollTop = this.view.scrollDOM.scrollTop;
const hasFocus = this.view.hasFocus;
const { from, to } = this.view.state.selection.ranges[ 0 ];
$( 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();
if ( hasFocus ) {
this.$textarea.trigger( 'focus' );
}
this.$textarea.prop( 'selectionStart', Math.min( from, to ) )
.prop( 'selectionEnd', Math.max( to, from ) );
this.$textarea.scrollTop( scrollTop );
this.textSelection = null;
/**
* Called just after CodeMirror is destroyed and the original textarea is restored.
*
* @event CodeMirror~'ext.CodeMirror.destroy'
* @param {jQuery} $textarea The original textarea.
* @stable to use
*/
mw.hook( 'ext.CodeMirror.destroy' ).fire( this.$textarea );
}
/**

View file

@ -187,21 +187,7 @@ class CodeMirrorWikiEditor extends CodeMirror {
switchCodeMirror() {
if ( this.view ) {
this.setCodeMirrorPreference( false );
const scrollTop = this.view.scrollDOM.scrollTop;
const hasFocus = this.view.hasFocus;
const { from, to } = this.view.state.selection.ranges[ 0 ];
$( 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();
if ( hasFocus ) {
this.$textarea.trigger( 'focus' );
}
this.$textarea.prop( 'selectionStart', Math.min( from, to ) )
.prop( 'selectionEnd', Math.max( to, from ) );
this.$textarea.scrollTop( scrollTop );
this.destroy();
mw.hook( 'ext.CodeMirror.switch' ).fire( false, this.$textarea );
} else {
this.enableCodeMirror();