mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2025-01-20 00:15:53 +00:00
Merge "CodeMirror: fix toggle-related issues"
This commit is contained in:
commit
aac752edc7
|
@ -92,6 +92,12 @@ class CodeMirror {
|
||||||
* @type {Function|null}
|
* @type {Function|null}
|
||||||
*/
|
*/
|
||||||
this.editRecoveryHandler = null;
|
this.editRecoveryHandler = null;
|
||||||
|
/**
|
||||||
|
* The form `submit` event handler.
|
||||||
|
*
|
||||||
|
* @type {Function|null}
|
||||||
|
*/
|
||||||
|
this.formSubmitEventHandler = null;
|
||||||
/**
|
/**
|
||||||
* jQuery.textSelection overrides for CodeMirror.
|
* jQuery.textSelection overrides for CodeMirror.
|
||||||
*
|
*
|
||||||
|
@ -464,13 +470,14 @@ class CodeMirror {
|
||||||
if ( !this.surface ) {
|
if ( !this.surface ) {
|
||||||
this.$textarea.hide();
|
this.$textarea.hide();
|
||||||
if ( this.$textarea[ 0 ].form ) {
|
if ( this.$textarea[ 0 ].form ) {
|
||||||
this.$textarea[ 0 ].form.addEventListener( 'submit', () => {
|
this.formSubmitEventHandler = () => {
|
||||||
this.$textarea.val( this.view.state.doc.toString() );
|
this.$textarea.val( this.view.state.doc.toString() );
|
||||||
const scrollTop = document.getElementById( 'wpScrolltop' );
|
const scrollTop = document.getElementById( 'wpScrolltop' );
|
||||||
if ( scrollTop ) {
|
if ( scrollTop ) {
|
||||||
scrollTop.value = this.view.scrollDOM.scrollTop;
|
scrollTop.value = this.view.scrollDOM.scrollTop;
|
||||||
}
|
}
|
||||||
} );
|
};
|
||||||
|
this.$textarea[ 0 ].form.addEventListener( 'submit', this.formSubmitEventHandler );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,7 +524,7 @@ class CodeMirror {
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
const scrollTop = this.view.scrollDOM.scrollTop;
|
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 ];
|
const { from, to } = this.view.state.selection.ranges[ 0 ];
|
||||||
$( this.view.dom ).textSelection( 'unregister' );
|
$( this.view.dom ).textSelection( 'unregister' );
|
||||||
this.$textarea.textSelection( 'unregister' );
|
this.$textarea.textSelection( 'unregister' );
|
||||||
|
@ -536,6 +543,12 @@ class CodeMirror {
|
||||||
this.$textarea.scrollTop( scrollTop );
|
this.$textarea.scrollTop( scrollTop );
|
||||||
this.textSelection = null;
|
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.
|
* Called just after CodeMirror is destroyed and the original textarea is restored.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const {
|
const {
|
||||||
|
Extension,
|
||||||
autocompletion,
|
autocompletion,
|
||||||
acceptCompletion,
|
acceptCompletion,
|
||||||
keymap
|
keymap
|
||||||
|
|
|
@ -1406,15 +1406,17 @@ const mediaWikiLang = ( config = { bidiIsolation: false }, mwConfig = null ) =>
|
||||||
if ( handler ) {
|
if ( handler ) {
|
||||||
mw.hook( 'ext.CodeMirror.ready' ).remove( handler );
|
mw.hook( 'ext.CodeMirror.ready' ).remove( handler );
|
||||||
}
|
}
|
||||||
handler = ( $textarea, cm ) => {
|
handler = ( _$textarea, cm ) => {
|
||||||
if ( config.templateFolding !== false ) {
|
if ( cm.view ) { // T380840
|
||||||
cm.preferences.registerExtension( 'templateFolding', templateFoldingExtension, cm.view );
|
if ( config.templateFolding !== false ) {
|
||||||
}
|
cm.preferences.registerExtension( 'templateFolding', templateFoldingExtension, cm.view );
|
||||||
if ( config.autocomplete !== false ) {
|
}
|
||||||
cm.preferences.registerExtension( 'autocomplete', autocompleteExtension, cm.view );
|
if ( config.autocomplete !== false ) {
|
||||||
}
|
cm.preferences.registerExtension( 'autocomplete', autocompleteExtension, cm.view );
|
||||||
if ( config.bidiIsolation ) {
|
}
|
||||||
cm.preferences.registerExtension( 'bidiIsolation', bidiIsolationExtension, cm.view );
|
if ( config.bidiIsolation ) {
|
||||||
|
cm.preferences.registerExtension( 'bidiIsolation', bidiIsolationExtension, cm.view );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
mw.hook( 'ext.CodeMirror.ready' ).add( handler );
|
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).
|
* @param {LanguageSupport|Extension} langExtension Language support and its extension(s).
|
||||||
* @stable to call and override
|
* @stable to call and override
|
||||||
*/
|
*/
|
||||||
constructor( $textarea, langExtension ) {
|
constructor( $textarea, langExtension = [] ) {
|
||||||
super( $textarea );
|
super( $textarea );
|
||||||
/**
|
/**
|
||||||
* Language support and its extension(s).
|
* Language support and its extension(s).
|
||||||
|
@ -56,6 +56,18 @@ class CodeMirrorWikiEditor extends CodeMirror {
|
||||||
* @type {Function|null}
|
* @type {Function|null}
|
||||||
*/
|
*/
|
||||||
this.realtimePreviewHandler = 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.
|
* The WikiEditor search button, which is usurped to open the CodeMirror search panel.
|
||||||
*
|
*
|
||||||
|
@ -192,12 +204,24 @@ class CodeMirrorWikiEditor extends CodeMirror {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
addRealtimePreviewHandler() {
|
addRealtimePreviewHandler() {
|
||||||
mw.hook( 'ext.WikiEditor.realtimepreview.enable' ).add( ( realtimePreview ) => {
|
this.realtimePreviewEnableHandler = ( realtimePreview ) => {
|
||||||
this.realtimePreviewHandler = realtimePreview.getEventHandler().bind( realtimePreview );
|
this.realtimePreviewHandler = realtimePreview.getEventHandler().bind( realtimePreview );
|
||||||
} );
|
};
|
||||||
mw.hook( 'ext.WikiEditor.realtimepreview.disable' ).add( () => {
|
this.realtimePreviewDisableHandler = () => {
|
||||||
this.realtimePreviewHandler = null;
|
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 ) {
|
if ( this.view ) {
|
||||||
this.setCodeMirrorPreference( false );
|
this.setCodeMirrorPreference( false );
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
this.removeRealtimePreviewHandler();
|
||||||
this.$searchBtn.replaceWith( this.$oldSearchBtn );
|
this.$searchBtn.replaceWith( this.$oldSearchBtn );
|
||||||
this.$textarea.wikiEditor( 'removeFromToolbar', {
|
this.$textarea.wikiEditor( 'removeFromToolbar', {
|
||||||
section: 'advanced',
|
section: 'advanced',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mw.loader = { getState: jest.fn() };
|
mw.loader = { getState: jest.fn() };
|
||||||
|
|
||||||
const CodeMirrorWikiEditor = require( '../../resources/codemirror.wikieditor.js' ),
|
const CodeMirrorWikiEditor = require( '../../resources/codemirror.wikieditor.js' ),
|
||||||
|
mediaWikiLang = require( '../../resources/codemirror.mediawiki.js' ),
|
||||||
$textarea = $( '<textarea>' )
|
$textarea = $( '<textarea>' )
|
||||||
.text( 'The Smashing Pumpkins' ),
|
.text( 'The Smashing Pumpkins' ),
|
||||||
cmWe = new CodeMirrorWikiEditor( $textarea );
|
cmWe = new CodeMirrorWikiEditor( $textarea );
|
||||||
|
@ -61,3 +62,61 @@ describe( 'updateToolbarButton', () => {
|
||||||
expect( btn.classList.contains( 'mw-editbutton-codemirror-active' ) ).toBeTruthy();
|
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.config.get = jest.fn().mockReturnValue( '1000+ edits' );
|
||||||
mw.track = jest.fn();
|
mw.track = jest.fn();
|
||||||
mw.Api.prototype.saveOption = 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' );
|
global.$ = require( 'jquery' );
|
||||||
$.fn.textSelection = () => {};
|
$.fn.textSelection = () => {};
|
||||||
window.matchMedia = jest.fn().mockReturnValue( { matches: false } );
|
window.matchMedia = jest.fn().mockReturnValue( { matches: false } );
|
||||||
|
|
Loading…
Reference in a new issue