X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Flib%2Fooui%2Foojs-ui-core.js;h=4eea3bdfa8ca6b2bd06b98d0f5abfd97e5481f1c;hb=8414e3c4d0c2a647f794e1e535e70f3a2abe6eb4;hp=b2b7bfe0fbecddc115731b4ccea4526894919908;hpb=962b690e92a258bd53100e976dc5575180696bf0;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/lib/ooui/oojs-ui-core.js b/resources/lib/ooui/oojs-ui-core.js index b2b7bfe0fb..4eea3bdfa8 100644 --- a/resources/lib/ooui/oojs-ui-core.js +++ b/resources/lib/ooui/oojs-ui-core.js @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.2 + * OOUI v0.31.4 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2019 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2019-03-26T23:00:40Z + * Date: 2019-04-16T23:14:51Z */ ( function ( OO ) { @@ -347,124 +347,74 @@ OO.ui.infuse = function ( idOrNode, config ) { return OO.ui.Element.static.infuse( idOrNode, config ); }; -( function () { - /** - * Message store for the default implementation of OO.ui.msg. - * - * Environments that provide a localization system should not use this, but should override - * OO.ui.msg altogether. - * - * @private - */ - var messages = { - // Tool tip for a button that moves items in a list down one place - 'ooui-outline-control-move-down': 'Move item down', - // Tool tip for a button that moves items in a list up one place - 'ooui-outline-control-move-up': 'Move item up', - // Tool tip for a button that removes items from a list - 'ooui-outline-control-remove': 'Remove item', - // Label for the toolbar group that contains a list of all other available tools - 'ooui-toolbar-more': 'More', - // Label for the fake tool that expands the full list of tools in a toolbar group - 'ooui-toolgroup-expand': 'More', - // Label for the fake tool that collapses the full list of tools in a toolbar group - 'ooui-toolgroup-collapse': 'Fewer', - // Default label for the tooltip for the button that removes a tag item - 'ooui-item-remove': 'Remove', - // Default label for the accept button of a confirmation dialog - 'ooui-dialog-message-accept': 'OK', - // Default label for the reject button of a confirmation dialog - 'ooui-dialog-message-reject': 'Cancel', - // Title for process dialog error description - 'ooui-dialog-process-error': 'Something went wrong', - // Label for process dialog dismiss error button, visible when describing errors - 'ooui-dialog-process-dismiss': 'Dismiss', - // Label for process dialog retry action button, visible when describing only recoverable - // errors - 'ooui-dialog-process-retry': 'Try again', - // Label for process dialog retry action button, visible when describing only warnings - 'ooui-dialog-process-continue': 'Continue', - // Label for button in combobox input that triggers its dropdown - 'ooui-combobox-button-label': 'Dropdown for combobox', - // Label for the file selection widget's select file button - 'ooui-selectfile-button-select': 'Select a file', - // Label for the file selection widget if file selection is not supported - 'ooui-selectfile-not-supported': 'File selection is not supported', - // Label for the file selection widget when no file is currently selected - 'ooui-selectfile-placeholder': 'No file is selected', - // Label for the file selection widget's drop target - 'ooui-selectfile-dragdrop-placeholder': 'Drop file here', - // Label for the help icon attached to a form field - 'ooui-field-help': 'Help' - }; - - /** - * Get a localized message. - * - * After the message key, message parameters may optionally be passed. In the default - * implementation, any occurrences of $1 are replaced with the first parameter, $2 with the - * second parameter, etc. - * Alternative implementations of OO.ui.msg may use any substitution system they like, as long - * as they support unnamed, ordered message parameters. - * - * In environments that provide a localization system, this function should be overridden to - * return the message translated in the user's language. The default implementation always - * returns English messages. An example of doing this with - * [jQuery.i18n](https://github.com/wikimedia/jquery.i18n) follows. - * - * @example - * var i, iLen, button, - * messagePath = 'oojs-ui/dist/i18n/', - * languages = [ $.i18n().locale, 'ur', 'en' ], - * languageMap = {}; - * - * for ( i = 0, iLen = languages.length; i < iLen; i++ ) { - * languageMap[ languages[ i ] ] = messagePath + languages[ i ].toLowerCase() + '.json'; - * } - * - * $.i18n().load( languageMap ).done( function() { - * // Replace the built-in `msg` only once we've loaded the internationalization. - * // OOUI uses `OO.ui.deferMsg` for all initially-loaded messages. So long as - * // you put off creating any widgets until this promise is complete, no English - * // will be displayed. - * OO.ui.msg = $.i18n; - * - * // A button displaying "OK" in the default locale - * button = new OO.ui.ButtonWidget( { - * label: OO.ui.msg( 'ooui-dialog-message-accept' ), - * icon: 'check' - * } ); - * $( document.body ).append( button.$element ); - * - * // A button displaying "OK" in Urdu - * $.i18n().locale = 'ur'; - * button = new OO.ui.ButtonWidget( { - * label: OO.ui.msg( 'ooui-dialog-message-accept' ), - * icon: 'check' - * } ); - * $( document.body ).append( button.$element ); - * } ); - * - * @param {string} key Message key - * @param {...Mixed} [params] Message parameters - * @return {string} Translated message with parameters substituted - */ - OO.ui.msg = function ( key ) { - var message = messages[ key ], - params = Array.prototype.slice.call( arguments, 1 ); - if ( typeof message === 'string' ) { - // Perform $1 substitution - message = message.replace( /\$(\d+)/g, function ( unused, n ) { - var i = parseInt( n, 10 ); - return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n; - } ); - } else { - // Return placeholder if message not found - message = '[' + key + ']'; - } - return message; - }; -}() ); +/** + * Get a localized message. + * + * After the message key, message parameters may optionally be passed. In the default + * implementation, any occurrences of $1 are replaced with the first parameter, $2 with the + * second parameter, etc. + * Alternative implementations of OO.ui.msg may use any substitution system they like, as long + * as they support unnamed, ordered message parameters. + * + * In environments that provide a localization system, this function should be overridden to + * return the message translated in the user's language. The default implementation always + * returns English messages. An example of doing this with + * [jQuery.i18n](https://github.com/wikimedia/jquery.i18n) follows. + * + * @example + * var i, iLen, button, + * messagePath = 'oojs-ui/dist/i18n/', + * languages = [ $.i18n().locale, 'ur', 'en' ], + * languageMap = {}; + * + * for ( i = 0, iLen = languages.length; i < iLen; i++ ) { + * languageMap[ languages[ i ] ] = messagePath + languages[ i ].toLowerCase() + '.json'; + * } + * + * $.i18n().load( languageMap ).done( function() { + * // Replace the built-in `msg` only once we've loaded the internationalization. + * // OOUI uses `OO.ui.deferMsg` for all initially-loaded messages. So long as + * // you put off creating any widgets until this promise is complete, no English + * // will be displayed. + * OO.ui.msg = $.i18n; + * + * // A button displaying "OK" in the default locale + * button = new OO.ui.ButtonWidget( { + * label: OO.ui.msg( 'ooui-dialog-message-accept' ), + * icon: 'check' + * } ); + * $( document.body ).append( button.$element ); + * + * // A button displaying "OK" in Urdu + * $.i18n().locale = 'ur'; + * button = new OO.ui.ButtonWidget( { + * label: OO.ui.msg( 'ooui-dialog-message-accept' ), + * icon: 'check' + * } ); + * $( document.body ).append( button.$element ); + * } ); + * + * @param {string} key Message key + * @param {...Mixed} [params] Message parameters + * @return {string} Translated message with parameters substituted + */ +OO.ui.msg = function ( key ) { + // `OO.ui.msg.messages` is defined in code generated during the build process + var messages = OO.ui.msg.messages, + message = messages[ key ], + params = Array.prototype.slice.call( arguments, 1 ); + if ( typeof message === 'string' ) { + // Perform $1 substitution + message = message.replace( /\$(\d+)/g, function ( unused, n ) { + var i = parseInt( n, 10 ); + return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n; + } ); + } else { + // Return placeholder if message not found + message = '[' + key + ']'; + } + return message; +}; /** * Package a message and arguments for deferred resolution. @@ -583,6 +533,36 @@ OO.ui.getDefaultOverlay = function () { return OO.ui.$defaultOverlay; }; +/** + * Message store for the default implementation of OO.ui.msg. + * + * Environments that provide a localization system should not use this, but should override + * OO.ui.msg altogether. + * + * @private + */ +OO.ui.msg.messages = { + "ooui-outline-control-move-down": "Move item down", + "ooui-outline-control-move-up": "Move item up", + "ooui-outline-control-remove": "Remove item", + "ooui-toolbar-more": "More", + "ooui-toolgroup-expand": "More", + "ooui-toolgroup-collapse": "Fewer", + "ooui-item-remove": "Remove", + "ooui-dialog-message-accept": "OK", + "ooui-dialog-message-reject": "Cancel", + "ooui-dialog-process-error": "Something went wrong", + "ooui-dialog-process-dismiss": "Dismiss", + "ooui-dialog-process-retry": "Try again", + "ooui-dialog-process-continue": "Continue", + "ooui-combobox-button-label": "Dropdown for combobox", + "ooui-selectfile-button-select": "Select a file", + "ooui-selectfile-not-supported": "File selection is not supported", + "ooui-selectfile-placeholder": "No file is selected", + "ooui-selectfile-dragdrop-placeholder": "Drop file here", + "ooui-field-help": "Help" +}; + /*! * Mixin namespace. */ @@ -1303,75 +1283,107 @@ OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) * Scroll element into view. * * @static - * @param {HTMLElement} el Element to scroll into view + * @param {HTMLElement|Object} elOrPosition Element to scroll into view * @param {Object} [config] Configuration options + * @param {string} [config.animate=true] Animate to the new scroll offset. * @param {string} [config.duration='fast'] jQuery animation duration value * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit * to scroll in both directions + * @param {Object} [config.padding] Additional padding on the container to scroll past. + * Object containing any of 'top', 'bottom', 'left', or 'right' as numbers. + * @param {Object} [config.scrollContainer] Scroll container. Defaults to + * getClosestScrollableContainer of the element. * @return {jQuery.Promise} Promise which resolves when the scroll is complete */ -OO.ui.Element.static.scrollIntoView = function ( el, config ) { - var position, animations, container, $container, elementDimensions, containerDimensions, - $window, +OO.ui.Element.static.scrollIntoView = function ( elOrPosition, config ) { + var position, animations, container, $container, elementPosition, containerDimensions, + $window, padding, animate, method, deferred = $.Deferred(); // Configuration initialization config = config || {}; + padding = $.extend( { + top: 0, + bottom: 0, + left: 0, + right: 0 + }, config.padding ); + + animate = config.animate !== false; + animations = {}; - container = this.getClosestScrollableContainer( el, config.direction ); + elementPosition = elOrPosition instanceof HTMLElement ? + this.getDimensions( elOrPosition ).rect : + elOrPosition; + container = config.scrollContainer || ( + elOrPosition instanceof HTMLElement ? + this.getClosestScrollableContainer( elOrPosition, config.direction ) : + // No scrollContainer or element + this.getClosestScrollableContainer( document.body ) + ); $container = $( container ); - elementDimensions = this.getDimensions( el ); containerDimensions = this.getDimensions( container ); - $window = $( this.getWindow( el ) ); + $window = $( this.getWindow( container ) ); // Compute the element's position relative to the container if ( $container.is( 'html, body' ) ) { // If the scrollable container is the root, this is easy position = { - top: elementDimensions.rect.top, - bottom: $window.innerHeight() - elementDimensions.rect.bottom, - left: elementDimensions.rect.left, - right: $window.innerWidth() - elementDimensions.rect.right + top: elementPosition.top, + bottom: $window.innerHeight() - elementPosition.bottom, + left: elementPosition.left, + right: $window.innerWidth() - elementPosition.right }; } else { // Otherwise, we have to subtract el's coordinates from container's coordinates position = { - top: elementDimensions.rect.top - + top: elementPosition.top - ( containerDimensions.rect.top + containerDimensions.borders.top ), bottom: containerDimensions.rect.bottom - containerDimensions.borders.bottom - - containerDimensions.scrollbar.bottom - elementDimensions.rect.bottom, - left: elementDimensions.rect.left - + containerDimensions.scrollbar.bottom - elementPosition.bottom, + left: elementPosition.left - ( containerDimensions.rect.left + containerDimensions.borders.left ), right: containerDimensions.rect.right - containerDimensions.borders.right - - containerDimensions.scrollbar.right - elementDimensions.rect.right + containerDimensions.scrollbar.right - elementPosition.right }; } if ( !config.direction || config.direction === 'y' ) { - if ( position.top < 0 ) { - animations.scrollTop = containerDimensions.scroll.top + position.top; - } else if ( position.top > 0 && position.bottom < 0 ) { + if ( position.top < padding.top ) { + animations.scrollTop = containerDimensions.scroll.top + position.top - padding.top; + } else if ( position.bottom < padding.bottom ) { animations.scrollTop = containerDimensions.scroll.top + - Math.min( position.top, -position.bottom ); + // Scroll the bottom into view, but not at the expense + // of scrolling the top out of view + Math.min( position.top - padding.top, -position.bottom + padding.bottom ); } } if ( !config.direction || config.direction === 'x' ) { - if ( position.left < 0 ) { - animations.scrollLeft = containerDimensions.scroll.left + position.left; - } else if ( position.left > 0 && position.right < 0 ) { + if ( position.left < padding.left ) { + animations.scrollLeft = containerDimensions.scroll.left + position.left - padding.left; + } else if ( position.right < padding.right ) { animations.scrollLeft = containerDimensions.scroll.left + - Math.min( position.left, -position.right ); + // Scroll the right into view, but not at the expense + // of scrolling the left out of view + Math.min( position.left - padding.left, -position.right + padding.right ); } } if ( !$.isEmptyObject( animations ) ) { - // eslint-disable-next-line no-jquery/no-animate - $container.stop( true ).animate( animations, config.duration === undefined ? - 'fast' : config.duration ); - $container.queue( function ( next ) { + if ( animate ) { + // eslint-disable-next-line no-jquery/no-animate + $container.stop( true ).animate( animations, config.duration === undefined ? 'fast' : config.duration ); + $container.queue( function ( next ) { + deferred.resolve(); + next(); + } ); + } else { + $container.stop( true ); + for ( method in animations ) { + $container[ method ]( animations[ method ] ); + } deferred.resolve(); - next(); - } ); + } } else { deferred.resolve(); } @@ -3252,10 +3264,14 @@ OO.ui.mixin.FlaggedElement = function OoUiMixinFlaggedElement( config ) { this.$flagged = null; // Initialization - this.setFlags( config.flags ); + this.setFlags( config.flags || this.constructor.static.flags ); this.setFlaggedElement( config.$flagged || this.$element ); }; +/* Setup */ + +OO.initClass( OO.ui.mixin.FlaggedElement ); + /* Events */ /** @@ -3268,6 +3284,17 @@ OO.ui.mixin.FlaggedElement = function OoUiMixinFlaggedElement( config ) { * that the flag was added, `false` that the flag was removed. */ +/* Static Properties */ + +/** + * Initial value to pass to setFlags if no value is provided in config. + * + * @static + * @inheritable + * @property {string|string[]|Object.} + */ +OO.ui.mixin.FlaggedElement.static.flags = null; + /* Methods */ /** @@ -3458,6 +3485,9 @@ OO.initClass( OO.ui.mixin.TitledElement ); * The title text, a function that returns text, or `null` for no title. The value of the static * property is overridden if the #title config option is used. * + * If the element has a default title (e.g. ``), `null` will allow that title to be + * shown. Use empty string to suppress it. + * * @static * @inheritable * @property {string|Function|null} @@ -3482,9 +3512,7 @@ OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) { } this.$titled = $titled; - if ( this.title ) { - this.updateTitle(); - } + this.updateTitle(); }; /** @@ -3497,7 +3525,7 @@ OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) { */ OO.ui.mixin.TitledElement.prototype.setTitle = function ( title ) { title = typeof title === 'function' ? OO.ui.resolveMsg( title ) : title; - title = ( typeof title === 'string' && title.length ) ? title : null; + title = typeof title === 'string' ? title : null; if ( this.title !== title ) { this.title = title; @@ -6721,13 +6749,15 @@ OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) { case OO.ui.Keys.UP: case OO.ui.Keys.LEFT: this.clearKeyPressBuffer(); - nextItem = currentItem ? this.findRelativeSelectableItem( currentItem, -1 ) : firstItem; + nextItem = currentItem ? + this.findRelativeSelectableItem( currentItem, -1 ) : firstItem; handled = true; break; case OO.ui.Keys.DOWN: case OO.ui.Keys.RIGHT: this.clearKeyPressBuffer(); - nextItem = currentItem ? this.findRelativeSelectableItem( currentItem, 1 ) : firstItem; + nextItem = currentItem ? + this.findRelativeSelectableItem( currentItem, 1 ) : firstItem; handled = true; break; case OO.ui.Keys.ESCAPE: @@ -7995,8 +8025,8 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { selectedItem = this.findSelectedItem(); if ( !this.multiselect && selectedItem ) { // TODO: Verify if this is even needed; This is already done on highlight changes - // in SelectWidget#highlightItem, so we should just need to highlight the item we need to - // highlight here and not bother with attr or checking selections. + // in SelectWidget#highlightItem, so we should just need to highlight the item + // we need to highlight here and not bother with attr or checking selections. this.$focusOwner.attr( 'aria-activedescendant', selectedItem.getElementId() ); selectedItem.scrollElementIntoView( { duration: 0 } ); } @@ -13086,19 +13116,40 @@ OO.ui.NumberInputWidget.prototype.onKeyDown = function ( e ) { }; /** - * @inheritdoc + * Update the disabled state of the controls + * + * @chainable + * @protected + * @return {OO.ui.NumberInputWidget} The widget, for chaining */ -OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) { - // Parent method - OO.ui.NumberInputWidget.parent.prototype.setDisabled.call( this, disabled ); - +OO.ui.NumberInputWidget.prototype.updateControlsDisabled = function () { + var disabled = this.isDisabled() || this.isReadOnly(); if ( this.minusButton ) { - this.minusButton.setDisabled( this.isDisabled() ); + this.minusButton.setDisabled( disabled ); } if ( this.plusButton ) { - this.plusButton.setDisabled( this.isDisabled() ); + this.plusButton.setDisabled( disabled ); } + return this; +}; +/** + * @inheritdoc + */ +OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) { + // Parent method + OO.ui.NumberInputWidget.parent.prototype.setDisabled.call( this, disabled ); + this.updateControlsDisabled(); + return this; +}; + +/** + * @inheritdoc + */ +OO.ui.NumberInputWidget.prototype.setReadOnly = function () { + // Parent method + OO.ui.NumberInputWidget.parent.prototype.setReadOnly.apply( this, arguments ); + this.updateControlsDisabled(); return this; }; @@ -13123,11 +13174,14 @@ OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) { * @constructor * @param {Object} [config] Configuration options * @cfg {string[]|null} [accept=null] MIME types to accept. null accepts all types. + * @cfg {boolean} [multiple=false] Allow multiple files to be selected. * @cfg {string} [placeholder] Text to display when no file is selected. * @cfg {Object} [button] Config to pass to select file button. * @cfg {string} [icon] Icon to show next to file info */ OO.ui.SelectFileInputWidget = function OoUiSelectFileInputWidget( config ) { + var widget = this; + config = config || {}; // Construct buttons before parent method is called (calling setDisabled) @@ -13157,26 +13211,52 @@ OO.ui.SelectFileInputWidget = function OoUiSelectFileInputWidget( config ) { OO.ui.SelectFileInputWidget.parent.call( this, config ); // Properties - this.currentFile = null; + this.currentFiles = this.filterFiles( this.$input[ 0 ].files || [] ); if ( Array.isArray( config.accept ) ) { this.accept = config.accept; } else { this.accept = null; } - this.onFileSelectedHandler = this.onFileSelected.bind( this ); + this.multiple = !!config.multiple; // Events this.info.connect( this, { change: 'onInfoChange' } ); this.selectButton.$button.on( { keypress: this.onKeyPress.bind( this ) } ); + this.$input.on( { + change: this.onFileSelected.bind( this ), + // Support: IE11 + // In IE 11, focussing a file input (by clicking on it) displays a text cursor and scrolls + // the cursor into view (in this case, it scrolls the button, which has 'overflow: hidden'). + // Since this messes with our custom styling (the file input has large dimensions and this + // causes the label to scroll out of view), scroll the button back to top. (T192131) + focus: function () { + widget.$input.parent().prop( 'scrollTop', 0 ); + } + } ); this.connect( this, { change: 'updateUI' } ); - // Initialization - this.setupInput(); - this.fieldLayout = new OO.ui.ActionFieldLayout( this.info, this.selectButton, { align: 'top' } ); + this.$input + .attr( { + type: 'file', + // this.selectButton is tabindexed + tabindex: -1, + // Infused input may have previously by + // TabIndexed, so remove aria-disabled attr. + 'aria-disabled': null + } ); + + if ( this.accept ) { + this.$input.attr( 'accept', this.accept.join( ', ' ) ); + } + if ( this.multiple ) { + this.$input.attr( 'multiple', '' ); + } + this.selectButton.$button.append( this.$input ); + this.$element .addClass( 'oo-ui-selectFileInputWidget' ) .append( this.fieldLayout.$element ); @@ -13188,6 +13268,13 @@ OO.ui.SelectFileInputWidget = function OoUiSelectFileInputWidget( config ) { OO.inheritClass( OO.ui.SelectFileInputWidget, OO.ui.InputWidget ); +/* Static properties */ + +// Set empty title so that browser default tooltips like "No file chosen" don't appear. +// On SelectFileWidget this tooltip will often be incorrect, so create a consistent +// experience on SelectFileInputWidget. +OO.ui.SelectFileInputWidget.static.title = ''; + /* Methods */ /** @@ -13196,8 +13283,10 @@ OO.inheritClass( OO.ui.SelectFileInputWidget, OO.ui.InputWidget ); * @return {string} Filename */ OO.ui.SelectFileInputWidget.prototype.getFilename = function () { - if ( this.currentFile ) { - return this.currentFile.name; + if ( this.currentFiles.length ) { + return this.currentFiles.map( function ( file ) { + return file.name; + } ).join( ', ' ); } else { // Try to strip leading fakepath. return this.getValue().split( '\\' ).pop(); @@ -13220,7 +13309,7 @@ OO.ui.SelectFileInputWidget.prototype.setValue = function ( value ) { this.emit( 'change', this.value ); } } else { - this.currentFile = null; + this.currentFiles = []; // Parent method OO.ui.SelectFileInputWidget.super.prototype.setValue.call( this, '' ); } @@ -13233,13 +13322,7 @@ OO.ui.SelectFileInputWidget.prototype.setValue = function ( value ) { * @param {jQuery.Event} e */ OO.ui.SelectFileInputWidget.prototype.onFileSelected = function ( e ) { - var file = OO.getProp( e.target, 'files', 0 ) || null; - - if ( file && !this.isAllowedType( file.type ) ) { - file = null; - } - - this.currentFile = file; + this.currentFiles = this.filterFiles( e.target.files || [] ); }; /** @@ -13251,65 +13334,39 @@ OO.ui.SelectFileInputWidget.prototype.updateUI = function () { this.info.setValue( this.getFilename() ); }; -/** - * Setup the input element. - * - * @protected - */ -OO.ui.SelectFileInputWidget.prototype.setupInput = function () { - var widget = this; - this.$input - .attr( { - type: 'file', - // this.selectButton is tabindexed - tabindex: -1, - // Infused input may have previously by - // TabIndexed, so remove aria-disabled attr. - 'aria-disabled': null - } ) - .on( 'change', this.onFileSelectedHandler ) - // Support: IE11 - // In IE 11, focussing a file input (by clicking on it) displays a text cursor and scrolls - // the cursor into view (in this case, it scrolls the button, which has 'overflow: hidden'). - // Since this messes with our custom styling (the file input has large dimensions and this - // causes the label to scroll out of view), scroll the button back to top. (T192131) - .on( 'focus', function () { - widget.$input.parent().prop( 'scrollTop', 0 ); - } ); - - if ( this.accept ) { - this.$input.attr( 'accept', this.accept.join( ', ' ) ); - } - this.selectButton.$button.append( this.$input ); -}; - /** * Determine if we should accept this file. * * @private - * @param {string} mimeType File MIME type - * @return {boolean} + * @param {FileList|File[]} files Files to filter + * @return {File[]} Filter files */ -OO.ui.SelectFileInputWidget.prototype.isAllowedType = function ( mimeType ) { - var i, mimeTest; +OO.ui.SelectFileInputWidget.prototype.filterFiles = function ( files ) { + var accept = this.accept; - if ( !this.accept || !mimeType ) { - return true; - } + function mimeAllowed( file ) { + var i, mimeTest, + mimeType = file.type; - for ( i = 0; i < this.accept.length; i++ ) { - mimeTest = this.accept[ i ]; - if ( mimeTest === mimeType ) { + if ( !accept || !mimeType ) { return true; - } else if ( mimeTest.substr( -2 ) === '/*' ) { - mimeTest = mimeTest.substr( 0, mimeTest.length - 1 ); - if ( mimeType.substr( 0, mimeTest.length ) === mimeTest ) { + } + + for ( i = 0; i < accept.length; i++ ) { + mimeTest = accept[ i ]; + if ( mimeTest === mimeType ) { return true; + } else if ( mimeTest.substr( -2 ) === '/*' ) { + mimeTest = mimeTest.substr( 0, mimeTest.length - 1 ); + if ( mimeType.substr( 0, mimeTest.length ) === mimeTest ) { + return true; + } } } + return false; } - return false; + return Array.prototype.filter.call( files, mimeAllowed ); }; /**