From: Mark Holmquist Date: Fri, 4 Mar 2016 17:51:58 +0000 (-0600) Subject: Finish stash uploads with upload dialog X-Git-Tag: 1.31.0-rc.0~7330^2 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22suivi_revisions%22%2C%22id_auteur=%24connecte%22%29%20.%20%22?a=commitdiff_plain;h=11a2791668260211f1b82ec4954082533ff3adee;p=lhc%2Fweb%2Fwiklou.git Finish stash uploads with upload dialog Adds a 'filekey' option to the upload dialog's booklet layout so it's possible to complete a stashed upload with the dialog, even if the dialog didn't stash the file. Proof of concept gadget/userscript for testing purposes: https://phabricator.wikimedia.org/P2783 Or use this ImageTweaks patch to test: I8f8421697a6d44ec47b66496ad9ada548c4a7d0b Change-Id: I2cdea08a29cd481eb7fe5cbd83b9e4b6941a6380 --- diff --git a/resources/Resources.php b/resources/Resources.php index bdf95a7d85..35f570f698 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1196,6 +1196,7 @@ return [ 'mediawiki.user', 'mediawiki.Upload', 'mediawiki.jqueryMsg', + 'mediawiki.widgets.StashedFileWidget' ], 'messages' => [ 'upload-form-label-infoform-title', @@ -2237,7 +2238,19 @@ return [ 'position' => 'top', 'targets' => [ 'desktop', 'mobile' ], ], - + 'mediawiki.widgets.StashedFileWidget' => [ + 'scripts' => [ + 'resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.js', + ], + 'skinStyles' => [ + 'default' => [ + 'resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.less', + ], + ], + 'dependencies' => [ + 'oojs-ui-core', + ], + ], /* es5-shim */ 'es5-shim' => [ 'scripts' => [ diff --git a/resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.js b/resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.js new file mode 100644 index 0000000000..cdcf5a23a8 --- /dev/null +++ b/resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.js @@ -0,0 +1,158 @@ +/*! + * MediaWiki Widgets - StashedFileWidget class. + * + * @copyright 2011-2016 MediaWiki Widgets Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ +( function ( $, mw, OO ) { + + /** + * Accepts a stashed file and displays the information for purposes of + * publishing the file at the behest of the user. + * + * Example use: + * var widget = new mw.widgets.StashedFileWidget( { + * filekey: '12r9e4rugeec.ddtmmp.1.jpg', + * } ); + * + * widget.getValue(); // '12r9e4rugeec.ddtmmp.1.jpg' + * widget.setValue( '12r9epfbnskk.knfiy7.1.jpg' ); + * widget.getValue(); // '12r9epfbnskk.knfiy7.1.jpg' + * + * Note that this widget will not finish an upload for you. Use mw.Upload + * and mw.Upload#setFilekey, then mw.Upload#finishStashUpload to accomplish + * that. + * + * @class mw.widgets.StashedFileWidget + * @extends OO.ui.Widget + */ + + /** + * @constructor + * @param {Object} config Configuration options + * @cfg {string} filekey The filekey of the stashed file. + * @cfg {Object} [api] API to use for thumbnails. + */ + mw.widgets.StashedFileWidget = function MWWStashedFileWidget( config ) { + if ( !config.api ) { + config.api = new mw.Api(); + } + + // Parent constructor + mw.widgets.StashedFileWidget.parent.call( this, config ); + + // Mixin constructors + OO.ui.mixin.IconElement.call( this, config ); + OO.ui.mixin.LabelElement.call( this, config ); + OO.ui.mixin.PendingElement.call( this, config ); + + // Properties + this.api = config.api; + this.$info = $( '' ); + this.setValue( config.filekey ); + this.$label.addClass( 'mw-widgets-stashedFileWidget-label' ); + this.$info + .addClass( 'mw-widgets-stashedFileWidget-info' ) + .append( this.$icon, this.$label ); + + this.$thumbnail = $( '
' ).addClass( 'mw-widgets-stashedFileWidget-thumbnail' ); + this.setPendingElement( this.$thumbnail ); + + this.$thumbContain = $( '
' ) + .addClass( 'mw-widgets-stashedFileWidget-thumbnail-container' ) + .append( this.$thumbnail, this.$info ); + + this.$element + .addClass( 'mw-widgets-stashedFileWidget' ) + .append( this.$thumbContain ); + + this.updateUI(); + }; + + OO.inheritClass( mw.widgets.StashedFileWidget, OO.ui.Widget ); + OO.mixinClass( mw.widgets.StashedFileWidget, OO.ui.mixin.IconElement ); + OO.mixinClass( mw.widgets.StashedFileWidget, OO.ui.mixin.LabelElement ); + OO.mixinClass( mw.widgets.StashedFileWidget, OO.ui.mixin.PendingElement ); + + /** + * Get the current filekey. + * + * @return {string|null} + */ + mw.widgets.StashedFileWidget.prototype.getValue = function () { + return this.filekey; + }; + + /** + * Set the filekey. + * + * @param {string|null} filekey + */ + mw.widgets.StashedFileWidget.prototype.setValue = function ( filekey ) { + if ( filekey !== this.filekey ) { + this.filekey = filekey; + this.updateUI(); + this.emit( 'change', this.filekey ); + } + }; + + mw.widgets.StashedFileWidget.prototype.updateUI = function () { + var $label, $filetype; + + if ( this.filekey ) { + this.$element.removeClass( 'mw-widgets-stashedFileWidget-empty' ); + $label = $( [] ); + $filetype = $( '' ) + .addClass( 'mw-widgets-stashedFileWidget-fileType' ); + + $label = $label.add( + $( '' ) + .addClass( 'mw-widgets-stashedFileWidget-filekey' ) + .text( this.filekey ) + ).add( $filetype ); + + this.setLabel( $label ); + + this.pushPending(); + this.loadAndGetImageUrl().done( function ( url, mime ) { + this.$thumbnail.css( 'background-image', 'url( ' + url + ' )' ); + if ( mime ) { + $filetype.text( mime ); + this.setLabel( $label ); + } + }.bind( this ) ).fail( function () { + this.$thumbnail.append( + new OO.ui.IconWidget( { + icon: 'attachment', + classes: [ 'mw-widgets-stashedFileWidget-noThumbnail-icon' ] + } ).$element + ); + }.bind( this ) ).always( function () { + this.popPending(); + }.bind( this ) ); + } else { + this.$element.addClass( 'mw-widgets-stashedFileWidget-empty' ); + this.setLabel( '' ); + } + }; + + mw.widgets.StashedFileWidget.prototype.loadAndGetImageUrl = function () { + var filekey = this.filekey; + + if ( filekey ) { + return this.api.get( { + action: 'query', + prop: 'stashimageinfo', + siifilekey: filekey, + siiprop: [ 'size', 'url', 'mime' ], + siiurlwidth: 220 + } ).then( function ( data ) { + var sii = data.query.stashimageinfo[ 0 ]; + + return $.Deferred().resolve( sii.thumburl, sii.mime ); + } ); + } + + return $.Deferred().reject( 'No filekey' ); + }; +}( jQuery, mediaWiki, OO ) ); diff --git a/resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.less b/resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.less new file mode 100644 index 0000000000..cf9496f91b --- /dev/null +++ b/resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.less @@ -0,0 +1,172 @@ +.mw-widgets-stashedFileWidget { + display: inline-block; + vertical-align: middle; + width: 100%; + max-width: 50em; + margin-right: 0.5em; + + &:last-child { + margin-right: 0; + } + + &.oo-ui-iconElement .mw-widgets-stashedFileWidget-info .mw-widgets-stashedFileWidget-label { + left: 2.875em; + } + + &.oo-ui-indicatorElement .mw-widgets-stashedFileWidget-info .mw-widgets-stashedFileWidget-label { + right: 4.4625em; + } +} + +.mw-widgets-stashedFileWidget-info { + height: 2.4em; + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 2px; + width: 100%; + display: table-cell; + vertical-align: middle; + position: relative; + overflow: hidden; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + > .mw-widgets-stashedFileWidget-label { + line-height: 2.3em; + margin: 0; + overflow: hidden; + white-space: nowrap; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + text-overflow: ellipsis; + left: 0.5em; + right: 2.375em; + position: absolute; + top: 0; + bottom: 0; + + > .mw-widgets-stashedFileWidget-fileName { + float: left; + } + > .mw-widgets-stashedFileWidget-fileType { + color: #888888; + float: right; + } + } + + > .oo-ui-indicatorElement-indicator, + > .oo-ui-iconElement-icon { + position: absolute; + } + + > .oo-ui-indicatorElement-indicator { + right: 0; + top: 0; + width: 0.9375em; + height: 2.3em; + margin-right: 0.775em; + } + + > .oo-ui-iconElement-icon { + top: 0; + width: 1.875em; + height: 2.3em; + margin-left: 0.5em; + left: 0; + } + + &.oo-ui-widget-disabled { + .mw-widgets-stashedFileWidget-info { + color: #cccccc; + text-shadow: 0 1px 1px #ffffff; + border-color: #dddddd; + background-color: #f3f3f3; + + > .oo-ui-iconElement-icon, + > .oo-ui-indicatorElement-indicator { + opacity: 0.2; + } + } + } +} + +.mw-widgets-stashedFileWidget-thumbnail-container { + cursor: default; + height: 5.5em; + text-align: left; + padding: 0; + background-color: #ffffff; + border: 1px solid #cccccc; + margin-bottom: 0.5em; + vertical-align: middle; + overflow: hidden; + border-radius: 2px; + + .mw-widgets-stashedFileWidget-thumbnail { + height: 5.5em; + width: 5.5em; + position: absolute; + background-size: cover; + background-position: center center; + + &.oo-ui-pendingElement-pending { + background-size: auto; + } + + > .mw-widgets-stashedFileWidget-noThumbnail-icon { + opacity: 0.4; + background-color: #cccccc; + height: 5.5em; + width: 5.5em; + } + } + + .mw-widgets-stashedFileWidget-info { + border: none; + background: none; + display: block; + height: 100%; + width: auto; + margin-left: 5.5em; + + > .mw-widgets-stashedFileWidget-label { + position: relative; + + > .mw-widgets-stashedFileWidget-fileName { + display: block; + float: none; + } + + > .mw-widgets-stashedFileWidget-fileType { + display: block; + float: none; + } + } + } +} + + +.mw-widgets-stashedFileWidget-empty { + .mw-widgets-stashedFileWidget-thumbnail-container { + text-align: center; + + .mw-widgets-stashedFileWidget-thumbnail, + .mw-widgets-stashedFileWidget-info { + margin: 0; + display: none; + } + } + + .mw-widgets-stashedFileWidget-label { + color: #cccccc; + right: 0.5em; + } + + &.oo-ui-indicatorElement { + .mw-widgets-stashedFileWidget-label { + right: 2em; + } + } +} diff --git a/resources/src/mediawiki/api/upload.js b/resources/src/mediawiki/api/upload.js index 981a2e93c4..a6a0d8c95b 100644 --- a/resources/src/mediawiki/api/upload.js +++ b/resources/src/mediawiki/api/upload.js @@ -347,21 +347,7 @@ } function finishUpload( moreData ) { - data = $.extend( data, moreData ); - data.filekey = filekey; - data.action = 'upload'; - data.format = 'json'; - - if ( !data.filename ) { - throw new Error( 'Filename not included in file data.' ); - } - - return api.postWithEditToken( data ).then( function ( result ) { - if ( result.upload && result.upload.warnings ) { - return $.Deferred().reject( getFirstKey( result.upload.warnings ), result ).promise(); - } - return result; - } ); + api.uploadFromStash( filekey, $.extend( data, moreData ) ); } return this.upload( file, { stash: true, filename: data.filename } ).then( @@ -380,6 +366,29 @@ ); }, + /** + * Finish an upload in the stash. + * + * @param {string} filekey + * @param {Object} data + */ + uploadFromStash: function ( filekey, data ) { + data.filekey = filekey; + data.action = 'upload'; + data.format = 'json'; + + if ( !data.filename ) { + throw new Error( 'Filename not included in file data.' ); + } + + return this.postWithEditToken( data ).then( function ( result ) { + if ( result.upload && result.upload.warnings ) { + return $.Deferred().reject( getFirstKey( result.upload.warnings ), result ).promise(); + } + return result; + } ); + }, + needToken: function () { return true; } diff --git a/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js b/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js index 33b10bdd30..f0245e53b0 100644 --- a/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js +++ b/resources/src/mediawiki/mediawiki.Upload.BookletLayout.js @@ -61,6 +61,7 @@ * @constructor * @param {Object} config Configuration options * @cfg {jQuery} [$overlay] Overlay to use for widgets in the booklet + * @cfg {string} [filekey] Sets the stashed file to finish uploading. Overrides most of the file selection process, and fetches a thumbnail from the server. */ mw.Upload.BookletLayout = function ( config ) { // Parent constructor @@ -68,6 +69,8 @@ this.$overlay = config.$overlay; + this.filekey = config.filekey; + this.renderUploadForm(); this.renderInfoForm(); this.renderInsertForm(); @@ -164,8 +167,13 @@ this.clear(); this.upload = this.createUpload(); + this.setPage( 'upload' ); + if ( this.filekey ) { + this.setFilekey( this.filekey ); + } + return this.upload.getApi().then( function ( api ) { // If the user can't upload anything, don't give them the option to. @@ -217,10 +225,23 @@ layout = this, file = this.getFile(); - this.setFilename( file.name ); - this.setPage( 'info' ); + if ( this.filekey ) { + if ( file === null ) { + // Someone gonna get-a hurt real bad + throw new Error( 'filekey not passed into file select widget, which is impossible. Quitting while we\'re behind.' ); + } + + // Stashed file already uploaded. + deferred.resolve(); + this.uploadPromise = deferred; + this.emit( 'fileUploaded' ); + return deferred; + } + + this.setFilename( file.name ); + this.upload.setFile( file ); // The original file name might contain invalid characters, so use our sanitized one this.upload.setFilename( this.getFilename() ); @@ -402,14 +423,12 @@ var fieldset, layout = this; - this.selectFileWidget = new OO.ui.SelectFileWidget( { - showDropTarget: true - } ); + this.selectFileWidget = this.getFileWidget(); fieldset = new OO.ui.FieldsetLayout(); fieldset.addItems( [ this.selectFileWidget ] ); this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } ); - // Validation + // Validation (if the SFW is for a stashed file, this never fires) this.selectFileWidget.on( 'change', this.onUploadFormChange.bind( this ) ); this.selectFileWidget.on( 'change', function () { @@ -419,6 +438,23 @@ return this.uploadForm; }; + /** + * Gets the widget for displaying or inputting the file to upload. + * + * @return {OO.ui.SelectFileWidget|mw.widgets.StashedFileWidget} + */ + mw.Upload.BookletLayout.prototype.getFileWidget = function () { + if ( this.filekey ) { + return new mw.widgets.StashedFileWidget( { + filekey: this.filekey + } ); + } + + return new OO.ui.SelectFileWidget( { + showDropTarget: true + } ); + }; + /** * Updates the file preview on the info form when a file is added. * @@ -626,6 +662,20 @@ this.selectFileWidget.setValue( file ); }; + /** + * Sets the filekey of a file already stashed on the server + * as the target of this upload operation. + * + * @protected + * @param {string} filekey + */ + mw.Upload.BookletLayout.prototype.setFilekey = function ( filekey ) { + this.upload.setFilekey( this.filekey ); + this.selectFileWidget.setValue( filekey ); + + this.onUploadFormChange(); + }; + /** * Clear the values of all fields * diff --git a/resources/src/mediawiki/mediawiki.Upload.js b/resources/src/mediawiki/mediawiki.Upload.js index 4a463b0010..23b0900e06 100644 --- a/resources/src/mediawiki/mediawiki.Upload.js +++ b/resources/src/mediawiki/mediawiki.Upload.js @@ -91,6 +91,20 @@ this.filename = filename; }; + /** + * Set the stashed file to finish uploading. + * + * @param {string} filekey + */ + UP.setFilekey = function ( filekey ) { + var upload = this; + + this.setState( Upload.State.STASHED ); + this.stashPromise = $.Deferred().resolve( function ( data ) { + return upload.api.uploadFromStash( filekey, data ); + } ); + }; + /** * Sets the filename based on the filename as it was on the upload. */