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

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

Change subject: Make mw.DestinationChecker more reusable
......................................................................

Make mw.DestinationChecker more reusable

* Remove all UI-related code.
* Turn it into a static object with some methods.
* Make things return promises instead of take callbacks.
* Correct and add documentation.

Everything related to UI is now done directly in UploadWizardDetails.

Change-Id: I2b5e0b091a3f49788fadf151256b5921b90f652c
---
M resources/controller/uw.controller.Details.js
M resources/mw.DestinationChecker.js
M resources/mw.UploadWizardDetails.js
3 files changed, 81 insertions(+), 220 deletions(-)


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

diff --git a/resources/controller/uw.controller.Details.js 
b/resources/controller/uw.controller.Details.js
index 247cbb7..d1ac4ca 100644
--- a/resources/controller/uw.controller.Details.js
+++ b/resources/controller/uw.controller.Details.js
@@ -70,8 +70,6 @@
                                upload.details.useCustomDeedChooser();
                        }
 
-                       upload.details.titleInput.checkTitle();
-
                        // Show toggler to copy selected metadata if there's 
more than one successful upload
                        if ( successes > 1 ) {
                                uploads[ 0 ].details.buildAndShowCopyMetadata();
diff --git a/resources/mw.DestinationChecker.js 
b/resources/mw.DestinationChecker.js
index 00c8bf2..d64a400 100644
--- a/resources/mw.DestinationChecker.js
+++ b/resources/mw.DestinationChecker.js
@@ -1,147 +1,56 @@
 ( function ( mw, $ ) {
-       /**
-        * Object to attach to a file name input, to be run on its change() 
event
-        * Largely derived from wgUploadWarningObj in old upload.js
-        * Perhaps this could be a jQuery ext
-        *
-        * @param {Object} options dictionary of options
-        *              selector  required, the selector for the input to check
-        *              processResult   required, function to execute on 
results. accepts two args:
-        *                      1) filename that invoked this request -- should 
check if this is still current filename
-        *                      2) an object with the following fields
-        *                              isUnique: boolean
-        *                              img: thumbnail image src (if not unique)
-        *                              href: the url of the full image (if not 
unique)
-        *                              title: normalized title of file (if not 
unique)
-        *              spinner required, closure to execute to show progress: 
accepts true to start, false to stop
-        *              apiUrl   optional url to call for api. falls back to 
local api url
-        *              delay     optional how long to delay after a change in 
ms. falls back to configured default
-        *              preprocess optional: function to apply to the contents 
of selector before testing
-        *              events   what events on the input trigger a check.
-        */
-       mw.DestinationChecker = function ( options ) {
 
-               var checker = this,
-                       check = this.getDelayedChecker();
+       mw.DestinationChecker = {
 
-               this.selector = options.selector;
-               this.spinner = options.spinner;
-               this.processResult = options.processResult;
-               this.api = options.api;
-
-               $.each( [ 'preprocess', 'delay', 'events' ], function ( i, 
option ) {
-                       if ( options[ option ] ) {
-                               checker[ option ] = options[ option ];
-                       }
-               } );
-
-               $.each( this.events, function ( i, eventName ) {
-                       $( checker.selector )[ eventName ]( check );
-               } );
-
-       };
-
-       mw.DestinationChecker.prototype = {
-
-               // events that the input undergoes which fire off a check
-               events: [ 'change', 'keyup' ],
-
-               // how long the input muse be "idle" before doing call (don't 
want to check on each key press)
-               delay: 500, // ms;
-
-               // what tracks the wait
-               timeoutId: null,
+               api: new mw.Api(),
 
                // cached results from uniqueness api calls
                cachedResult: {},
-
                cachedBlacklist: {},
 
                /**
-                * There is an option to preprocess the name (in order to 
perhaps convert it from
-                * title to path, e.g. spaces to underscores, or to add the 
"File:" part.) Depends on
-                * exactly what your input field represents.
-                * In the event that the invoker doesn't supply a name 
preprocessor, use this identity function
-                * as default
+                * Check title for validity.
+                * 
+                * @param {string} title Title to check
+                * @return {jQuery.Promise}
+                * @return {Function} return.done
+                * @return {string} return.done.title The title that was passed 
in
+                * @return {Object|boolean} return.done.blacklist See 
#checkBlacklist
+                * @return {Object|boolean} return.done.unique See #checkUnique
                 */
-               preprocess: function ( x ) { return x; },
-
-               /**
-                * fire when the input changes value or keypress
-                * will trigger a check of the name if the field has been idle 
for delay ms.
-                */
-               getDelayedChecker: function () {
-                       var checker = this;
-
-                       return function () {
-                               // if we changed before the old timeout ran, 
clear that timeout.
-                               if ( checker.timeoutId ) {
-                                       window.clearTimeout( checker.timeoutId 
);
-                               }
-
-                               // and start another, hoping this time we'll be 
idle for delay ms.
-                               checker.timeoutId = window.setTimeout(
-                                       function () {
-                                               checker.spinner( true );
-                                               checker.checkTitle();
-                                       },
-                                       checker.delay
-                               );
-                       };
-               },
-
-               /**
-                * the backend of getDelayedChecker, and the title checker 
jQuery extension
-                * dispatches title check requests in parallel, aggregates 
results
-                */
-               checkTitle: function () {
-                       var checker = this,
-                               title = this.getTitle(),
-                               status = {
-                                       unique: null,
-                                       blacklist: null
+               checkTitle: function ( title ) {
+                       return $.when(
+                               this.checkUnique( title ),
+                               this.checkBlacklist( title )
+                       ).then( function ( unique, blacklist ) {
+                               var status = {
+                                       unique: unique,
+                                       blacklist: blacklist,
+                                       title: title
                                };
-
-                       function checkerStatus( result ) {
-                               if ( result.unique ) {
-                                       status.unique = result.unique;
-                               }
-
-                               if ( result.blacklist ) {
-                                       status.blacklist = result.blacklist;
-                               }
-
-                               // $.extend( status, result );
-                               if ( status.unique !== null && status.blacklist 
!== null ) {
-                                       status.title = title;
-                                       checker.processResult( status );
-                               }
-
-                               checker.spinner( status.unique === null || 
status.blacklist === null );
-                       }
-
-                       this.checkUnique( checkerStatus, title );
-                       this.checkBlacklist( checkerStatus, title );
-               },
-
-               /**
-                * Get the current value of the input, with optional 
preprocessing
-                *
-                * @return {string} the current input value, with optional 
processing
-                */
-               getTitle: function () {
-                       return this.preprocess( $( this.selector ).val() );
+                               return status;
+                       } );
                },
 
                /**
                 * Async check if a title is in the titleblacklist.
                 *
-                * @param {Function} callback takes object, like { 
blacklist:result }
-                * @param {string} title the blacklist should be checked against
+                * @param {string} title Title to check against the blacklist
+                * @return {jQuery.Promise}
+                * @return {Function} return.done
+                * @return {boolean} return.done.notBlacklisted
+                * @return {string} [return.done.blacklistReason] See 
mw.Api#isBlacklisted
+                * @return {string} [return.done.blacklistMessage] See 
mw.Api#isBlacklisted
+                * @return {string} [return.done.blacklistLine] See 
mw.Api#isBlacklisted
                 */
-               checkBlacklist: function ( callback, title ) {
+               checkBlacklist: function ( title ) {
                        var checker = this;
 
+                       /**
+                        * Processes result of a TitleBlacklist api call
+                        *
+                        * @param {Mixed} false if not blacklisted, object if 
blacklisted
+                        */
                        function blacklistResultProcessor( blacklistResult ) {
                                var result;
 
@@ -157,28 +66,18 @@
                                }
 
                                checker.cachedBlacklist[ title ] = result;
-                               callback( { blacklist: result } );
-                       }
-
-                       if ( title === '' ) {
-                               return;
+                               return result;
                        }
 
                        if ( this.cachedBlacklist[ title ] !== undefined ) {
-                               callback( { blacklist: this.cachedBlacklist[ 
title ] } );
-                               return;
+                               return $.Deferred().resolve( 
this.cachedBlacklist[ title ] ).promise();
                        }
 
-                       /**
-                        * Processes result of a TitleBlacklist api call with 
callback()
-                        *
-                        * @param mixed - false if not blacklisted, object if 
blacklisted
-                        */
                        if ( mw.config.get( 'UploadWizardConfig' 
).useTitleBlacklistApi ) {
-                               this.api.isBlacklisted( title, 
blacklistResultProcessor );
+                               return this.api.isBlacklisted( title ).then( 
blacklistResultProcessor );
                        } else {
                                // it's not blacklisted, because the API isn't 
even available
-                               blacklistResultProcessor( false );
+                               return $.Deferred().resolve( { notBlacklisted: 
true } ).promise();
                        }
                },
 
@@ -186,28 +85,24 @@
                 * Async check if a filename is unique. Can be attached to a 
field's change() event
                 * This is a more abstract version of 
AddMedia/UploadHandler.js::doDestCheck
                 *
-                * @param {Function} callback takes object, like { 
unique:result }
-                * @param {string} title the uniqueness should be checked for
+                * @param {string} title Title to check for uniqueness
+                * @return {jQuery.Promise}
+                * @return {Function} return.done
+                * @return {boolean} return.done.isUnique
+                * @return {boolean} [return.done.isProtected]
+                * @return {Object} [return.done.img] Image info
+                * @return {string} [return.done.href] URL to file description 
page
                 */
-               checkUnique: function ( callback, title ) {
-                       var params,
-                               checker = this;
+               checkUnique: function ( title ) {
+                       var checker = this;
 
-                       function ok( data ) {
+                       function checkUniqueProcessor( data ) {
                                var result, protection, pageId, ntitle, img;
-
-                               // Remove spinner
-                               checker.spinner( false );
-
-                               // if the name's changed in the meantime, our 
result is useless
-                               if ( title !== checker.getTitle() ) {
-                                       return;
-                               }
 
                                if ( !data || !data.query || !data.query.pages 
) {
                                        // Ignore a null result
                                        mw.log( 
'mw.DestinationChecker::checkUnique> No data in checkUnique result' );
-                                       return;
+                                       return $.Deferred().reject();
                                }
 
                                // The API will check for files with that 
filename.
@@ -265,52 +160,24 @@
                                }
 
                                checker.cachedResult[ title ] = result;
-                               callback( { unique: result } );
+                               return result;
                        }
 
-                       function err( code ) {
-                               checker.spinner( false );
-                               mw.log( 'mw.DestinationChecker::checkUnique> 
Error in checkUnique result: ' + code );
+                       if ( this.cachedResult[ title ] !== undefined ) {
+                               return $.Deferred().resolve( this.cachedResult[ 
title ] ).promise();
                        }
 
                        // Setup the request -- will return thumbnail data if 
it finds one
                        // XXX do not use iiurlwidth as it will create a 
thumbnail
-                       params = {
+                       return this.api.get( {
                                titles: title,
                                prop: 'info|imageinfo',
                                inprop: 'protection',
                                iiprop: 'url|mime|size',
                                iiurlwidth: 150
-                       };
-
-                       // if input is empty or invalid, don't bother.
-                       if ( title === '' ) {
-                               return;
-                       }
-
-                       if ( this.cachedResult[ title ] !== undefined ) {
-                               callback( { unique: this.cachedResult[ title ] 
} );
-                               return;
-                       }
-
-                       // set the spinner to spin
-                       this.spinner( true );
-
-                       // Do the destination check
-                       this.api.get( params ).done( ok ).fail( err );
+                       } ).then( checkUniqueProcessor );
                }
 
        };
 
-       /**
-        * jQuery extension to make a field upload-checkable
-        */
-       $.fn.destinationChecked = function ( options ) {
-               var checker;
-               options.selector = this;
-               checker = new mw.DestinationChecker( options );
-               // this should really be done with triggers
-               this.checkTitle = function () { checker.checkTitle(); };
-               return this;
-       };
 }( mediaWiki, jQuery ) );
diff --git a/resources/mw.UploadWizardDetails.js 
b/resources/mw.UploadWizardDetails.js
index dff90d8..e126e90 100644
--- a/resources/mw.UploadWizardDetails.js
+++ b/resources/mw.UploadWizardDetails.js
@@ -51,36 +51,30 @@
                // 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 )
-                       .keyup( function () {
-                               details.setCleanTitle( $( details.titleInput 
).val() );
-                       } )
-                       .destinationChecked( {
-                               api: this.upload.api,
-                               spinner: function ( bool ) { 
details.toggleDestinationBusy( bool ); },
-                               preprocess: function ( name ) {
-                                       var cleanTitle;
-
-                                       // First, clear out any existing 
errors, to prevent strange visual effects.
-                                       // Fixes bug 32469. But introduced a 
new bug: Some validator methods run immediately
-                                       // and this cleared out any error set 
by the validator if no titleblacklist
-                                       // is installed (where validation is 
done entirely remotely) because that second type
-                                       // of validation had a delay.
-                                       // Now only clearing errors from the 
delayed methods.
-                                       // TLDR; FIXME: `clearTitleErrors` 
should not be in a function called "preprocess"
-                                       // It's simply counter-intuitive.
-                                       details.clearTitleErrors();
-
-                                       if ( name !== '' ) {
-                                               // 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( name );
-                                               return cleanTitle && 
cleanTitle.getPrefixedText() || '';
-                                       } else {
-                                               return name;
-                                       }
-                               },
-                               processResult: function ( result ) { 
details.processDestinationCheck( result ); }
-                       } );
+                       .on( 'change keyup', OO.ui.debounce( function () {
+                               var title, cleanTitle;
+                               // First, clear out any existing errors, to 
prevent strange visual effects.
+                               details.clearTitleErrors();
+                               title = $.trim( $( details.titleInput ).val() );
+                               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' );
 
@@ -1280,7 +1274,9 @@
                 * Note: the interface's notion of "filename" versus "title" is 
the opposite of MediaWiki
                 */
                prefillTitle: function () {
-                       $( this.titleInput ).val( 
this.upload.title.getNameText() );
+                       $( this.titleInput )
+                               .val( this.upload.title.getNameText() )
+                               .trigger( 'change' );
                },
 
                /**

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I2b5e0b091a3f49788fadf151256b5921b90f652c
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