mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-12-12 06:15:27 +00:00
Merge "CodeMirror: fix toggle-related issues"
This commit is contained in:
commit
aac752edc7
|
@ -92,6 +92,12 @@ class CodeMirror {
|
|||
* @type {Function|null}
|
||||
*/
|
||||
this.editRecoveryHandler = null;
|
||||
/**
|
||||
* The form `submit` event handler.
|
||||
*
|
||||
* @type {Function|null}
|
||||
*/
|
||||
this.formSubmitEventHandler = null;
|
||||
/**
|
||||
* jQuery.textSelection overrides for CodeMirror.
|
||||
*
|
||||
|
@ -464,13 +470,14 @@ class CodeMirror {
|
|||
if ( !this.surface ) {
|
||||
this.$textarea.hide();
|
||||
if ( this.$textarea[ 0 ].form ) {
|
||||
this.$textarea[ 0 ].form.addEventListener( 'submit', () => {
|
||||
this.formSubmitEventHandler = () => {
|
||||
this.$textarea.val( this.view.state.doc.toString() );
|
||||
const scrollTop = document.getElementById( 'wpScrolltop' );
|
||||
if ( scrollTop ) {
|
||||
scrollTop.value = this.view.scrollDOM.scrollTop;
|
||||
}
|
||||
} );
|
||||
};
|
||||
this.$textarea[ 0 ].form.addEventListener( 'submit', this.formSubmitEventHandler );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -517,7 +524,7 @@ class CodeMirror {
|
|||
*/
|
||||
destroy() {
|
||||
const scrollTop = this.view.scrollDOM.scrollTop;
|
||||
const hasFocus = this.view.hasFocus;
|
||||
const hasFocus = this.surface ? this.surface.getView().isFocused() : this.view.hasFocus;
|
||||
const { from, to } = this.view.state.selection.ranges[ 0 ];
|
||||
$( this.view.dom ).textSelection( 'unregister' );
|
||||
this.$textarea.textSelection( 'unregister' );
|
||||
|
@ -536,6 +543,12 @@ class CodeMirror {
|
|||
this.$textarea.scrollTop( scrollTop );
|
||||
this.textSelection = null;
|
||||
|
||||
// remove all hook handlers and event listeners
|
||||
if ( this.formSubmitEventHandler && this.$textarea[ 0 ].form ) {
|
||||
this.$textarea[ 0 ].form.removeEventListener( 'submit', this.formSubmitEventHandler );
|
||||
this.formSubmitEventHandler = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called just after CodeMirror is destroyed and the original textarea is restored.
|
||||
*
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const {
|
||||
Extension,
|
||||
autocompletion,
|
||||
acceptCompletion,
|
||||
keymap
|
||||
|
|
|
@ -1406,15 +1406,17 @@ const mediaWikiLang = ( config = { bidiIsolation: false }, mwConfig = null ) =>
|
|||
if ( handler ) {
|
||||
mw.hook( 'ext.CodeMirror.ready' ).remove( handler );
|
||||
}
|
||||
handler = ( $textarea, cm ) => {
|
||||
if ( config.templateFolding !== false ) {
|
||||
cm.preferences.registerExtension( 'templateFolding', templateFoldingExtension, cm.view );
|
||||
}
|
||||
if ( config.autocomplete !== false ) {
|
||||
cm.preferences.registerExtension( 'autocomplete', autocompleteExtension, cm.view );
|
||||
}
|
||||
if ( config.bidiIsolation ) {
|
||||
cm.preferences.registerExtension( 'bidiIsolation', bidiIsolationExtension, cm.view );
|
||||
handler = ( _$textarea, cm ) => {
|
||||
if ( cm.view ) { // T380840
|
||||
if ( config.templateFolding !== false ) {
|
||||
cm.preferences.registerExtension( 'templateFolding', templateFoldingExtension, cm.view );
|
||||
}
|
||||
if ( config.autocomplete !== false ) {
|
||||
cm.preferences.registerExtension( 'autocomplete', autocompleteExtension, cm.view );
|
||||
}
|
||||
if ( config.bidiIsolation ) {
|
||||
cm.preferences.registerExtension( 'bidiIsolation', bidiIsolationExtension, cm.view );
|
||||
}
|
||||
}
|
||||
};
|
||||
mw.hook( 'ext.CodeMirror.ready' ).add( handler );
|
||||
|
|
|
@ -36,7 +36,7 @@ class CodeMirrorWikiEditor extends CodeMirror {
|
|||
* @param {LanguageSupport|Extension} langExtension Language support and its extension(s).
|
||||
* @stable to call and override
|
||||
*/
|
||||
constructor( $textarea, langExtension ) {
|
||||
constructor( $textarea, langExtension = [] ) {
|
||||
super( $textarea );
|
||||
/**
|
||||
* Language support and its extension(s).
|
||||
|
@ -56,6 +56,18 @@ class CodeMirrorWikiEditor extends CodeMirror {
|
|||
* @type {Function|null}
|
||||
*/
|
||||
this.realtimePreviewHandler = null;
|
||||
/**
|
||||
* The `ext.WikiEditor.realtimepreview.enable` hook handler.
|
||||
*
|
||||
* @type {Function|null}
|
||||
*/
|
||||
this.realtimePreviewEnableHandler = null;
|
||||
/**
|
||||
* The `ext.WikiEditor.realtimepreview.disable` hook handler.
|
||||
*
|
||||
* @type {Function|null}
|
||||
*/
|
||||
this.realtimePreviewDisableHandler = null;
|
||||
/**
|
||||
* The WikiEditor search button, which is usurped to open the CodeMirror search panel.
|
||||
*
|
||||
|
@ -192,12 +204,24 @@ class CodeMirrorWikiEditor extends CodeMirror {
|
|||
* @private
|
||||
*/
|
||||
addRealtimePreviewHandler() {
|
||||
mw.hook( 'ext.WikiEditor.realtimepreview.enable' ).add( ( realtimePreview ) => {
|
||||
this.realtimePreviewEnableHandler = ( realtimePreview ) => {
|
||||
this.realtimePreviewHandler = realtimePreview.getEventHandler().bind( realtimePreview );
|
||||
} );
|
||||
mw.hook( 'ext.WikiEditor.realtimepreview.disable' ).add( () => {
|
||||
};
|
||||
this.realtimePreviewDisableHandler = () => {
|
||||
this.realtimePreviewHandler = null;
|
||||
} );
|
||||
};
|
||||
mw.hook( 'ext.WikiEditor.realtimepreview.enable' ).add( this.realtimePreviewEnableHandler );
|
||||
mw.hook( 'ext.WikiEditor.realtimepreview.disable' ).add( this.realtimePreviewDisableHandler );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Realtime Preview handler.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
removeRealtimePreviewHandler() {
|
||||
mw.hook( 'ext.WikiEditor.realtimepreview.enable' ).remove( this.realtimePreviewEnableHandler );
|
||||
mw.hook( 'ext.WikiEditor.realtimepreview.disable' ).remove( this.realtimePreviewDisableHandler );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -292,6 +316,7 @@ class CodeMirrorWikiEditor extends CodeMirror {
|
|||
if ( this.view ) {
|
||||
this.setCodeMirrorPreference( false );
|
||||
this.destroy();
|
||||
this.removeRealtimePreviewHandler();
|
||||
this.$searchBtn.replaceWith( this.$oldSearchBtn );
|
||||
this.$textarea.wikiEditor( 'removeFromToolbar', {
|
||||
section: 'advanced',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mw.loader = { getState: jest.fn() };
|
||||
|
||||
const CodeMirrorWikiEditor = require( '../../resources/codemirror.wikieditor.js' ),
|
||||
mediaWikiLang = require( '../../resources/codemirror.mediawiki.js' ),
|
||||
$textarea = $( '<textarea>' )
|
||||
.text( 'The Smashing Pumpkins' ),
|
||||
cmWe = new CodeMirrorWikiEditor( $textarea );
|
||||
|
@ -61,3 +62,61 @@ describe( 'updateToolbarButton', () => {
|
|||
expect( btn.classList.contains( 'mw-editbutton-codemirror-active' ) ).toBeTruthy();
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'Hook handlers and event listeners', () => {
|
||||
const textarea = document.createElement( 'textarea' ),
|
||||
editform = document.createElement( 'form' ),
|
||||
events = {};
|
||||
editform.append( textarea );
|
||||
editform.addEventListener = jest.fn( ( event, callback ) => {
|
||||
events[ event ] = callback;
|
||||
} );
|
||||
editform.removeEventListener = jest.fn( ( event ) => {
|
||||
delete events[ event ];
|
||||
} );
|
||||
const cmWe3 = new CodeMirrorWikiEditor( textarea );
|
||||
cmWe3.langExtension = mediaWikiLang( {
|
||||
bidiIsolation: false
|
||||
}, {
|
||||
tags: {},
|
||||
functionSynonyms: [ {}, {} ],
|
||||
doubleUnderscore: [ {}, {} ],
|
||||
urlProtocols: 'http://'
|
||||
} );
|
||||
cmWe3.$textarea.wikiEditor = jest.fn();
|
||||
|
||||
it( 'should remove submit event listener when CodeMirror is off', () => {
|
||||
cmWe3.switchCodeMirror();
|
||||
expect( typeof events.submit ).toBe( 'function' );
|
||||
cmWe3.switchCodeMirror();
|
||||
expect( events.submit ).toBeUndefined();
|
||||
} );
|
||||
|
||||
it( 'should remove realtime preview hook handler when CodeMirror is off', () => {
|
||||
cmWe3.switchCodeMirror();
|
||||
expect( mw.hook.mockHooks[ 'ext.WikiEditor.realtimepreview.enable' ].length ).toBe( 1 );
|
||||
expect( mw.hook.mockHooks[ 'ext.WikiEditor.realtimepreview.disable' ].length ).toBe( 1 );
|
||||
cmWe3.switchCodeMirror();
|
||||
expect( mw.hook.mockHooks[ 'ext.WikiEditor.realtimepreview.enable' ].length ).toBe( 0 );
|
||||
expect( mw.hook.mockHooks[ 'ext.WikiEditor.realtimepreview.disable' ].length ).toBe( 0 );
|
||||
} );
|
||||
|
||||
it( 'T380840', () => {
|
||||
cmWe3.switchCodeMirror();
|
||||
cmWe3.switchCodeMirror();
|
||||
expect( cmWe3.view ).toBeNull();
|
||||
mw.hook( 'ext.CodeMirror.ready' ).fire( cmWe3.$textarea, cmWe3 );
|
||||
} );
|
||||
|
||||
it( 'only 1 ext.CodeMirror.ready hook handler', () => {
|
||||
mediaWikiLang( {
|
||||
bidiIsolation: false
|
||||
}, {
|
||||
tags: {},
|
||||
functionSynonyms: [ {}, {} ],
|
||||
doubleUnderscore: [ {}, {} ],
|
||||
urlProtocols: 'http://'
|
||||
} );
|
||||
expect( mw.hook.mockHooks[ 'ext.CodeMirror.ready' ].length ).toBe( 1 );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -22,6 +22,26 @@ mw.user = Object.assign( mw.user, {
|
|||
mw.config.get = jest.fn().mockReturnValue( '1000+ edits' );
|
||||
mw.track = jest.fn();
|
||||
mw.Api.prototype.saveOption = jest.fn();
|
||||
mw.hook = jest.fn( ( name ) => ( {
|
||||
fire: jest.fn( ( ...args ) => {
|
||||
if ( mw.hook.mockHooks[ name ] ) {
|
||||
mw.hook.mockHooks[ name ].forEach( ( callback ) => callback( ...args ) );
|
||||
}
|
||||
} ),
|
||||
add: jest.fn( ( callback ) => {
|
||||
if ( !mw.hook.mockHooks[ name ] ) {
|
||||
mw.hook.mockHooks[ name ] = [];
|
||||
}
|
||||
mw.hook.mockHooks[ name ].push( callback );
|
||||
} ),
|
||||
remove: jest.fn( ( callback ) => {
|
||||
if ( mw.hook.mockHooks[ name ] ) {
|
||||
mw.hook.mockHooks[ name ] = mw.hook.mockHooks[ name ]
|
||||
.filter( ( cb ) => cb !== callback );
|
||||
}
|
||||
} )
|
||||
} ) );
|
||||
mw.hook.mockHooks = {};
|
||||
global.$ = require( 'jquery' );
|
||||
$.fn.textSelection = () => {};
|
||||
window.matchMedia = jest.fn().mockReturnValue( { matches: false } );
|
||||
|
|
Loading…
Reference in a new issue