Bartosz Dziewoński has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/251182

Change subject: Split off uw.TitleDetailsWidget from UploadWizardDetails (and 
OOUI-ize)
......................................................................

Split off uw.TitleDetailsWidget from UploadWizardDetails (and OOUI-ize)

This change converts the title field to use OOjs UI, moving the code to
uw.TitleDetailsWidget.

This one is a bit different:

* There are two kinds of errors we can't generate immediately:
  * Checks for duplicate titles in the form. We probably could do this one,
    but it would require some thought to do nicely.
  * Checks that are only done by the action=upload API.

  The logic for them remains the same, the methods #setDuplicateTitleError
  and #recoverFromError were adapted to display errors using the new system.

* We finally make use of the asynchronous validity checking (title blacklist
  and title uniqueness). We're now using OOjs UI PendingElement functionality
  to display "spinners" while API requests are in flight.

Also note that with the new system, it's currently impossible to display the
full error message for title blacklist. The code is commented out right now;
I submitted Ia09131c2ffc1d149ca6b1c32dcd918a539c3a719 to add the necessary
functionality. I'll submit a follow-up here when it's merged.

UploadWizardUpload's 'title' property is no longer updated continuously, but
only when the form is being submitted. To get current title from user input,
use UploadWizardDetails's getTitle() method. If we get weird issues with files
being saved under wrong titles, it means I missed a spot when changing this.

jquery.validate.wmCommonsBlacklist has been changed not to rely on 
jquery.validate
and renamed to mw.QuickTitleChecker.

Bug: T96917
Change-Id: I867499d4f912240053a767f6e542aa29e9e787ad
---
M UploadWizardHooks.php
M resources/controller/uw.controller.Details.js
A resources/details/uw.TitleDetailsWidget.js
R resources/mw.QuickTitleChecker.js
M resources/mw.UploadWizardDetails.js
M resources/ui/uw.ui.Thanks.js
M resources/uw.DetailsWidget.js
M resources/uw.FieldLayout.js
8 files changed, 311 insertions(+), 322 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/UploadWizard 
refs/changes/82/251182/1

diff --git a/UploadWizardHooks.php b/UploadWizardHooks.php
index d16ead6..314c699 100644
--- a/UploadWizardHooks.php
+++ b/UploadWizardHooks.php
@@ -69,6 +69,7 @@
                                // OOjs UI interface elements
                                'resources/uw.DetailsWidget.js',
                                'resources/uw.FieldLayout.js',
+                               'resources/details/uw.TitleDetailsWidget.js',
                                'resources/details/uw.DateDetailsWidget.js',
                                
'resources/details/uw.CategoriesDetailsWidget.js',
                                
'resources/details/uw.DescriptionDetailsWidget.js',
@@ -80,11 +81,9 @@
                                'resources/mw.canvas.js',
                                'resources/mw.ErrorDialog.js',
 
-                               // wikimedia-comons specific title checker
-                               
'resources/jquery/jquery.validate.wmCommonsBlacklist.js',
-
-                               // workhorse libraries
+                               // title validity checks
                                'resources/mw.DestinationChecker.js',
+                               'resources/mw.QuickTitleChecker.js',
 
                                // firefogg support libraries
                                'resources/mw.Firefogg.js',
diff --git a/resources/controller/uw.controller.Details.js 
b/resources/controller/uw.controller.Details.js
index 9f81103..51c8d37 100644
--- a/resources/controller/uw.controller.Details.js
+++ b/resources/controller/uw.controller.Details.js
@@ -138,14 +138,14 @@
                                        }
                                }
 
-                               upload.details.clearDuplicateTitleError();
                                // This also updates legacy error messages
                                if ( !upload.details.valid() ) {
                                        hasErrors = true;
                                }
 
                                // Seen this title before?
-                               title = upload.title.getName() + '.' + 
mw.Title.normalizeExtension( upload.title.getExtension() );
+                               title = upload.details.getTitle();
+                               title = title.getName() + '.' + 
mw.Title.normalizeExtension( title.getExtension() );
                                if ( titles[ title ] ) {
                                        // Don't submit. Instead, set an error 
in details step.
                                        upload.details.setDuplicateTitleError();
@@ -246,7 +246,7 @@
                        }
 
                        // Set details view to have correct title
-                       upload.details.setVisibleTitle( upload.title.getMain() 
);
+                       upload.details.setVisibleTitle( 
upload.details.getTitle().getMain() );
                } );
 
                // Disable edit interface
diff --git a/resources/details/uw.TitleDetailsWidget.js 
b/resources/details/uw.TitleDetailsWidget.js
new file mode 100644
index 0000000..37a8822
--- /dev/null
+++ b/resources/details/uw.TitleDetailsWidget.js
@@ -0,0 +1,208 @@
+( function ( mw, uw, $, OO ) {
+
+       /**
+        * A title field in UploadWizard's "Details" step form.
+        *
+        * @extends uw.DetailsWidget
+        */
+       uw.TitleDetailsWidget = function UWTitleDetailsWidget( config ) {
+               config = config || {};
+               uw.TitleDetailsWidget.parent.call( this );
+
+               this.extension = config.extension;
+               // We wouldn't want or use any of mw.widgets.TitleInputWidget 
functionality.
+               this.titleInput = new OO.ui.TextInputWidget( {
+                       classes: [ 'mwe-title', 
'mwe-upwiz-titleDetailsWidget-title' ],
+                       maxLength: 250
+               } );
+
+               // Aggregate 'change' event (with delay)
+               this.titleInput.on( 'change', OO.ui.debounce( this.emit.bind( 
this, 'change' ), 500 ) );
+
+               this.$element.addClass( 'mwe-upwiz-titleDetailsWidget' );
+               this.$element.append(
+                       this.titleInput.$element
+               );
+       };
+       OO.inheritClass( uw.TitleDetailsWidget, uw.DetailsWidget );
+
+       /**
+        * @inheritdoc
+        */
+       uw.TitleDetailsWidget.prototype.pushPending = function () {
+               this.titleInput.pushPending();
+       };
+
+       /**
+        * @inheritdoc
+        */
+       uw.TitleDetailsWidget.prototype.popPending = function () {
+               this.titleInput.popPending();
+       };
+
+       /**
+        * Get a mw.Title object for current value.
+        *
+        * @return {mw.Title|null}
+        */
+       uw.TitleDetailsWidget.prototype.getTitle = function () {
+               var value, extRegex, cleaned, title;
+               value = this.titleInput.getValue().trim();
+               if ( !value ) {
+                       return null;
+               }
+               extRegex = new RegExp( '\\.' + this.extension + '$', 'i' );
+               cleaned = value.replace( extRegex, '' ).replace( /\.+$/g, '' 
).trim();
+               title = mw.UploadWizardDetails.makeTitleInFileNS( cleaned + '.' 
+ this.extension );
+               return title;
+       };
+
+       /**
+        * @inheritdoc
+        */
+       uw.TitleDetailsWidget.prototype.getErrors = function () {
+               var
+                       errors = [],
+                       value = this.titleInput.getValue().trim(),
+                       title = this.getTitle();
+
+               if ( value === '' ) {
+                       errors.push( mw.message( 'mwe-upwiz-error-blank' ) );
+                       return $.Deferred().resolve( errors ).promise();
+               }
+
+               if ( !title ) {
+                       errors.push( mw.message( 'mwe-upwiz-unparseable-title' 
) );
+                       return $.Deferred().resolve( errors ).promise();
+               }
+
+               // If we have access to a title blacklist, assume it knows 
better.
+               // Otherwise check for some likely undesirable patterns.
+               if ( !mw.config.get( 'UploadWizardConfig' 
).useTitleBlacklistApi ) {
+                       errors = errors.concat(
+                               mw.QuickTitleChecker.checkTitle( 
title.getNameText() ).map( function ( errorCode ) {
+                                       // Messages:
+                                       // mwe-upwiz-error-title-badchars, 
mwe-upwiz-error-title-senselessimagename,
+                                       // mwe-upwiz-error-title-thumbnail, 
mwe-upwiz-error-title-extension,
+                                       return mw.message( 
'mwe-upwiz-error-title-' + errorCode );
+                               } )
+                       );
+               }
+
+               return mw.DestinationChecker.checkTitle( 
title.getPrefixedText() )
+                       .then( this.processDestinationCheck )
+                       .then( function ( moreErrors ) {
+                               return [].concat( errors, moreErrors );
+                       }, function () {
+                               return $.Deferred().resolve( errors );
+                       } );
+       };
+
+       /**
+        * Process the result of a destination filename check, return array of 
mw.Messages objects
+        * representing errors.
+        *
+        * @private
+        * @param {Object} result Result to process, output from 
mw.DestinationChecker
+        * @return {mw.Message[]} Error messages
+        */
+       uw.TitleDetailsWidget.prototype.processDestinationCheck = function ( 
result ) {
+               var errors, titleString;
+
+               if ( result.unique.isUnique && result.blacklist.notBlacklisted 
&& !result.unique.isProtected ) {
+                       return [];
+               }
+
+               // Something is wrong with this title.
+               errors = [];
+
+               try {
+                       titleString = mw.UploadWizardDetails.makeTitleInFileNS( 
result.title ).toString();
+               } catch ( e ) {
+                       // Unparseable result? This shouldn't happen, we 
checked for that earlier...
+                       errors.push( mw.message( 'mwe-upwiz-unparseable-title' 
) );
+                       return errors;
+               }
+
+               if ( !result.unique.isUnique ) {
+                       // result is NOT unique
+                       if ( result.unique.href ) {
+                               errors.push( mw.message(
+                                       'mwe-upwiz-fileexists-replace-on-page',
+                                       titleString,
+                                       $( '<a>' ).attr( { href: 
result.unique.href, target: '_blank' } )
+                               ) );
+                       } else {
+                               errors.push( mw.message( 
'mwe-upwiz-fileexists-replace-no-link', titleString ) );
+                       }
+               } else if ( result.unique.isProtected ) {
+                       errors.push( mw.message( 
'mwe-upwiz-error-title-protected' ) );
+               } else {
+                       errors.push( mw.message( 'mwe-upwiz-blacklisted', 
titleString ) );
+                       // TODO Handle this
+                       /*
+                       completeErrorLink = $( '<span 
class="contentSubLink"></span>' ).msg(
+                               'mwe-upwiz-feedback-blacklist-info-prompt',
+                               function () {
+                                       var errorDialog = new mw.ErrorDialog( 
result.blacklist.blacklistReason );
+                                       errorDialog.open();
+                                       return false;
+                               }
+                       );
+
+                       $errorEl.append( '&nbsp;&middot;&nbsp;' ).append( 
completeErrorLink );
+
+                       // feedback request for titleblacklist
+                       if ( mw.UploadWizard.config.blacklistIssuesPage !== 
undefined && mw.UploadWizard.config.blacklistIssuesPage !== '' ) {
+                               feedback = new mw.Feedback( {
+                                       title: new mw.Title( 
mw.UploadWizard.config.blacklistIssuesPage ),
+                                       dialogTitleMessageKey: 
'mwe-upwiz-feedback-title'
+                               } );
+
+                               feedbackLink = $( '<span 
class="contentSubLink"></span>' ).msg(
+                                       
'mwe-upwiz-feedback-blacklist-report-prompt',
+                                       function () {
+                                               feedback.launch( {
+                                                       message: mw.message( 
'mwe-upwiz-feedback-blacklist-line-intro', result.blacklist.blacklistLine 
).escaped(),
+                                                       subject: mw.message( 
'mwe-upwiz-feedback-blacklist-subject', titleString ).escaped()
+                                               } );
+                                               return false;
+                                       }
+                               );
+
+                               $errorEl.append( '&nbsp;&middot;&nbsp;' 
).append( feedbackLink );
+                       }
+                       */
+               }
+
+               return errors;
+       };
+
+       /**
+        * @inheritdoc
+        */
+       uw.TitleDetailsWidget.prototype.getWikiText = function () {
+               return this.titleInput.getValue().trim();
+       };
+
+       /**
+        * @inheritdoc
+        * @return {Object} See #setSerialized
+        */
+       uw.TitleDetailsWidget.prototype.getSerialized = function () {
+               return {
+                       title: this.titleInput.getValue()
+               };
+       };
+
+       /**
+        * @inheritdoc
+        * @param {Object} serialized
+        * @param {string} serialized.language Title language code
+        * @param {string} serialized.title Title text
+        */
+       uw.TitleDetailsWidget.prototype.setSerialized = function ( serialized ) 
{
+               this.titleInput.setValue( serialized.title );
+       };
+
+} )( mediaWiki, mediaWiki.uploadWizard, jQuery, OO );
diff --git a/resources/jquery/jquery.validate.wmCommonsBlacklist.js 
b/resources/mw.QuickTitleChecker.js
similarity index 73%
rename from resources/jquery/jquery.validate.wmCommonsBlacklist.js
rename to resources/mw.QuickTitleChecker.js
index 83cb035..9dc4a19 100644
--- a/resources/jquery/jquery.validate.wmCommonsBlacklist.js
+++ b/resources/mw.QuickTitleChecker.js
@@ -12,13 +12,15 @@
  *   - noedit, moveonly, repuload is irrelevant
  *   - we can't check autoconfirmed-ness of users here, so we ignore it
  *   - Javascript doesn't have a standard way to access unicode character 
properties in regexes, so \p{PROPERTY}, \P{PROPERTY}, and [[:PROPERTY:]] have 
been changed when possible
- *      or the associated regex removed
+ *     or the associated regex removed
 */
-( function ( $ ) {
+( function ( mw, $ ) {
 
-       var regexSets = {
+       mw.QuickTitleChecker = {};
 
-               titleBadchars: [
+       mw.QuickTitleChecker.regexSets = {
+
+               badchars: [
                        
/[\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]/, // NBSP and 
other unusual spaces
                        /[\u202A-\u202E]/, // BiDi overrides
                        /[\x00-\x1f]/, // Control characters
@@ -30,7 +32,7 @@
                ],
 
                // note lack of extension, since we test title without 
extension.
-               titleSenselessimagename: [
+               senselessimagename: [
                        /^DCP[\d\s]+$/i, //  Kodak
                        /^DSC.[\d\s]+$/i, //  [[w:Design rule for Camera File 
system]] (Nikon, Fuji, Polaroid)
                        /^MVC-?[\d\s]+$/i, //  Sony Mavica
@@ -49,31 +51,33 @@
                        /^SANY[\d\s]+$/ //  Sanyo
                ],
 
-               titleThumbnail: [
+               thumbnail: [
                        /^\d+px-.*/
                ],
 
-               titleExtension: [
+               extension: [
                        /\.(jpe?g|png|gif|svg|ogg|ogv|oga)$/
                ]
 
        };
 
-       $.each( regexSets, function ( name, regexes ) {
-               var tester = ( function ( regexes ) {
-                       return function ( value ) {
-                               var ok = true;
-                               $.each( regexes, function ( i, regex ) {
-                                       // if we make a mistake with commas in 
the above list, IE sometimes gives us an undefined regex, causes nastiness
-                                       if ( typeof regex !== undefined && 
value.match( regex ) ) {
-                                               ok = false;
-                                               return false;
-                                       }
-                               } );
-                               return ok;
-                       };
-               } )( regexes );
-               $.validator.addMethod( name, tester, 'This title is not 
allowed' );
-       } );
+       /**
+        * Check title for validity, without relying on TitleBlacklist 
extension or API requests.
+        *
+        * @param {string} title
+        * @return {string[]} Array of error codes; if it's empty, the title is 
acceptable.
+        *   Possible error codes are 'badchars', 'senselessimagename', 
'thumbnail', 'extension'.
+        */
+       mw.QuickTitleChecker.checkTitle = function ( title ) {
+               var errors = [];
+               $.each( mw.QuickTitleChecker.regexSets, function ( setName, 
regexes ) {
+                       $.each( regexes, function ( i, regex ) {
+                               if ( title.match( regex ) ) {
+                                       errors.push( setName );
+                               }
+                       } );
+               } );
+               return errors;
+       };
 
-} )( jQuery );
+} )( mediaWiki, jQuery );
diff --git a/resources/mw.UploadWizardDetails.js 
b/resources/mw.UploadWizardDetails.js
index 4be36ed..3ea703b 100644
--- a/resources/mw.UploadWizardDetails.js
+++ b/resources/mw.UploadWizardDetails.js
@@ -10,7 +10,7 @@
         * @param {jQuery} containerDiv The `div` to put the interface into
         */
        mw.UploadWizardDetails = function ( upload, containerDiv ) {
-               var titleContainerDiv,
+               var
                        descriptionRequired, uri,
                        categoriesHinter,
                        moreDetailsCtrlDiv, moreDetailsDiv, otherInformationId,
@@ -48,63 +48,15 @@
                this.descriptionsDetailsField.$label.addHint( 'description' );
 
 
-               // Commons specific help for titles
-               //  https://commons.wikimedia.org/wiki/Commons:File_naming
-               //  
https://commons.wikimedia.org/wiki/MediaWiki:Filename-prefix-blacklist
-               // XXX make sure they can't use ctrl characters or returns or 
any other bad stuff.
-               this.titleId = 'title' + this.upload.index;
-               this.titleInput = this.makeTextInput( this.titleId, 'title', 
undefined, 250 )
-                       .on( 'change keyup', OO.ui.debounce( function () {
-                               var title, cleanTitle;
-                               // First, clear out any existing errors, to 
prevent strange visual effects.
-                               details.clearTitleErrors();
-                               title = $( details.titleInput ).val().trim();
-                               if ( !title ) {
-                                       return;
-                               }
-                               // turn the contents of the input into a 
MediaWiki title ("File:foo bar.jpg") to look up
-                               // side effect -- also sets this as our current 
title
-                               cleanTitle = details.setCleanTitle( title );
-                               cleanTitle = cleanTitle && 
cleanTitle.getPrefixedText() || '';
-                               if ( !cleanTitle ) {
-                                       return;
-                               }
-                               details.toggleDestinationBusy( true );
-                               mw.DestinationChecker.checkTitle( cleanTitle )
-                                       .done( function ( result ) {
-                                               
details.processDestinationCheck( result );
-                                       } )
-                                       .always( function () {
-                                               details.toggleDestinationBusy( 
false );
-                                       } );
-                       }, 500 ) );
-
-               this.titleErrorDiv = $( '<div>' ).addClass( 
'mwe-upwiz-details-input-error' );
-
-               function makeAndAppendTitleErrorLabel( labelClass ) {
-                       $( '<label>' )
-                               .attr( {
-                                       'for': details.titleId,
-                                       generated: 'true'
-                               } )
-                               .addClass( 'mwe-error ' + labelClass )
-                               .appendTo( details.titleErrorDiv );
-               }
-
-               makeAndAppendTitleErrorLabel( 'mwe-validator-error 
mwe-upwiz-validation-immediate' );
-               makeAndAppendTitleErrorLabel( 'mwe-upwiz-duplicate-title 
mwe-upwiz-validation-immediate' );
-               makeAndAppendTitleErrorLabel( 'mwe-upwiz-error-title-unique 
mwe-upwiz-validation-delayed' );
-               makeAndAppendTitleErrorLabel( 'mwe-upwiz-error-recovery 
mwe-upwiz-validation-delayed' );
-
-               titleContainerDiv = $( '<div 
class="mwe-upwiz-details-fieldname-input ui-helper-clearfix"></div>' )
-                       .append(
-                               this.titleErrorDiv,
-                               $( '<div 
class="mwe-upwiz-details-fieldname"></div>' )
-                                       .msg( 'mwe-upwiz-title' )
-                                       .requiredFieldLabel()
-                                       .addHint( 'title' ),
-                               $( '<div 
class="mwe-upwiz-details-input"></div>' ).append( this.titleInput )
-                       );
+               this.titleDetails = new uw.TitleDetailsWidget( {
+                       extension: this.upload.title.getExtension()
+               } );
+               this.titleDetailsField = new uw.FieldLayout( this.titleDetails, 
{
+                       label: mw.message( 'mwe-upwiz-title' ).text(),
+                       required: true
+               } );
+               // TODO Rethink hints
+               this.titleDetailsField.$label.addHint( 'title' );
 
                this.deedDiv = $( '<div class="mwe-upwiz-custom-deed" />' );
 
@@ -193,7 +145,7 @@
                /* Build the form for the file upload */
                this.$form = $( '<form id="mwe-upwiz-detailsform' + 
this.upload.index + '"></form>' ).addClass( 'detailsForm' );
                this.$form.append(
-                       titleContainerDiv,
+                       this.titleDetailsField.$element,
                        this.descriptionsDetailsField.$element,
                        this.copyrightInfoFieldset,
                        this.dateDetailsField.$element,
@@ -355,38 +307,6 @@
                                ]
                        } );
                }
-
-               if ( mw.config.get( 'UploadWizardConfig' ).useTitleBlacklistApi 
) {
-                       // less strict checking, since TitleBlacklist checks 
should catch most errors.
-                       this.$form.find( '.mwe-title' )
-                               .rules( 'add', {
-                                       required: true,
-                                       titleParsability: true,
-                                       messages: {
-                                               required: mw.message( 
'mwe-upwiz-error-blank' ).escaped(),
-                                               titleParsability: mw.message( 
'mwe-upwiz-unparseable-title' ).escaped()
-                                       }
-                               } );
-               } else {
-                       // make the title field required, and non-blacklisted
-                       this.$form.find( '.mwe-title' )
-                               .rules( 'add', {
-                                       required: true,
-                                       titleBadchars: true,
-                                       titleSenselessimagename: true,
-                                       titleThumbnail: true,
-                                       titleExtension: true,
-                                       titleParsability: true,
-                                       messages: {
-                                               required: mw.message( 
'mwe-upwiz-error-blank' ).escaped(),
-                                               titleBadchars: mw.message( 
'mwe-upwiz-error-title-badchars' ).escaped(),
-                                               titleSenselessimagename: 
mw.message( 'mwe-upwiz-error-title-senselessimagename' ).escaped(),
-                                               titleThumbnail: mw.message( 
'mwe-upwiz-error-title-thumbnail' ).escaped(),
-                                               titleExtension: mw.message( 
'mwe-upwiz-error-title-extension' ).escaped(),
-                                               titleParsability: mw.message( 
'mwe-upwiz-unparseable-title' ).escaped()
-                                       }
-                               } );
-               }
        };
 
        /**
@@ -437,65 +357,23 @@
                        other: true
                },
 
-               /*
-                * Get a reference to the error labels
+               /**
+                * Get file page title for this upload.
                 *
-                * @return {jQuery} reference to error labels
+                * @return {mw.Title|null}
                 */
-               $getTitleErrorLabels: function () {
-                       if ( !this.$titleErrorLabels || 
this.$titleErrorLabels.length === 0 ) {
-                               this.$titleErrorLabels = this.$form
-                                       .find( 'label[for=' + this.titleId + 
'].mwe-upwiz-validation-delayed' );
-                       }
-                       return this.$titleErrorLabels;
+               getTitle: function () {
+                       return this.titleDetails.getTitle();
                },
 
-               /*
-                * Clears errors shown in UI
-                *
-                * @chainable
-                */
-               clearTitleErrors: function () {
-                       var $labels = this.$getTitleErrorLabels();
-
-                       $labels.empty();
-
-                       return this;
-               },
-
-               /*
+               /**
                 * Display error message about multiple uploaded files with the 
same title specified
                 *
                 * @chainable
                 */
                setDuplicateTitleError: function () {
-                       var $duplicateTitleLabel = this.$form
-                               .find( 'label[for=' + this.titleId + 
'].mwe-upwiz-duplicate-title' );
-
-                       $duplicateTitleLabel.text( mw.message( 
'mwe-upwiz-error-title-duplicate' ).text() );
-
-                       // Clear error as soon as the value changed
-                       // The input event is not implemented in all browsers 
we support but
-                       // it's sufficient to clear the error upon form submit 
and when this happens
-                       // the change event is fired anyway
-                       // keyup would clear the error when pressing meta keys, 
adding leading spaces, ...
-                       this.titleInput.one( 'input change', function () {
-                               $duplicateTitleLabel.empty();
-                       } );
-
-                       return this;
-               },
-
-               /*
-                * Empties the error message about multiple uploaded files with 
the same title specified
-                *
-                * @chainable
-                */
-               clearDuplicateTitleError: function () {
-                       this.$form
-                               .find( 'label[for=' + this.titleId + 
'].mwe-upwiz-duplicate-title' )
-                               .empty();
-
+                       // TODO This should give immediate response, not only 
when submitting the form
+                       this.titleDetailsField.setErrors( [ mw.message( 
'mwe-upwiz-error-title-duplicate' ) ] );
                        return this;
                },
 
@@ -509,8 +387,9 @@
                 * @param String metadataType One of the types defined in the 
copyMetadataTypes property
                 */
                copyMetadata: function ( metadataType ) {
-                       var titleZero, matches,
+                       var titleZero, matches, i, currentTitle,
                                uploads = this.upload.wizard.uploads,
+                               sourceUpload = uploads[ 0 ],
                                sourceId = uploads[ 0 ].index;
 
                        // In the simplest case, we can use this 
self-explanatory vanilla loop.
@@ -536,7 +415,6 @@
 
                        function oouiCopy( property ) {
                                var i,
-                                       sourceUpload = uploads[ 0 ],
                                        sourceValue = sourceUpload.details[ 
property ].getSerialized();
 
                                for ( i = 1; i < uploads.length; i++ ) {
@@ -547,30 +425,30 @@
                        if ( metadataType === 'title' ) {
 
                                // Add number suffix to first title if no 
numbering present
-                               titleZero = $( '#title' + sourceId ).val();
+                               titleZero = 
sourceUpload.details.titleDetails.getSerialized().title;
                                matches = titleZero.match( 
/(\D+)(\d{1,3})(\D*)$/ );
                                if ( matches === null ) {
                                        titleZero = titleZero + ' 01';
-                                       // After setting the value, we must 
trigger input processing for the change to take effect
-                                       $( '#title' + sourceId ).val( titleZero 
).keyup();
                                }
 
                                // Overwrite remaining title inputs with first 
title + increment of rightmost
                                // number in the title. Note: We ignore numbers 
with more than three digits, because these
                                // are more likely to be years ("Wikimania 2011 
Celebration") or other non-sequence
                                // numbers.
-                               $( 'input[id^=title]:not(#title' + sourceId + 
')' ).each( function ( i ) {
-                                       var currentTitle = $( this ).val();
+
+                               // This loop starts from 0 and not 1 - we want 
to overwrite the source upload too!
+                               for ( i = 0; i < uploads.length; i++ ) {
+                                       /*jshint loopfunc:true */
                                        currentTitle = titleZero.replace( 
/(\D+)(\d{1,3})(\D*)$/,
                                                function ( str, m1, m2, m3 ) {
-                                                       var newstr = String( 
+m2 + i + 1 );
+                                                       var newstr = String( 
+m2 + i );
                                                        return m1 + new Array( 
m2.length + 1 - newstr.length )
                                                                .join( '0' ) + 
newstr + m3;
                                                }
                                        );
-                                       $( this ).val( currentTitle ).keyup();
+                                       uploads[ i 
].details.titleDetails.setSerialized( { title: currentTitle } );
+                               }
 
-                               } );
                        } else if ( metadataType === 'description' ) {
 
                                oouiCopy( 'descriptionsDetails' );
@@ -733,6 +611,7 @@
                 */
                getErrors: function () {
                        return $.when(
+                               this.titleDetails.getErrors(),
                                this.descriptionsDetails.getErrors(),
                                this.dateDetails.getErrors(),
                                this.categoriesDetails.getErrors()
@@ -747,6 +626,7 @@
                 */
                getWarnings: function () {
                        return $.when(
+                               this.titleDetails.getWarnings(),
                                this.descriptionsDetails.getWarnings(),
                                this.dateDetails.getWarnings(),
                                this.categoriesDetails.getWarnings()
@@ -759,6 +639,7 @@
                 * UI.
                 */
                checkValidity: function () {
+                       this.titleDetailsField.checkValidity();
                        this.descriptionsDetailsField.checkValidity();
                        this.dateDetailsField.checkValidity();
                        this.categoriesDetailsField.checkValidity();
@@ -853,102 +734,6 @@
                                        return 
details.upload.providedFile.licenseValue;
                                }
                        }, overrides );
-               },
-
-               /**
-                * show file destination field as "busy" while checking
-                *
-                * @param {boolean} busy True = show busy-ness, false = remove
-                */
-               toggleDestinationBusy: function ( busy ) {
-                       if ( busy ) {
-                               this.titleInput.addClass( 'mwe-upwiz-busy' );
-                               $( this.titleInput ).data( 'valid', undefined );
-                       } else {
-                               this.titleInput.removeClass( 'mwe-upwiz-busy' );
-                       }
-               },
-
-               /**
-                * Process the result of a destination filename check.
-                * See mw.DestinationChecker.js for documentation of result 
format
-                * XXX would be simpler if we created all these divs in the DOM 
and had a more jquery-friendly way of selecting
-                * attrs. Instead we create & destroy whole interface each 
time. Won't someone think of the DOM elements?
-                *
-                * @param {Object} result Result to process
-                */
-               processDestinationCheck: function ( result ) {
-                       var titleString, errHtml, completeErrorLink,
-                               feedback, feedbackLink,
-                               $errorEl = this.$form
-                                       .find( 'label[for=' + this.titleId + 
'].mwe-upwiz-error-title-unique' );
-
-                       if ( result.unique.isUnique && 
result.blacklist.notBlacklisted && !result.unique.isProtected ) {
-                               $( this.titleInput ).data( 'valid', true );
-                               $errorEl.hide().empty();
-                               this.ignoreWarningsInput = undefined;
-                               return;
-                       }
-
-                       // something is wrong with this title.
-                       $( this.titleInput ).data( 'valid', false );
-
-                       try {
-                               titleString = 
mw.UploadWizardDetails.makeTitleInFileNS( result.title ).toString();
-                       } catch ( e ) {
-                               // unparseable result from unique test?
-                               titleString = '[unparseable name]';
-                       }
-
-                       if ( !result.unique.isUnique ) {
-                               // result is NOT unique
-                               if ( result.unique.href ) {
-                                       errHtml = mw.message( 
'mwe-upwiz-fileexists-replace-on-page', titleString, $( '<a>' ).attr( { href: 
result.unique.href, target: '_blank' } ) ).parse();
-                               } else {
-                                       errHtml = mw.message( 
'mwe-upwiz-fileexists-replace-no-link', titleString ).escaped();
-                               }
-
-                               $errorEl.html( errHtml );
-                       } else if ( result.unique.isProtected ) {
-                               errHtml = mw.message( 
'mwe-upwiz-error-title-protected' ).text();
-                               $errorEl.text( errHtml );
-                       } else {
-                               errHtml = mw.message( 'mwe-upwiz-blacklisted', 
titleString ).text();
-                               $errorEl.text( errHtml );
-
-                               completeErrorLink = $( '<span 
class="contentSubLink"></span>' ).msg(
-                                       
'mwe-upwiz-feedback-blacklist-info-prompt',
-                                       function () {
-                                               var errorDialog = new 
mw.ErrorDialog( result.blacklist.blacklistReason );
-                                               errorDialog.open();
-                                               return false;
-                                       }
-                               );
-
-                               $errorEl.append( '&nbsp;&middot;&nbsp;' 
).append( completeErrorLink );
-
-                               // feedback request for titleblacklist
-                               if ( mw.UploadWizard.config.blacklistIssuesPage 
!== undefined && mw.UploadWizard.config.blacklistIssuesPage !== '' ) {
-                                       feedback = new mw.Feedback( {
-                                               title: new mw.Title( 
mw.UploadWizard.config.blacklistIssuesPage ),
-                                               dialogTitleMessageKey: 
'mwe-upwiz-feedback-title'
-                                       } );
-
-                                       feedbackLink = $( '<span 
class="contentSubLink"></span>' ).msg(
-                                               
'mwe-upwiz-feedback-blacklist-report-prompt',
-                                               function () {
-                                                       feedback.launch( {
-                                                               message: 
mw.message( 'mwe-upwiz-feedback-blacklist-line-intro', 
result.blacklist.blacklistLine ).escaped(),
-                                                               subject: 
mw.message( 'mwe-upwiz-feedback-blacklist-subject', titleString ).escaped()
-                                                       } );
-                                                       return false;
-                                               }
-                                       );
-
-                                       $errorEl.append( '&nbsp;&middot;&nbsp;' 
).append( feedbackLink );
-                               }
-                       }
-                       $errorEl.show();
                },
 
                /**
@@ -1085,12 +870,11 @@
 
                /**
                 * Set the title of the thing we just uploaded, visibly
-                * Note: the interface's notion of "filename" versus "title" is 
the opposite of MediaWiki
                 */
                prefillTitle: function () {
-                       $( this.titleInput )
-                               .val( this.upload.title.getNameText() )
-                               .trigger( 'change' );
+                       this.titleDetails.setSerialized( {
+                               title: this.upload.title.getNameText()
+                       } );
                },
 
                /**
@@ -1347,6 +1131,7 @@
 
                        $( 'form', this.containerDiv ).submit();
 
+                       this.upload.title = this.getTitle();
                        this.upload.state = 'submitting-details';
                        this.setStatus( mw.message( 
'mwe-upwiz-submitting-details' ).text() );
                        this.showIndicator( 'progress' );
@@ -1356,7 +1141,7 @@
                        params = {
                                action: 'upload',
                                filekey: this.upload.fileKey,
-                               filename: this.upload.title.getMain(),
+                               filename: this.getTitle().getMain(),
                                comment: 'User created page with ' + 
mw.UploadWizard.userAgent
                        };
 
@@ -1442,7 +1227,7 @@
                        } else if ( warnings && warnings[ 'exists-normalized' ] 
) {
                                existingFile = warnings[ 'exists-normalized' ];
                                existingFileExt = mw.Title.normalizeExtension( 
existingFile.split( '.' ).pop() );
-                               ourFileExt = mw.Title.normalizeExtension( 
this.upload.title.getExtension() );
+                               ourFileExt = mw.Title.normalizeExtension( 
this.getTitle().getExtension() );
 
                                if ( existingFileExt !== ourFileExt ) {
                                        delete warnings[ 'exists-normalized' ];
@@ -1475,16 +1260,16 @@
                                } );
                        } else if ( result && result.upload.warnings ) {
                                if ( warnings.thumb || warnings[ 'thumb-name' ] 
) {
-                                       this.recoverFromError( this.titleId, 
mw.message( 'mwe-upwiz-error-title-thumbnail' ).text(), 'error-title-thumbnail' 
);
+                                       this.recoverFromError( mw.message( 
'mwe-upwiz-error-title-thumbnail' ), 'error-title-thumbnail' );
                                } else if ( warnings.badfilename ) {
-                                       this.recoverFromError( this.titleId, 
mw.message( 'mwe-upwiz-error-title-badchars' ).text(), 'title-badchars' );
+                                       this.recoverFromError( mw.message( 
'mwe-upwiz-error-title-badchars' ), 'title-badchars' );
                                } else if ( warnings[ 'bad-prefix' ] ) {
-                                       this.recoverFromError( this.titleId, 
mw.message( 'mwe-upwiz-error-title-senselessimagename' ).text(), 
'title-senselessimagename' );
+                                       this.recoverFromError( mw.message( 
'mwe-upwiz-error-title-senselessimagename' ), 'title-senselessimagename' );
                                } else if ( existingFile ) {
                                        existingFileUrl = mw.config.get( 
'wgServer' ) + new mw.Title( existingFile, NS_FILE ).getUrl();
-                                       this.recoverFromError( this.titleId, 
mw.message( 'mwe-upwiz-api-warning-exists', existingFileUrl ).parse(), 
'api-warning-exists' );
+                                       this.recoverFromError( mw.message( 
'mwe-upwiz-api-warning-exists', existingFileUrl ).parse(), 'api-warning-exists' 
);
                                } else if ( warnings.duplicate ) {
-                                       this.recoverFromError( this.titleId, 
mw.message( 'mwe-upwiz-upload-error-duplicate' ).text(), 
'upload-error-duplicate' );
+                                       this.recoverFromError( mw.message( 
'mwe-upwiz-upload-error-duplicate' ), 'upload-error-duplicate' );
                                } else if ( warnings[ 'duplicate-archive' ] !== 
undefined ) {
                                        // warnings[ 'duplicate-archive' ] may 
be '' (empty string) for revdeleted files
                                        if ( this.upload.ignoreWarning[ 
'duplicate-archive' ] ) {
@@ -1498,7 +1283,7 @@
                                                } );
                                        } else {
                                                // This should _never_ happen, 
but just in case....
-                                               this.recoverFromError( 
this.titleId, mw.message( 'mwe-upwiz-upload-error-duplicate-archive' ).text(), 
'upload-error-duplicate-archive' );
+                                               this.recoverFromError( 
mw.message( 'mwe-upwiz-upload-error-duplicate-archive' ), 
'upload-error-duplicate-archive' );
                                        }
                                } else {
                                        warningsKeys = [];
@@ -1506,7 +1291,7 @@
                                                warningsKeys.push( key );
                                        } );
                                        this.upload.state = 'error';
-                                       this.recoverFromError( this.titleId, 
mw.message( 'api-error-unknown-warning', warningsKeys.join( ', ' ) ).text(), 
'api-error-unknown-warning' );
+                                       this.recoverFromError( mw.message( 
'api-error-unknown-warning', warningsKeys.join( ', ' ) ), 
'api-error-unknown-warning' );
                                }
 
                                return $.Deferred().resolve();
@@ -1516,21 +1301,16 @@
                },
 
                /**
-                * Create a recoverable error -- show the form again, and 
highlight the problematic field. Go to error state but do not block submission
+                * Create a recoverable error -- show the form again, and 
highlight the problematic field.
                 *
-                * @param {string} fieldId id of input field -- presumed to be 
within this upload's details form.
-                * @param {string} errorMessage HTML error message to show. 
Make sure escaping text properly.
+                * @param {mw.Message} errorMessage Error message to show.
                 * @param {string} errorCode
                 */
-               recoverFromError: function ( fieldId, errorMessage, errorCode ) 
{
+               recoverFromError: function ( errorMessage, errorCode ) {
                        uw.eventFlowLogger.logError( 'details', { code: 
errorCode || 'details.recoverFromError.unknown', message: errorMessage } );
                        this.upload.state = 'error';
                        this.dataDiv.morphCrossfade( '.detailsForm' );
-                       $( '#' + fieldId ).addClass( 'mwe-error' );
-                       this.$form
-                               .find( 'label[for=' + fieldId + 
'].mwe-upwiz-error-recovery' )
-                               .html( errorMessage )
-                               .show();
+                       this.titleDetailsField.setErrors( [ errorMessage ] );
                },
 
                /**
@@ -1574,7 +1354,7 @@
 
                        if ( result && result.error && result.error.code ) {
                                if ( titleErrorMap[ code ] ) {
-                                       this.recoverFromError( this.titleId, 
mw.message( 'mwe-upwiz-error-title-' + titleErrorMap[ code ] ).escaped(), 
'title-' + titleErrorMap[ code ] );
+                                       this.recoverFromError( mw.message( 
'mwe-upwiz-error-title-' + titleErrorMap[ code ] ), 'title-' + titleErrorMap[ 
code ] );
                                        return;
                                } else {
                                        statusKey = 'api-error-' + code;
@@ -1654,30 +1434,11 @@
                        $moreDiv.addClass( 'mwe-upwiz-toggled' );
                },
 
-               /**
-                * Apply some special cleanups for titles before adding to 
model. These cleanups are not reflected in what the user sees in the title 
input field.
-                * For example, we remove an extension in the title if it 
matches the extension we're going to add anyway. (bug #30676)
-                *
-                * @param {string} s Title in human-readable form, e.g. "Foo 
bar", rather than "File:Foo_bar.jpg"
-                * @return {mw.Title} cleaned title with prefix and extension, 
stringified.
-                */
-               setCleanTitle: function ( s ) {
-                       var ext = this.upload.title.getExtension(),
-                               re = new RegExp( '\\.' + 
this.upload.title.getExtension() + '$', 'i' ),
-                               cleaned = s.replace( re, '' ).replace( /\.+$/g, 
'' ).trim();
-                       this.upload.title = 
mw.UploadWizardDetails.makeTitleInFileNS( cleaned + '.' + ext ) || 
this.upload.title;
-                       return this.upload.title;
-               },
-
                setVisibleTitle: function ( s ) {
                        $( this.submittingDiv )
                                .find( '.mwe-upwiz-visible-file-filename-text' )
                                .text( s );
                }
        };
-
-       $.validator.addMethod( 'titleParsability', function ( s, elem ) {
-               return this.optional( elem ) || mw.Title.newFromText( s.trim() 
);
-       } );
 
 } )( mediaWiki, mediaWiki.uploadWizard, jQuery, OO );
diff --git a/resources/ui/uw.ui.Thanks.js b/resources/ui/uw.ui.Thanks.js
index 96da4f1..c245c0b 100644
--- a/resources/ui/uw.ui.Thanks.js
+++ b/resources/ui/uw.ui.Thanks.js
@@ -102,7 +102,7 @@
                }
 
                thumbWikiText = '[[' + [
-                               upload.title.toText(),
+                               upload.details.getTitle().getPrefixedText(),
                                'thumb',
                                upload.details.getThumbnailCaption()
                        ].join( '|' ) + ']]';
@@ -119,7 +119,7 @@
                        .css( { 'text-align': 'center', 'font-size': 'small' } )
                        .appendTo( $thumbnailWrapDiv );
                $thumbnailLink = $( '<a>' )
-                       .text( upload.title.getMainText() )
+                       .text( upload.details.getTitle().getMainText() )
                        .appendTo( $thumbnailCaption );
 
                $( '<div>' )
diff --git a/resources/uw.DetailsWidget.js b/resources/uw.DetailsWidget.js
index 90b5492..b20d464 100644
--- a/resources/uw.DetailsWidget.js
+++ b/resources/uw.DetailsWidget.js
@@ -21,6 +21,20 @@
         */
 
        /**
+        * @inheritdoc OO.ui.mixin.PendingElement#pushPending
+        */
+       uw.DetailsWidget.prototype.pushPending = function () {
+               // Do nothing by default
+       };
+
+       /**
+        * @inheritdoc OO.ui.mixin.PendingElement#popPending
+        */
+       uw.DetailsWidget.prototype.popPending = function () {
+               // Do nothing by default
+       };
+
+       /**
         * Get the list of errors about the current state of the widget.
         *
         * @return {jQuery.Promise} Promise resolved with an array of 
mw.Message objects
diff --git a/resources/uw.FieldLayout.js b/resources/uw.FieldLayout.js
index 26570df..6c20bed 100644
--- a/resources/uw.FieldLayout.js
+++ b/resources/uw.FieldLayout.js
@@ -53,6 +53,7 @@
         */
        uw.FieldLayout.prototype.checkValidity = function () {
                var layout = this;
+               this.fieldWidget.pushPending();
                $.when(
                        this.fieldWidget.getWarnings(),
                        this.fieldWidget.getErrors()
@@ -60,6 +61,8 @@
                        // this.notices and this.errors are arrays of 
mw.Messages and not strings in this subclass
                        layout.setNotices( warnings );
                        layout.setErrors( errors );
+               } ).always( function () {
+                       layout.fieldWidget.popPending();
                } );
        };
 

-- 
To view, visit https://gerrit.wikimedia.org/r/251182
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I867499d4f912240053a767f6e542aa29e9e787ad
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/UploadWizard
Gerrit-Branch: master
Gerrit-Owner: Bartosz Dziewoński <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to