X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Flib%2Foojs-ui%2Foojs-ui-windows.js;h=23db06aa3addf199b4e9ca04b704b35c024e5a30;hb=f7e1770fb832aa77bf4e16ce8cc815f2b24dd10d;hp=5a06841e1b0bc49d70412331d01ecc758c50babc;hpb=885628afcbac6fb6ee3fbb569357325c7207b652;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/lib/oojs-ui/oojs-ui-windows.js b/resources/lib/oojs-ui/oojs-ui-windows.js index 5a06841e1b..23db06aa3a 100644 --- a/resources/lib/oojs-ui/oojs-ui-windows.js +++ b/resources/lib/oojs-ui/oojs-ui-windows.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.21.2 + * OOjs UI v0.22.1 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2017 OOjs UI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2017-04-26T01:05:10Z + * Date: 2017-05-31T19:07:36Z */ ( function ( OO ) { @@ -851,6 +851,67 @@ OO.ui.Process.prototype.next = function ( step, context ) { return this; }; +/** + * A window instance represents the life cycle for one single opening of a window + * until its closing. + * + * While OO.ui.WindowManager will reuse OO.ui.Window objects, each time a window is + * opened, a new lifecycle starts. + * + * For more information, please see the [OOjs UI documentation on MediaWiki] [1]. + * + * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows + * + * @class + * + * @constructor + */ +OO.ui.WindowInstance = function OOuiWindowInstance() { + var deferreds = { + opening: $.Deferred(), + opened: $.Deferred(), + closing: $.Deferred(), + closed: $.Deferred() + }; + + /** + * @private + * @property {Object} + */ + this.deferreds = deferreds; + + // Set these up as chained promises so that rejecting of + // an earlier stage automatically rejects the subsequent + // would-be stages as well. + + /** + * @property {jQuery.Promise} + */ + this.opening = deferreds.opening.promise(); + /** + * @property {jQuery.Promise} + */ + this.opened = this.opening.then( function () { + return deferreds.opened; + } ); + /** + * @property {jQuery.Promise} + */ + this.closing = this.opened.then( function () { + return deferreds.closing; + } ); + /** + * @property {jQuery.Promise} + */ + this.closed = this.closing.then( function () { + return deferreds.closed; + } ); +}; + +/* Setup */ + +OO.initClass( OO.ui.WindowInstance ); + /** * Window managers are used to open and close {@link OO.ui.Window windows} and control their presentation. * Managed windows are mutually exclusive. If a new window is opened while a current window is opening @@ -865,13 +926,11 @@ OO.ui.Process.prototype.next = function ( step, context ) { * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window. * * - an `opening` event is emitted with an `opening` promise - * - the #getSetupDelay method is called and the returned value is used to time a pause in execution before - * the window’s {@link OO.ui.Window#getSetupProcess getSetupProcess} method is called on the - * window and its result executed + * - the #getSetupDelay method is called and the returned value is used to time a pause in execution before the + * window’s {@link OO.ui.Window#method-setup setup} method is called which executes OO.ui.Window#getSetupProcess. * - a `setup` progress notification is emitted from the `opening` promise - * - the #getReadyDelay method is called the returned value is used to time a pause in execution before - * the window’s {@link OO.ui.Window#getReadyProcess getReadyProcess} method is called on the - * window and its result executed + * - the #getReadyDelay method is called the returned value is used to time a pause in execution before the + * window’s {@link OO.ui.Window#method-ready ready} method is called which executes OO.ui.Window#getReadyProcess. * - a `ready` progress notification is emitted from the `opening` promise * - the `opening` promise is resolved with an `opened` promise * @@ -921,9 +980,9 @@ OO.ui.WindowManager = function OoUiWindowManager( config ) { this.factory = config.factory; this.modal = config.modal === undefined || !!config.modal; this.windows = {}; - this.opening = null; - this.opened = null; - this.closing = null; + // Deprecated placeholder promise given to compatOpening in openWindow() + // that is resolved in closeWindow(). + this.compatOpened = null; this.preparingToOpen = null; this.preparingToClose = null; this.currentWindow = null; @@ -952,9 +1011,9 @@ OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter ); * * @event opening * @param {OO.ui.Window} win Window that's being opened - * @param {jQuery.Promise} opening An `opening` promise resolved with a value when the window is opened successfully. - * When the `opening` promise is resolved, the first argument of the value is an 'opened' promise, the second argument - * is the opening data. The `opening` promise emits `setup` and `ready` notifications when those processes are complete. + * @param {jQuery.Promise} opened A promise resolved with a value when the window is opened successfully. + * This promise also emits `setup` and `ready` notifications. When this promise is resolved, the first + * argument of the value is an 'closed' promise, the second argument is the opening data. * @param {Object} data Window opening data */ @@ -963,10 +1022,9 @@ OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter ); * * @event closing * @param {OO.ui.Window} win Window that's being closed - * @param {jQuery.Promise} closing A `closing` promise is resolved with a value when the window - * is closed successfully. The promise emits `hold` and `teardown` notifications when those - * processes are complete. When the `closing` promise is resolved, the first argument of its value - * is the closing data. + * @param {jQuery.Promise} closed A promise resolved with a value when the window is closed successfully. + * This promise also emits `hold` and `teardown` notifications. When this promise is resolved, the first + * argument of its value is the closing data. * @param {Object} data Window closing data */ @@ -1049,7 +1107,8 @@ OO.ui.WindowManager.prototype.afterWindowResize = function () { * @return {boolean} Window is opening */ OO.ui.WindowManager.prototype.isOpening = function ( win ) { - return win === this.currentWindow && !!this.opening && this.opening.state() === 'pending'; + return win === this.currentWindow && !!this.lifecycle && + this.lifecycle.opened.state() === 'pending'; }; /** @@ -1059,7 +1118,9 @@ OO.ui.WindowManager.prototype.isOpening = function ( win ) { * @return {boolean} Window is closing */ OO.ui.WindowManager.prototype.isClosing = function ( win ) { - return win === this.currentWindow && !!this.closing && this.closing.state() === 'pending'; + return win === this.currentWindow && !!this.lifecycle && + this.lifecycle.closing.state() === 'resolved' && + this.lifecycle.closed.state() === 'pending'; }; /** @@ -1069,7 +1130,9 @@ OO.ui.WindowManager.prototype.isClosing = function ( win ) { * @return {boolean} Window is opened */ OO.ui.WindowManager.prototype.isOpened = function ( win ) { - return win === this.currentWindow && !!this.opened && this.opened.state() === 'pending'; + return win === this.currentWindow && !!this.lifecycle && + this.lifecycle.opened.state() === 'resolved' && + this.lifecycle.closing.state() === 'pending'; }; /** @@ -1191,76 +1254,107 @@ OO.ui.WindowManager.prototype.getCurrentWindow = function () { * @param {Object} [data] Window opening data * @param {jQuery|null} [data.$returnFocusTo] Element to which the window will return focus when closed. * Defaults the current activeElement. If set to null, focus isn't changed on close. - * @return {jQuery.Promise} An `opening` promise resolved when the window is done opening. - * See {@link #event-opening 'opening' event} for more information about `opening` promises. + * @return {OO.ui.WindowInstance|jQuery.Promise} A lifecycle object representing this particular + * opening of the window. For backwards-compatibility, then object is also a Thenable that is resolved + * when the window is done opening, with nested promise for when closing starts. This behaviour + * is deprecated and is not compatible with jQuery 3 (T163510). * @fires opening */ -OO.ui.WindowManager.prototype.openWindow = function ( win, data ) { - var manager = this, - opening = $.Deferred(); +OO.ui.WindowManager.prototype.openWindow = function ( win, data, lifecycle, compatOpening ) { + var manager = this; data = data || {}; + // Internal parameter 'lifecycle' allows this method to always return + // a lifecycle even if the window still needs to be created + // asynchronously when 'win' is a string. + lifecycle = lifecycle || new OO.ui.WindowInstance(); + compatOpening = compatOpening || $.Deferred(); + + // Turn lifecycle into a Thenable for backwards-compatibility with + // the deprecated nested-promise behaviour (T163510). + [ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ] + .forEach( function ( method ) { + lifecycle[ method ] = function () { + OO.ui.warnDeprecation( + 'Using the return value of openWindow as a promise is deprecated. ' + + 'Use .openWindow( ... ).opening.' + method + '( ... ) instead.' + ); + return compatOpening[ method ].apply( this, arguments ); + }; + } ); + // Argument handling if ( typeof win === 'string' ) { - return this.getWindow( win ).then( function ( win ) { - return manager.openWindow( win, data ); - } ); + this.getWindow( win ).then( + function ( win ) { + manager.openWindow( win, data, lifecycle, compatOpening ); + }, + function ( err ) { + lifecycle.deferreds.opening.reject( err ); + } + ); + return lifecycle; } // Error handling if ( !this.hasWindow( win ) ) { - opening.reject( new OO.ui.Error( + compatOpening.reject( new OO.ui.Error( + 'Cannot open window: window is not attached to manager' + ) ); + lifecycle.deferreds.opening.reject( new OO.ui.Error( 'Cannot open window: window is not attached to manager' ) ); - } else if ( this.preparingToOpen || this.opening || this.opened ) { - opening.reject( new OO.ui.Error( + return lifecycle; + } + if ( this.preparingToOpen || ( this.lifecycle && this.lifecycle.closing.state() !== 'resolved' ) ) { + compatOpening.reject( new OO.ui.Error( + 'Cannot open window: another window is opening or open' + ) ); + lifecycle.deferreds.opening.reject( new OO.ui.Error( 'Cannot open window: another window is opening or open' ) ); + return lifecycle; } - // Window opening - if ( opening.state() !== 'rejected' ) { - // If a window is currently closing, wait for it to complete - this.preparingToOpen = $.when( this.closing ); - // Ensure handlers get called after preparingToOpen is set - this.preparingToOpen.done( function () { - if ( manager.modal ) { - manager.toggleGlobalEvents( true ); - manager.toggleAriaIsolation( true ); - } - manager.$returnFocusTo = data.$returnFocusTo !== undefined ? data.$returnFocusTo : $( document.activeElement ); - manager.currentWindow = win; - manager.opening = opening; - manager.preparingToOpen = null; - manager.emit( 'opening', win, opening, data ); - setTimeout( function () { - win.setup( data ).then( function () { - manager.updateWindowSize( win ); - manager.opening.notify( { state: 'setup' } ); - setTimeout( function () { - win.ready( data ).then( function () { - manager.opening.notify( { state: 'ready' } ); - manager.opening = null; - manager.opened = $.Deferred(); - opening.resolve( manager.opened.promise(), data ); - }, function () { - manager.opening = null; - manager.opened = $.Deferred(); - opening.reject(); - manager.closeWindow( win ); - } ); - }, manager.getReadyDelay() ); - }, function () { - manager.opening = null; - manager.opened = $.Deferred(); - opening.reject(); - manager.closeWindow( win ); - } ); - }, manager.getSetupDelay() ); - } ); - } + // If a window is currently closing, wait for it to complete + this.preparingToOpen = $.when( this.lifecycle && this.lifecycle.closed ); + // Ensure handlers get called after preparingToOpen is set + this.preparingToOpen.done( function () { + if ( manager.modal ) { + manager.toggleGlobalEvents( true ); + manager.toggleAriaIsolation( true ); + } + manager.$returnFocusTo = data.$returnFocusTo !== undefined ? data.$returnFocusTo : $( document.activeElement ); + manager.currentWindow = win; + manager.lifecycle = lifecycle; + manager.preparingToOpen = null; + manager.emit( 'opening', win, compatOpening, data ); + lifecycle.deferreds.opening.resolve( data ); + setTimeout( function () { + manager.compatOpened = $.Deferred(); + win.setup( data ).then( function () { + manager.updateWindowSize( win ); + compatOpening.notify( { state: 'setup' } ); + setTimeout( function () { + win.ready( data ).then( function () { + compatOpening.notify( { state: 'ready' } ); + lifecycle.deferreds.opened.resolve( data ); + compatOpening.resolve( manager.compatOpened.promise(), data ); + }, function () { + lifecycle.deferreds.opened.reject(); + compatOpening.reject(); + manager.closeWindow( win ); + } ); + }, manager.getReadyDelay() ); + }, function () { + lifecycle.deferreds.opened.reject(); + compatOpening.reject(); + manager.closeWindow( win ); + } ); + }, manager.getSetupDelay() ); + } ); - return opening.promise(); + return lifecycle; }; /** @@ -1268,15 +1362,17 @@ OO.ui.WindowManager.prototype.openWindow = function ( win, data ) { * * @param {OO.ui.Window|string} win Window object or symbolic name of window to close * @param {Object} [data] Window closing data - * @return {jQuery.Promise} A `closing` promise resolved when the window is done closing. - * See {@link #event-closing 'closing' event} for more information about closing promises. - * @throws {Error} An error is thrown if the window is not managed by the window manager. + * @return {OO.ui.WindowInstance|jQuery.Promise} A lifecycle object representing this particular + * opening of the window. For backwards-compatibility, the object is also a Thenable that is resolved + * when the window is done closing (T163510). * @fires closing */ OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) { - var manager = this, - closing = $.Deferred(), - opened; + var error, + manager = this, + compatClosing = $.Deferred(), + lifecycle = this.lifecycle, + compatOpened; // Argument handling if ( typeof win === 'string' ) { @@ -1286,56 +1382,78 @@ OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) { } // Error handling - if ( !win ) { - closing.reject( new OO.ui.Error( - 'Cannot close window: window is not attached to manager' - ) ); + if ( !lifecycle ) { + error = 'Cannot close window: no window is currently open'; + } else if ( !win ) { + error = 'Cannot close window: window is not attached to manager'; } else if ( win !== this.currentWindow ) { - closing.reject( new OO.ui.Error( - 'Cannot close window: window already closed with different data' - ) ); - } else if ( this.preparingToClose || this.closing ) { - closing.reject( new OO.ui.Error( - 'Cannot close window: window already closing with different data' - ) ); - } - - // Window closing - if ( closing.state() !== 'rejected' ) { - // If the window is currently opening, close it when it's done - this.preparingToClose = $.when( this.opening ); - // Ensure handlers get called after preparingToClose is set - this.preparingToClose.always( function () { - manager.closing = closing; - manager.preparingToClose = null; - manager.emit( 'closing', win, closing, data ); - opened = manager.opened; - manager.opened = null; - opened.resolve( closing.promise(), data ); - setTimeout( function () { - win.hold( data ).then( function () { - closing.notify( { state: 'hold' } ); - setTimeout( function () { - win.teardown( data ).then( function () { - closing.notify( { state: 'teardown' } ); - if ( manager.modal ) { - manager.toggleGlobalEvents( false ); - manager.toggleAriaIsolation( false ); - } - if ( manager.$returnFocusTo && manager.$returnFocusTo.length ) { - manager.$returnFocusTo[ 0 ].focus(); - } - manager.closing = null; - manager.currentWindow = null; - closing.resolve( data ); - } ); - }, manager.getTeardownDelay() ); - } ); - }, manager.getHoldDelay() ); + error = 'Cannot close window: window already closed with different data'; + } else if ( this.preparingToClose || lifecycle.closing.state() === 'resolved' ) { + error = 'Cannot close window: window already closing with different data'; + } + + if ( error ) { + // This function was called for the wrong window and we don't want to mess with the current + // window's state. + lifecycle = new OO.ui.WindowInstance(); + // Pretend the window has been opened, so that we can pretend to fail to close it. + lifecycle.deferreds.opening.resolve( {} ); + lifecycle.deferreds.opened.resolve( {} ); + } + + // Turn lifecycle into a Thenable for backwards-compatibility with + // the deprecated nested-promise behaviour (T163510). + [ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ] + .forEach( function ( method ) { + lifecycle[ method ] = function () { + OO.ui.warnDeprecation( + 'Using the return value of closeWindow as a promise is deprecated. ' + + 'Use .closeWindow( ... ).closed.' + method + '( ... ) instead.' + ); + return compatClosing[ method ].apply( this, arguments ); + }; } ); - } - return closing.promise(); + if ( error ) { + compatClosing.reject( new OO.ui.Error( error ) ); + lifecycle.deferreds.closing.reject( new OO.ui.Error( error ) ); + return lifecycle; + } + + // If the window is currently opening, close it when it's done + this.preparingToClose = $.when( this.lifecycle.opened ); + // Ensure handlers get called after preparingToClose is set + this.preparingToClose.always( function () { + manager.preparingToClose = null; + manager.emit( 'closing', win, compatClosing, data ); + lifecycle.deferreds.closing.resolve( data ); + compatOpened = manager.compatOpened; + manager.compatOpened = null; + compatOpened.resolve( compatClosing.promise(), data ); + setTimeout( function () { + win.hold( data ).then( function () { + compatClosing.notify( { state: 'hold' } ); + setTimeout( function () { + win.teardown( data ).then( function () { + compatClosing.notify( { state: 'teardown' } ); + if ( manager.modal ) { + manager.toggleGlobalEvents( false ); + manager.toggleAriaIsolation( false ); + } + if ( manager.$returnFocusTo && manager.$returnFocusTo.length ) { + manager.$returnFocusTo[ 0 ].focus(); + } + manager.currentWindow = null; + manager.lifecycle = null; + lifecycle.deferreds.closed.resolve( data ); + compatClosing.resolve( data ); + } ); + }, manager.getTeardownDelay() ); + } ); + }, manager.getHoldDelay() ); + } ); + + return lifecycle; }; /** @@ -1438,7 +1556,7 @@ OO.ui.WindowManager.prototype.removeWindows = function ( names ) { throw new Error( 'Cannot remove window' ); } cleanupWindow = cleanup.bind( null, name, win ); - promises.push( this.closeWindow( name ).then( cleanupWindow, cleanupWindow ) ); + promises.push( this.closeWindow( name ).closed.then( cleanupWindow, cleanupWindow ) ); } return $.when.apply( $, promises ); @@ -2786,7 +2904,7 @@ OO.ui.MessageDialog.prototype.getReadyProcess = function ( data ) { return action.getFlags().indexOf( 'primary' ) > -1; } ); if ( actions.length > 0 ) { - actions[ 0 ].$button.focus(); + actions[ 0 ].focus(); } }, this ); }; @@ -3174,7 +3292,8 @@ OO.ui.ProcessDialog.prototype.fitLabel = function () { } else if ( this.isOpening() ) { if ( !this.fitOnOpen ) { // Size is relative and the dialog isn't open yet, so wait. - this.manager.opening.done( this.fitLabel.bind( this ) ); + // FIXME: This should ideally be handled by setup somehow. + this.manager.lifecycle.opened.done( this.fitLabel.bind( this ) ); this.fitOnOpen = true; } return; @@ -3329,12 +3448,8 @@ OO.ui.alert = function ( text, options ) { return OO.ui.getWindowManager().openWindow( 'message', $.extend( { message: text, actions: [ OO.ui.MessageDialog.static.actions[ 0 ] ] - }, options ) ).then( function ( opened ) { - return opened.then( function ( closing ) { - return closing.then( function () { - return $.Deferred().resolve(); - } ); - } ); + }, options ) ).closed.then( function () { + return undefined; } ); }; @@ -3364,12 +3479,8 @@ OO.ui.alert = function ( text, options ) { OO.ui.confirm = function ( text, options ) { return OO.ui.getWindowManager().openWindow( 'message', $.extend( { message: text - }, options ) ).then( function ( opened ) { - return opened.then( function ( closing ) { - return closing.then( function ( data ) { - return $.Deferred().resolve( !!( data && data.action === 'accept' ) ); - } ); - } ); + }, options ) ).closed.then( function ( data ) { + return !!( data && data.action === 'accept' ); } ); }; @@ -3398,29 +3509,31 @@ OO.ui.confirm = function ( text, options ) { * resolve to `null`. */ OO.ui.prompt = function ( text, options ) { - var manager = OO.ui.getWindowManager(), + var instance, + manager = OO.ui.getWindowManager(), textInput = new OO.ui.TextInputWidget( ( options && options.textInput ) || {} ), textField = new OO.ui.FieldLayout( textInput, { align: 'top', label: text } ); - // TODO: This is a little hacky, and could be done by extending MessageDialog instead. - - return manager.openWindow( 'message', $.extend( { + instance = manager.openWindow( 'message', $.extend( { message: textField.$element - }, options ) ).then( function ( opened ) { - // After ready + }, options ) ); + + // TODO: This is a little hacky, and could be done by extending MessageDialog instead. + instance.opened.then( function () { textInput.on( 'enter', function () { manager.getCurrentWindow().close( { action: 'accept' } ); } ); textInput.focus(); - return opened.then( function ( closing ) { - return closing.then( function ( data ) { - return $.Deferred().resolve( data && data.action === 'accept' ? textInput.getValue() : null ); - } ); - } ); + } ); + + return instance.closed.then( function ( data ) { + return data && data.action === 'accept' ? textInput.getValue() : null; } ); }; }( OO ) ); + +//# sourceMappingURL=oojs-ui-windows.js.map \ No newline at end of file