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

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

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

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

This change converts the descriptions field to use OOjs UI, moving the
code to uw.DescriptionDetailsWidget and uw.DescriptionsDetailsWidget.
* uw.DescriptionDetailsWidget implements logic and UI for a single
  language and description pair. This code was previously scattered in
  mw.UploadWizardDescription, uw.model.Description and mw.LanguageUpWiz,
  all of which are removed now.
* uw.DescriptionsDetailsWidget implements logic for managing multiple
  descriptions and adding/removing them, which was mostly in
  UploadWizardDetails.

The dropdowns and text fields now use OOUI widgets and are aligned on one
line (as they used to be a long time ago, but this broke at some point).

Everything related to uw.model is also removed, since that attempt at
refactoring seems to have been aborted.

Some weird things that I noticed, but did not change:
* There is some questionably-looking logic changing some languages to
  others (now in uw.DescriptionDetailsWidget). I'm not sure how much
  of it is needed.
* 'descriptionlang' and 'description' URI query parameters are handled
  differently.

Bug: T96917
Change-Id: I9563ff3bf7acb24ab0df35930643df81aee89dba
---
M UploadWizardHooks.php
M i18n/en.json
M i18n/qqq.json
A resources/details/uw.DescriptionDetailsWidget.js
A resources/details/uw.DescriptionDetailsWidget.less
A resources/details/uw.DescriptionsDetailsWidget.js
A resources/details/uw.DescriptionsDetailsWidget.less
D resources/mw.LanguageUpWiz.js
D resources/mw.UploadWizardDescription.js
M resources/mw.UploadWizardDetails.js
M resources/ui/uw.ui.Thanks.js
M resources/uploadWizard.css
D resources/uw/model/uw.model.Description.js
D resources/uw/model/uw.model.base.js
D tests/qunit/mw.uw.model.Description.test.js
15 files changed, 447 insertions(+), 633 deletions(-)


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

diff --git a/UploadWizardHooks.php b/UploadWizardHooks.php
index 058a125..d16ead6 100644
--- a/UploadWizardHooks.php
+++ b/UploadWizardHooks.php
@@ -51,7 +51,6 @@
                                'uw.controller.Thanks',
                                'uw.controller.Tutorial',
                                'uw.controller.Upload',
-                               'uw.model.Description',
 
                                'oojs',
                                'oojs-ui',
@@ -72,6 +71,8 @@
                                'resources/uw.FieldLayout.js',
                                'resources/details/uw.DateDetailsWidget.js',
                                
'resources/details/uw.CategoriesDetailsWidget.js',
+                               
'resources/details/uw.DescriptionDetailsWidget.js',
+                               
'resources/details/uw.DescriptionsDetailsWidget.js',
 
                                // common utilities
                                'resources/mw.fileApi.js',
@@ -81,9 +82,6 @@
 
                                // wikimedia-comons specific title checker
                                
'resources/jquery/jquery.validate.wmCommonsBlacklist.js',
-
-                               // language menus
-                               'resources/mw.LanguageUpWiz.js',
 
                                // workhorse libraries
                                'resources/mw.DestinationChecker.js',
@@ -109,7 +107,6 @@
                                // main library components:
                                'resources/mw.UploadWizardUpload.js',
                                'resources/mw.UploadWizardDeed.js',
-                               'resources/mw.UploadWizardDescription.js',
                                'resources/mw.UploadWizardDetails.js',
                                'resources/mw.UploadWizardUploadInterface.js',
                        ),
@@ -119,6 +116,8 @@
                                // OOjs UI interface elements
                                'resources/details/uw.DateDetailsWidget.less',
                                
'resources/details/uw.CategoriesDetailsWidget.less',
+                               
'resources/details/uw.DescriptionDetailsWidget.less',
+                               
'resources/details/uw.DescriptionsDetailsWidget.less',
                        ),
                        'messages' => array(
                                'comma-separator',
@@ -340,6 +339,7 @@
                                'mwe-upwiz-error-blank',
                                'mwe-upwiz-error-too-long',
                                'mwe-upwiz-error-too-short',
+                               'mwe-upwiz-error-bad-descriptions',
                                'mwe-upwiz-error-bad-chars',
                                'mwe-upwiz-error-title-blacklisted',
                                'mwe-upwiz-error-title-badchars',
@@ -663,26 +663,6 @@
                        ),
                ),
 
-               'uw.model.base' => array(
-                       'scripts' => array(
-                               'resources/uw/model/uw.model.base.js',
-                       ),
-
-                       'dependencies' => array(
-                               'uw.base',
-                       ),
-               ),
-
-               'uw.model.Description' => array(
-                       'scripts' => array(
-                               'resources/uw/model/uw.model.Description.js',
-                       ),
-
-                       'dependencies' => array(
-                               'uw.model.base',
-                       ),
-               ),
-
                'uw.ui.Tutorial' => array(
                        'scripts' => array(
                                'resources/ui/uw.ui.Tutorial.js',
@@ -926,7 +906,6 @@
                                'tests/qunit/mw.UploadWizard.test.js',
                                'tests/qunit/mw.UploadWizardUpload.test.js',
                                
'tests/qunit/mw.UploadWizardLicenseInput.test.js',
-                               'tests/qunit/mw.uw.model.Description.test.js',
                                'tests/qunit/mw.FlickrChecker.test.js',
                                'tests/qunit/mw.UploadWizardDetails.test.js',
                                'tests/qunit/mw.fileApi.test.js',
diff --git a/i18n/en.json b/i18n/en.json
index ceafee3..1a8c43b 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -204,6 +204,7 @@
        "mwe-upwiz-error-nologin": "You must be <span 
class=\"plainlinks\">[{{fullurl:{{#Special:UserLogin}}|returnto=$1}} logged 
in]</span> to upload files.",
        "mwe-upwiz-error-too-long": "This entry is too long.\nPlease make this 
entry shorter than $1 {{PLURAL:$1|character|characters}}.",
        "mwe-upwiz-error-too-short": "This entry is too short.\nPlease make 
this entry longer than $1 {{PLURAL:$1|character|characters}}.",
+       "mwe-upwiz-error-bad-descriptions": "There are problems with some of 
the descriptions.",
        "mwe-upwiz-error-bad-chars": "This entry contains symbols that are not 
allowed.\nPlease do not use wikitext or HTML here.",
        "mwe-upwiz-error-title-blacklisted": "This title contains some 
undesirable text. Please revise it.",
        "mwe-upwiz-error-title-badchars": "This title contains some undesirable 
characters. Please remove them.",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index c44252b..68d2c16 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -227,6 +227,7 @@
        "mwe-upwiz-error-nologin": "{{doc-important|Do not translate 
<code><nowiki><span 
class=\"plainlinks\">[{{fullurl:{{#Special:UserLogin}}|returnto=$1}}</nowiki></code>.}}\nError
 shown when user is not logged in, returns them to the upload 
form.\n\nParameters:\n* $1 - page title",
        "mwe-upwiz-error-too-long": "Used as error message. Parameters:\n* $1 - 
maximum number of characters\nSee also:\n* 
{{msg-mw|Mwe-upwiz-error-too-short}}",
        "mwe-upwiz-error-too-short": "Used as error message. Parameters:\n* $1 
- minimum number of characters\nSee also:\n* 
{{msg-mw|Mwe-upwiz-error-too-long}}",
+       "mwe-upwiz-error-bad-descriptions": "Used as error message.",
        "mwe-upwiz-error-bad-chars": "Error message shown to the user when they 
have entered characters that aren't allowed.",
        "mwe-upwiz-error-title-blacklisted": "Error message shown to the user 
when they have entered a file name that matches the 'blacklist' of banned 
words.",
        "mwe-upwiz-error-title-badchars": "Error message shown to the user when 
they have entered a file name that matches the 'blacklist' of banned 
characters.",
diff --git a/resources/details/uw.DescriptionDetailsWidget.js 
b/resources/details/uw.DescriptionDetailsWidget.js
new file mode 100644
index 0000000..a5189b4
--- /dev/null
+++ b/resources/details/uw.DescriptionDetailsWidget.js
@@ -0,0 +1,180 @@
+( function ( mw, uw, $, OO ) {
+
+       /**
+        * A description field in UploadWizard's "Details" step form.
+        *
+        * @extends uw.DetailsWidget
+        */
+       uw.DescriptionDetailsWidget = function UWDescriptionDetailsWidget() {
+               uw.DescriptionDetailsWidget.parent.call( this );
+
+               this.languageDropdown = new OO.ui.DropdownWidget( {
+                       menu: { items: this.getLanguageDropdownOptions() },
+                       classes: [ 'mwe-upwiz-desc-lang-select', 
'mwe-upwiz-descriptionDetailsWidget-language' ]
+               } );
+               this.descriptionInput = new OO.ui.TextInputWidget( {
+                       classes: [ 'mwe-upwiz-desc-lang-text', 
'mwe-upwiz-descriptionDetailsWidget-description' ],
+                       placeholder: mw.message( 'mwe-upwiz-desc-placeholder' 
).text(),
+                       multiline: true,
+                       autosize: true,
+                       rows: 2
+               } );
+
+               this.languageDropdown.getMenu()
+                       .selectItemByData( 
this.constructor.static.getDefaultLanguage() );
+
+               // Aggregate 'change' event
+               this.languageDropdown.connect( this, { change: [ 'emit', 
'change' ] } );
+               // (but do not flash warnings in the user's face while they're 
typing)
+               this.descriptionInput.on( 'change', OO.ui.debounce( 
this.emit.bind( this, 'change' ), 500 ) );
+
+               this.$element.addClass( 'mwe-upwiz-descriptionDetailsWidget' );
+               this.$element.append(
+                       this.languageDropdown.$element,
+                       this.descriptionInput.$element
+               );
+       };
+       OO.inheritClass( uw.DescriptionDetailsWidget, uw.DetailsWidget );
+
+       /**
+        * Check if the given language code can be used for descriptions.
+        * If not, try finding a similar language code that can be.
+        *
+        * @private
+        * @param {string} code Language code
+        * @return {string|null}
+        */
+       uw.DescriptionDetailsWidget.static.getClosestAllowedLanguage = function 
( code ) {
+               // Is this still needed?
+               if ( code === 'nan' || code === 'minnan' ) {
+                       code = 'zh-min-nan';
+               }
+               if ( mw.UploadWizard.config.uwLanguages[ code ] ) {
+                       return code;
+               }
+               if ( code.lastIndexOf( '-' ) !== -1 ) {
+                       return this.getClosestAllowedLanguage( code.substring( 
0, code.lastIndexOf( '-' ) ) );
+               }
+               return this.getDefaultLanguage();
+       };
+
+       /**
+        * Get the default language to use for descriptions.
+        * Choose a sane default based on user preferences and wiki config.
+        *
+        * @private
+        * @return {string}
+        */
+       uw.DescriptionDetailsWidget.static.getDefaultLanguage = function () {
+               var defaultLanguage;
+
+               if ( this.defaultLanguage !== undefined ) {
+                       return this.defaultLanguage;
+               }
+
+               if ( this.getClosestAllowedLanguage( mw.config.get( 
'wgUserLanguage' ) ) ) {
+                       defaultLanguage = this.getClosestAllowedLanguage( 
mw.config.get( 'wgUserLanguage' ) );
+               } else if ( this.getClosestAllowedLanguage( mw.config.get( 
'wgContentLanguage' ) ) ) {
+                       defaultLanguage = this.getClosestAllowedLanguage( 
mw.config.get( 'wgContentLanguage' ) );
+               } else if ( this.getClosestAllowedLanguage( 'en' ) ) {
+                       defaultLanguage = this.getClosestAllowedLanguage( 'en' 
);
+               } else {
+                       defaultLanguage = Object.keys( 
mw.UploadWizard.config.uwLanguages )[ 0 ];
+               }
+
+               // Logic copied from MediaWiki:UploadForm.js
+               // Per request from Portuguese and Brazilian users, treat 
Brazilian Portuguese as Portuguese.
+               if ( defaultLanguage === 'pt-br' ) {
+                       defaultLanguage = 'pt';
+               // this was also in UploadForm.js, but without the heartwarming 
justification
+               } else if ( defaultLanguage === 'en-gb' ) {
+                       defaultLanguage = 'en';
+               }
+
+               this.defaultLanguage = defaultLanguage;
+               return defaultLanguage;
+       };
+
+       /**
+        * Get options for the dropdown list of all allowed languages.
+        *
+        * @private
+        * @return {OO.ui.MenuOptionWidget[]}
+        */
+       uw.DescriptionDetailsWidget.prototype.getLanguageDropdownOptions = 
function () {
+               var options, code, language;
+
+               options = [];
+               for ( code in mw.UploadWizard.config.uwLanguages ) {
+                       language = mw.UploadWizard.config.uwLanguages[ code ];
+                       options.push( new OO.ui.MenuOptionWidget( {
+                               data: code,
+                               label: language
+                       } ) );
+               }
+               return options;
+       };
+
+       /**
+        * @inheritdoc
+        */
+       uw.DescriptionDetailsWidget.prototype.getErrors = function () {
+               var
+                       errors = [],
+                       minLength = mw.UploadWizard.config.minDescriptionLength,
+                       maxLength = mw.UploadWizard.config.maxDescriptionLength,
+                       descriptionText = 
this.descriptionInput.getValue().trim();
+
+               if ( descriptionText.length !== 0 && descriptionText.length < 
minLength ) {
+                       // Empty description is allowed
+                       errors.push( mw.message( 'mwe-upwiz-error-too-short', 
minLength - 1 ) );
+               }
+               if ( descriptionText.length > maxLength ) {
+                       errors.push( mw.message( 'mwe-upwiz-error-too-long', 
maxLength + 1 ) );
+               }
+
+               return $.Deferred().resolve( errors ).promise();
+       };
+
+       /**
+        * @inheritdoc
+        */
+       uw.DescriptionDetailsWidget.prototype.getWikiText = function () {
+               var
+                       language = 
this.languageDropdown.getMenu().getSelectedItem().getData(),
+                       description = this.descriptionInput.getValue().trim();
+
+               if ( !description ) {
+                       return '';
+               }
+
+               if ( mw.UploadWizard.config.languageTemplateFixups[ language ] 
) {
+                       language = 
mw.UploadWizard.config.languageTemplateFixups[ language ];
+               }
+
+               return '{{' + language + '|1=' + description + '}}';
+       };
+
+       /**
+        * @inheritdoc
+        * @return {Object} See #setSerialized
+        */
+       uw.DescriptionDetailsWidget.prototype.getSerialized = function () {
+               return {
+                       language: 
this.languageDropdown.getMenu().getSelectedItem().getData(),
+                       description: this.descriptionInput.getValue()
+               };
+       };
+
+       /**
+        * @inheritdoc
+        * @param {Object} serialized
+        * @param {string} serialized.language Description language code
+        * @param {string} serialized.description Description text
+        */
+       uw.DescriptionDetailsWidget.prototype.setSerialized = function ( 
serialized ) {
+               this.languageDropdown.getMenu().selectItemByData( 
serialized.language );
+               this.descriptionInput.setValue( serialized.description );
+       };
+
+} )( mediaWiki, mediaWiki.uploadWizard, jQuery, OO );
diff --git a/resources/details/uw.DescriptionDetailsWidget.less 
b/resources/details/uw.DescriptionDetailsWidget.less
new file mode 100644
index 0000000..3c90aa5
--- /dev/null
+++ b/resources/details/uw.DescriptionDetailsWidget.less
@@ -0,0 +1,16 @@
+.mwe-upwiz-descriptionDetailsWidget {
+       .mwe-upwiz-descriptionDetailsWidget-language {
+               width: 11.5em;
+       }
+
+       .mwe-upwiz-descriptionDetailsWidget-description {
+               width: 30.5em;
+       }
+
+       .mwe-upwiz-descriptionDetailsWidget-language,
+       .mwe-upwiz-descriptionDetailsWidget-description {
+               display: inline-block;
+               // 'top' because the description text input is autosized
+               vertical-align: top;
+       }
+}
diff --git a/resources/details/uw.DescriptionsDetailsWidget.js 
b/resources/details/uw.DescriptionsDetailsWidget.js
new file mode 100644
index 0000000..a1bdef5
--- /dev/null
+++ b/resources/details/uw.DescriptionsDetailsWidget.js
@@ -0,0 +1,161 @@
+( function ( mw, uw, $, OO ) {
+
+       /**
+        * A descriptions field in UploadWizard's "Details" step form.
+        *
+        * @extends uw.DetailsWidget
+        * @mixins OO.ui.mixin.GroupElement
+        */
+       uw.DescriptionsDetailsWidget = function UWDescriptionsDetailsWidget( 
config ) {
+               config = $.extend( { required: true }, config );
+               uw.DescriptionsDetailsWidget.parent.call( this );
+               OO.ui.mixin.GroupElement.call( this );
+
+               this.required = !!config.required;
+               this.addDescriptionButton = new OO.ui.ButtonWidget( {
+                       classes: [ 'mwe-upwiz-details-descriptions-add' ],
+                       framed: false,
+                       flags: [ 'constructive' ],
+                       // Messages: mwe-upwiz-desc-add-0, mwe-upwiz-desc-add-n
+                       label: mw.msg( 'mwe-upwiz-desc-add-' + ( !this.required 
? '0' : 'n' ) )
+               } );
+               this.addDescriptionButton.connect( this, { click: 
'addDescription' } );
+
+               this.$element.addClass( 'mwe-upwiz-descriptionsDetailsWidget' );
+               this.$element.append(
+                       this.$group,
+                       this.addDescriptionButton.$element
+               );
+
+               if ( this.required ) {
+                       this.addItems( [ new uw.FieldLayout( new 
uw.DescriptionDetailsWidget() ) ] );
+                       // Hide the "Remove" button for first description if 
this field is required
+                       this.items[ 0 ].$element.next().hide();
+               }
+       };
+       OO.inheritClass( uw.DescriptionsDetailsWidget, uw.DetailsWidget );
+       OO.mixinClass( uw.DescriptionsDetailsWidget, OO.ui.mixin.GroupElement );
+
+       /**
+        * Add a description in another language.
+        */
+       uw.DescriptionsDetailsWidget.prototype.addDescription = function () {
+               this.addItems( [ new uw.FieldLayout( new 
uw.DescriptionDetailsWidget() ) ] );
+               this.recountDescriptions();
+       };
+
+       /**
+        * Update the button label after adding or removing descriptions.
+        */
+       uw.DescriptionsDetailsWidget.prototype.recountDescriptions = function 
() {
+               // Messages: mwe-upwiz-desc-add-0, mwe-upwiz-desc-add-n
+               var label = mw.msg( 'mwe-upwiz-desc-add-' + ( this.items.length 
=== 0 ? '0' : 'n' ) );
+               this.addDescriptionButton.setLabel( label );
+               this.emit( 'change' );
+       };
+
+       /**
+        * @inheritdoc
+        */
+       uw.DescriptionsDetailsWidget.prototype.addItems = function ( items, 
index ) {
+               // Mixin method
+               OO.ui.mixin.GroupElement.prototype.addItems.call( this, items, 
index );
+               items.forEach( function ( item ) {
+                       // Aggregate 'change' event
+                       item.fieldWidget.connect( this, { change: [ 'emit', 
'change' ] } );
+
+                       // Insert "Remove" button
+                       var removeButton = new OO.ui.ButtonWidget( {
+                               classes: [ 'mwe-upwiz-remove-ctrl', 
'mwe-upwiz-descriptionsDetailsWidget-removeItem' ],
+                               icon: 'remove',
+                               framed: false,
+                               flags: [ 'destructive' ],
+                               title: mw.message( 
'mwe-upwiz-remove-description' ).text()
+                       } );
+                       removeButton.on( 'click', function () {
+                               removeButton.$element.remove();
+                               this.removeItems( [ item ] );
+                               this.recountDescriptions();
+                       }.bind( this ) );
+                       item.$element.after( removeButton.$element );
+
+               }.bind( this ) );
+               return this;
+       };
+
+       /**
+        * @inheritdoc
+        */
+       uw.DescriptionsDetailsWidget.prototype.getErrors = function () {
+               // Gather errors from each item
+               var errorPromises = this.getItems().map( function ( item ) {
+                       return item.fieldWidget.getErrors();
+               } );
+               return $.when.apply( $, errorPromises ).then( function () {
+                       var i, errors;
+                       errors = [];
+                       // Fold all errors into a single one (they are 
displayed in the UI for each item, but we still
+                       // need to return an error here to prevent form 
submission).
+                       for ( i = 0; i < arguments.length; i++ ) {
+                               if ( arguments[ i ].length ) {
+                                       // One of the items has errors
+                                       errors.push( mw.message( 
'mwe-upwiz-error-bad-descriptions' ) );
+                                       break;
+                               }
+                       }
+                       // And add some more:
+                       if ( this.required && this.getWikiText() === '' ) {
+                               errors.push( mw.message( 
'mwe-upwiz-error-blank' ) );
+                       }
+                       // TODO Check for duplicate languages
+                       return errors;
+               }.bind( this ) );
+       };
+
+       /**
+        * @inheritdoc
+        */
+       uw.DescriptionsDetailsWidget.prototype.getWikiText = function () {
+               // Some code here and in mw.UploadWizardDetails relies on this 
function returning an empty
+               // string when there are some descriptions, but all are empty.
+               return this.getItems().map( function ( layout ) {
+                       return layout.fieldWidget.getWikiText();
+               } ).filter( function ( wikiText ) {
+                       return !!wikiText;
+               } ).join( '\n' );
+       };
+
+       /**
+        * @inheritdoc
+        * @return {Object} See #setSerialized
+        */
+       uw.DescriptionsDetailsWidget.prototype.getSerialized = function () {
+               var descriptions = this.getItems().map( function ( layout ) {
+                       return layout.fieldWidget.getSerialized();
+               } );
+               return {
+                       descriptions: descriptions
+               };
+       };
+
+       /**
+        * @inheritdoc
+        * @param {Object} serialized
+        * @param {Object[]} serialized.descriptions Array of serialized 
descriptions,
+        *   see uw.DescriptionDetailsWidget#setSerialized
+        */
+       uw.DescriptionsDetailsWidget.prototype.setSerialized = function ( 
serialized ) {
+               var items = serialized.descriptions.map( function ( serialized 
) {
+                       var layout = new uw.FieldLayout( new 
uw.DescriptionDetailsWidget() );
+                       layout.fieldWidget.setSerialized( serialized );
+                       return layout;
+               }.bind( this ) );
+               this.clearItems().addItems( items );
+               if ( this.required ) {
+                       // Hide the "Remove" button for first description if 
this field is required
+                       this.items[ 0 ].$element.next().hide();
+               }
+               this.recountDescriptions();
+       };
+
+} )( mediaWiki, mediaWiki.uploadWizard, jQuery, OO );
diff --git a/resources/details/uw.DescriptionsDetailsWidget.less 
b/resources/details/uw.DescriptionsDetailsWidget.less
new file mode 100644
index 0000000..9c233d7
--- /dev/null
+++ b/resources/details/uw.DescriptionsDetailsWidget.less
@@ -0,0 +1,20 @@
+.mwe-upwiz-descriptionsDetailsWidget {
+       .mwe-upwiz-descriptionsDetailsWidget-removeItem {
+               // HACK Until T97631 is fixed, otherwise the icons look awful
+               &.oo-ui-iconElement > .oo-ui-buttonElement-button > 
.oo-ui-iconElement-icon {
+                       width: 24px;
+               }
+       }
+
+       // Allow the "Remove" button to appear alongside
+       .mwe-upwiz-details-fieldname-input {
+               display: inline-block;
+               margin-bottom: 0;
+               margin-right: 0.5em;
+       }
+
+       .mwe-upwiz-descriptionsDetailsWidget-removeItem {
+               // 'top' because the description text input is autosized
+               vertical-align: top;
+       }
+}
diff --git a/resources/mw.LanguageUpWiz.js b/resources/mw.LanguageUpWiz.js
deleted file mode 100644
index 1c382df..0000000
--- a/resources/mw.LanguageUpWiz.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/* jshint nomen: false */
-( function ( mw, $ ) {
-       /**
-        * Utility class which knows about languages, and how to construct HTML 
to select them
-        * TODO: make this a more common library, used by this and TimedText
-        */
-       mw.LanguageUpWiz = {
-
-               defaultCode: null,
-
-               initialized: false,
-
-               /**
-                * List of default languages
-                * Make sure you have language templates set up for each of 
these on your wiki, e.g. {{en}}
-                * TODO make this more configurable.
-                * This is set in the initialize().
-                */
-               languages: null,
-
-               // Helper function to see if a language is in the list.
-               checkForLang: function ( lang ) {
-                       var langIndex;
-                       for ( langIndex in mw.LanguageUpWiz.languages ) {
-                               if ( mw.LanguageUpWiz.languages[ langIndex 
].code === lang ) {
-                                       return true;
-                               }
-                       }
-                       return false;
-               },
-
-               /**
-                * cache some useful objects
-                * 1) mostly ready-to-go language HTML menu. When/if we 
upgrade, make it a jQuery combobox
-                * 2) dict of language code to name -- useful for testing for 
existence, maybe other things.
-                */
-               initialize: function () {
-                       var langs, langcode,
-                               thisUri = new mw.Uri( window.location.href, { 
overrideKeys: true } ),
-                               $select = $( '<select>' );
-                       if ( mw.LanguageUpWiz.initialized ) {
-                               return;
-                       }
-                       mw.LanguageUpWiz.languages = [];
-                       langs = mw.UploadWizard.config.uwLanguages;
-                       for ( langcode in langs ) {
-                               mw.LanguageUpWiz.languages.push( { code: 
langcode, text: langs[ langcode ] } );
-                       }
-
-                       // If a descriptionlang param is passed in the query 
string, use that,
-                       // otherwise choose a good default for the description 
language.
-
-                       if ( thisUri.query.descriptionlang && 
mw.LanguageUpWiz.checkForLang( thisUri.query.descriptionlang ) ) {
-                               mw.LanguageUpWiz.defaultCode = 
thisUri.query.descriptionlang;
-                       } else if ( mw.LanguageUpWiz.checkForLang( 
mw.config.get( 'wgUserLanguage' ) ) ) {
-                               mw.LanguageUpWiz.defaultCode = mw.config.get( 
'wgUserLanguage' );
-                       } else if ( mw.LanguageUpWiz.checkForLang( 
mw.config.get( 'wgContentLanguage' ) ) ) {
-                               mw.LanguageUpWiz.defaultCode = mw.config.get( 
'wgContentLanguage' );
-                       } else if ( mw.LanguageUpWiz.checkForLang( 'en' ) ) {
-                               mw.LanguageUpWiz.defaultCode = 'en';
-                       } else {
-                               mw.LanguageUpWiz.defaultCode = 
mw.LanguageUpWiz.languages[ 0 ].code;
-                       }
-
-                       mw.LanguageUpWiz.codes = {};
-                       $.each( mw.LanguageUpWiz.languages, function ( i, 
language ) {
-                               // add an option for each language
-                               var $opt = $( '<option>' )
-                                       .prop( 'value', language.code )
-                                       .text( language.text )
-                                       .prop( 'selected', language.code === 
this.defaultCode );
-
-                               $select.append( $opt );
-
-                               // add each language into dictionary
-                               mw.LanguageUpWiz.codes[ language.code ] = 
language.text;
-                       } );
-
-                       mw.LanguageUpWiz.$select = $select;
-                       mw.LanguageUpWiz.initialized = true;
-               },
-
-               /**
-                * Get an HTML select menu of all our languages.
-                *
-                * @param {string} name Desired name of select element
-                * @param {string} [code] Selected language code
-                * @return {HTMLSelectElement} The `select` element configured 
as desired
-                */
-               getMenu: function ( name, code ) {
-                       var $select;
-
-                       mw.LanguageUpWiz.initialize();
-                       /* If we did not request a specific selected language 
code, see if we have a default. */
-                       if ( !code ) {
-                               code = mw.LanguageUpWiz.defaultCode;
-                       }
-
-                       $select = mw.LanguageUpWiz.$select
-                               .clone()
-                               .prop( 'name', name );
-
-                       /* Pre select the 'code' language */
-                       if ( mw.LanguageUpWiz.checkForLang( code ) ) {
-                               $select.val( mw.LanguageUpWiz.getClosest( code 
) );
-                       }
-
-                       return $select.get( 0 );
-               },
-
-               /**
-                * Figure out the closest language we have to a supplied 
language code.
-                * It seems that people on Mediawiki set their language code as 
freetext, and it could be anything, even
-                * variants we don't have a record for, or ones that are not in 
any ISO standard.
-                *
-                * Logic copied from MediaWiki:LanguageHandler.js
-                * handle null cases, special cases for some Chinese variants
-                * Otherwise, if handed "foo-bar-baz" language, try to match 
most specific language,
-                *      "foo-bar-baz", then "foo-bar", then "foo"
-                *
-                * @param {string} code A string representing a language code, 
which we may or may not have.
-                *                              Expected to be separated with 
dashes as codes from ISO 639, e.g. "zh-tw" for Chinese ( Traditional )
-                * @return {string} A language code which is close to the 
supplied parameter, or fall back to mw.LanguageUpWiz.defaultCode
-                */
-               getClosest: function ( code ) {
-                       mw.LanguageUpWiz.initialize();
-                       if ( typeof ( code ) !== 'string' || code === null || 
code.length === 0 ) {
-                               return mw.LanguageUpWiz.defaultCode;
-                       }
-                       if ( code === 'nan' || code === 'minnan' ) {
-                               return 'zh-min-nan';
-                       } else if ( mw.LanguageUpWiz.codes[ code ] !== 
undefined ) {
-                               return code;
-                       }
-                       return mw.LanguageUpWiz.getClosest( code.substring( 0, 
code.indexOf( '-' ) ) );
-               }
-       };
-}( mediaWiki, jQuery ) );
diff --git a/resources/mw.UploadWizardDescription.js 
b/resources/mw.UploadWizardDescription.js
deleted file mode 100644
index 770b9ca..0000000
--- a/resources/mw.UploadWizardDescription.js
+++ /dev/null
@@ -1,152 +0,0 @@
-( function ( mw, $, uw ) {
-       /**
-        * Object that represents an indvidual language description, in the 
details portion of Upload Wizard
-        *
-        * @param {string} languageCode
-        * @param {boolean} required The first description is required and 
should be validated and displayed a bit differently
-        * @param {string} [initialValue] If set, an initial value to which to 
set the language.
-        */
-       mw.UploadWizardDescription = function ( languageCode, required, 
initialValue ) {
-               var errorLabelDiv, fieldnameDiv;
-
-               mw.UploadWizardDescription.prototype.count++;
-               this.id = 'description' + 
mw.UploadWizardDescription.prototype.count;
-               this.isRequired = required;
-
-               // XXX for some reason this display:block is not making it into 
HTML
-               errorLabelDiv = $(
-                               '<div class="mwe-upwiz-details-input-error">' +
-                                       '<label generated="true" 
class="mwe-validator-error" for="' + this.id + '" />' +
-                               '</div>'
-                       );
-               fieldnameDiv = $( '<div class="mwe-upwiz-details-fieldname" />' 
);
-
-               fieldnameDiv.text( mw.message( 'mwe-upwiz-desc' ).text() 
).addHint( 'description' );
-
-               if ( this.isRequired ) {
-                       fieldnameDiv.requiredFieldLabel();
-               }
-
-               // Logic copied from MediaWiki:UploadForm.js
-               // Per request from Portuguese and Brazilian users, treat 
Brazilian Portuguese as Portuguese.
-               if ( languageCode === 'pt-br' ) {
-                       languageCode = 'pt';
-               // this was also in UploadForm.js, but without the heartwarming 
justification
-               } else if ( languageCode === 'en-gb' ) {
-                       languageCode = 'en';
-               }
-
-               this.languageMenu = mw.LanguageUpWiz.getMenu( 'lang', 
languageCode );
-               $( this.languageMenu ).addClass( 'mwe-upwiz-desc-lang-select' );
-
-               this.input = $( '<textarea>' )
-                       .attr( {
-                               name: this.id,
-                               rows: 2,
-                               cols: 36,
-                               'class': 'mwe-upwiz-desc-lang-text',
-                               placeholder: mw.message( 
'mwe-upwiz-desc-placeholder' ).text()
-                       } )
-                       .growTextArea();
-
-               if ( initialValue !== undefined ) {
-                       this.input.val( initialValue );
-               }
-
-               // descriptions
-               this.div = $( '<div 
class="mwe-upwiz-details-descriptions-container ui-helper-clearfix"></div>' )
-                               .append( errorLabelDiv, fieldnameDiv, 
this.languageMenu, this.input );
-
-               this.description = new uw.model.Description( languageCode, 
initialValue, mw.UploadWizard.config.languageTemplateFixups );
-       };
-
-       mw.UploadWizardDescription.prototype = {
-
-               /* widget count for auto incrementing */
-               count: 0,
-
-               setText: function ( text ) {
-                       // strip out any HTML tags
-                       text = text.replace( /<[^>]+>/g, '' );
-                       // & and " are escaped by Flickr, so we need to unescape
-                       text = text.replace( /&amp;/g, '&' ).replace( 
/&quot;/g, '"' );
-                       $( this.input ).val( text.trim() );
-               },
-
-               getWikiText: function () {
-                       this.updateDescriptionLanguage();
-                       this.updateDescriptionText();
-
-                       return this.description.getValue();
-               },
-
-               getDescriptionText: function () {
-                       this.updateDescriptionText();
-                       return this.description.text;
-               },
-
-               updateDescriptionText: function () {
-                       this.description.setText( $( this.input ).val().trim() 
);
-               },
-
-               updateDescriptionLanguage: function () {
-                       this.description.setLanguage( $( this.languageMenu 
).val().trim() );
-               },
-
-               getLanguage: function () {
-                       return $( this.languageMenu ).val().trim();
-               },
-
-               /**
-                * Sets the language of a description
-                *
-                * @param {string} ietfLanguageTag the selector should be set to
-                */
-               setLanguage: function ( ietfLanguageTag ) {
-                       $( this.languageMenu ).val( ietfLanguageTag.replace( 
/_/g, '-' ).toLowerCase() );
-               },
-
-               /**
-                * Locks the language specifier
-                */
-               lockLanguageMenu: function () {
-                       $( this.languageMenu ).prop( 'disabled', true );
-               },
-
-               /**
-                * Unlocks the language specifier
-                */
-               unlockLanguageMenu: function () {
-                       $( this.languageMenu ).prop( 'disabled', false );
-               },
-
-               /**
-                * defer adding rules until it's in a form
-                *
-                * @return {Object|false} validator
-                */
-               addValidationRules: function ( required ) {
-                       // Make sure the keyup event doesn't bubble...we don't 
care
-                       // about validity until blur.
-                       // Note that this event is caught higher up by the 
jQuery
-                       // validation plugin, and they don't have any options 
for
-                       // setting which events should trigger a check.
-                       this.input.on( 'keyup', function ( e ) {
-                               e.stopPropagation();
-                               return false;
-                       } );
-
-                       // validator must find a form, so we add rules here
-                       return this.input.rules( 'add', {
-                               minlength: 
mw.UploadWizard.config.minDescriptionLength,
-                               maxlength: 
mw.UploadWizard.config.maxDescriptionLength,
-                               required: required,
-                               messages: {
-                                       required: mw.message( 
'mwe-upwiz-error-blank' ).escaped(),
-                                       minlength: mw.message( 
'mwe-upwiz-error-too-short', mw.UploadWizard.config.minDescriptionLength - 1 
).escaped(),
-                                       maxlength: mw.message( 
'mwe-upwiz-error-too-long', mw.UploadWizard.config.maxDescriptionLength + 1 
).escaped()
-                               }
-                       } );
-               }
-       };
-}( mediaWiki, jQuery, mediaWiki.uploadWizard ) );
diff --git a/resources/mw.UploadWizardDetails.js 
b/resources/mw.UploadWizardDetails.js
index d831147..4be36ed 100644
--- a/resources/mw.UploadWizardDetails.js
+++ b/resources/mw.UploadWizardDetails.js
@@ -10,7 +10,8 @@
         * @param {jQuery} containerDiv The `div` to put the interface into
         */
        mw.UploadWizardDetails = function ( upload, containerDiv ) {
-               var descriptionAdderDiv, titleContainerDiv,
+               var titleContainerDiv,
+                       descriptionRequired, uri,
                        categoriesHinter,
                        moreDetailsCtrlDiv, moreDetailsDiv, otherInformationId,
                        otherInformationDiv, latitudeDiv, longitudeDiv, 
headingDiv,
@@ -22,8 +23,6 @@
                this.containerDiv = containerDiv;
                this.api = upload.api;
 
-               this.descriptions = [];
-
                this.div = $( '<div class="mwe-upwiz-info-file 
ui-helper-clearfix filled"></div>' );
 
                this.thumbnailDiv = $( '<div class="mwe-upwiz-thumbnail 
mwe-upwiz-thumbnail-side"></div>' );
@@ -31,18 +30,23 @@
                this.dataDiv = $( '<div class="mwe-upwiz-data"></div>' );
 
                // descriptions
-               this.descriptionsDiv = $( '<div 
class="mwe-upwiz-details-descriptions"></div>' );
+               // Description is not required if a campaign provides 
alternative wikitext fields,
+               // which are assumed to function like a description
+               descriptionRequired = !(
+                       mw.UploadWizard.config.fields &&
+                       mw.UploadWizard.config.fields.length &&
+                       mw.UploadWizard.config.fields[ 0 ].wikitext
+               );
+               this.descriptionsDetails = new uw.DescriptionsDetailsWidget( {
+                       required: descriptionRequired
+               } );
+               this.descriptionsDetailsField = new uw.FieldLayout( 
this.descriptionsDetails, {
+                       label: mw.message( 'mwe-upwiz-desc' ).text(),
+                       required: descriptionRequired
+               } );
+               // TODO Rethink hints
+               this.descriptionsDetailsField.$label.addHint( 'description' );
 
-               this.descriptionAdder = $( '<a 
class="mwe-upwiz-more-options"></a>' )
-                                               .text( mw.message( 
'mwe-upwiz-desc-add-0' ).text() )
-                                               .click( function ( ) { 
details.addDescription(); } );
-
-               descriptionAdderDiv =
-                       $( '<div>' ).append(
-                               $( '<div 
class="mwe-upwiz-details-fieldname"></div>' ),
-                               $( '<div 
class="mwe-upwiz-details-descriptions-add"></div>' )
-                                               .append( this.descriptionAdder )
-                       );
 
                // Commons specific help for titles
                //  https://commons.wikimedia.org/wiki/Commons:File_naming
@@ -190,8 +194,7 @@
                this.$form = $( '<form id="mwe-upwiz-detailsform' + 
this.upload.index + '"></form>' ).addClass( 'detailsForm' );
                this.$form.append(
                        titleContainerDiv,
-                       this.descriptionsDiv,
-                       descriptionAdderDiv,
+                       this.descriptionsDetailsField.$element,
                        this.copyrightInfoFieldset,
                        this.dateDetailsField.$element,
                        this.categoriesDetailsField.$element
@@ -341,16 +344,17 @@
                        'mwe-upwiz-more-options'
                );
 
-               this.addDescription(
-                       !(
-                               mw.UploadWizard.config.fields &&
-                               mw.UploadWizard.config.fields.length &&
-                               mw.UploadWizard.config.fields[ 0 ].wikitext
-                       ),
-                       undefined,
-                       false,
-                       mw.UploadWizard.config.defaults.description
-               );
+               uri = new mw.Uri( location.href, { overrideKeys: true } );
+               if ( mw.UploadWizard.config.defaults.description ) {
+                       this.descriptionsDetails.setSerialized( {
+                               descriptions: [
+                                       {
+                                               language: 
uw.DescriptionDetailsWidget.static.getClosestAllowedLanguage( 
uri.query.descriptionlang ),
+                                               description: 
mw.UploadWizard.config.defaults.description
+                                       }
+                               ]
+                       } );
+               }
 
                if ( mw.config.get( 'UploadWizardConfig' ).useTitleBlacklistApi 
) {
                        // less strict checking, since TitleBlacklist checks 
should catch most errors.
@@ -506,7 +510,6 @@
                 */
                copyMetadata: function ( metadataType ) {
                        var titleZero, matches,
-                               details = this,
                                uploads = this.upload.wizard.uploads,
                                sourceId = uploads[ 0 ].index;
 
@@ -531,15 +534,13 @@
                                } );
                        }
 
-                       function oouiCopy( property, methods ) {
+                       function oouiCopy( property ) {
                                var i,
                                        sourceUpload = uploads[ 0 ],
-                                       getMethod = methods && methods.get || 
'getValue',
-                                       setMethod = methods && methods.set || 
'setValue',
-                                       sourceValue = sourceUpload.details[ 
property ][ getMethod ]();
+                                       sourceValue = sourceUpload.details[ 
property ].getSerialized();
 
                                for ( i = 1; i < uploads.length; i++ ) {
-                                       uploads[ i ].details[ property ][ 
setMethod ]( sourceValue );
+                                       uploads[ i ].details[ property 
].setSerialized( sourceValue );
                                }
                        }
 
@@ -571,37 +572,16 @@
 
                                } );
                        } else if ( metadataType === 'description' ) {
-                               $.each( uploads, function ( uploadIndex, upload 
) {
-                                       if ( upload !== undefined && 
upload.index !== sourceId ) {
 
-                                               // We could merge, but it's 
unlikely that the user wants to do anything other
-                                               // than just having the same 
descriptions across all files, so rather than
-                                               // create unintended 
consequences, we nuke any existing descriptions first.
-                                               
upload.details.removeAllDescriptions();
-
-                                               $.each( details.descriptions, 
function ( srcDescriptionIndex, srcDescription ) {
-                                                       var isRequired = 
srcDescription.isRequired,
-                                                               languageCode = 
srcDescription.getLanguage(),
-                                                               allowRemoval = 
!isRequired,
-                                                               descriptionText 
= srcDescription.getDescriptionText();
-                                                       
upload.details.addDescription( isRequired, languageCode, allowRemoval, 
descriptionText );
-                                               } );
-                                       }
-                               } );
+                               oouiCopy( 'descriptionsDetails' );
 
                        } else if ( metadataType === 'date' ) {
 
-                               oouiCopy( 'dateDetails', {
-                                       get: 'getSerialized',
-                                       set: 'setSerialized'
-                               } );
+                               oouiCopy( 'dateDetails' );
 
                        } else if ( metadataType === 'categories' ) {
 
-                               oouiCopy( 'categoriesDetails', {
-                                       get: 'getSerialized',
-                                       set: 'setSerialized'
-                               } );
+                               oouiCopy( 'categoriesDetails' );
 
                        } else if ( metadataType === 'location' ) {
 
@@ -753,6 +733,7 @@
                 */
                getErrors: function () {
                        return $.when(
+                               this.descriptionsDetails.getErrors(),
                                this.dateDetails.getErrors(),
                                this.categoriesDetails.getErrors()
                                // More fields will go here as we convert 
things to the new system...
@@ -766,6 +747,7 @@
                 */
                getWarnings: function () {
                        return $.when(
+                               this.descriptionsDetails.getWarnings(),
                                this.dateDetails.getWarnings(),
                                this.categoriesDetails.getWarnings()
                                // More fields will go here as we convert 
things to the new system...
@@ -777,9 +759,17 @@
                 * UI.
                 */
                checkValidity: function () {
+                       this.descriptionsDetailsField.checkValidity();
                        this.dateDetailsField.checkValidity();
                        this.categoriesDetailsField.checkValidity();
                        // More fields will go here as we convert things to the 
new system...
+               },
+
+               /**
+                * Get a thumbnail caption for this upload (basically, the 
first description).
+                */
+               getThumbnailCaption: function () {
+                       return 
this.descriptionsDetails.getSerialized().descriptions[ 0 ].description.trim();
                },
 
                /**
@@ -962,79 +952,6 @@
                },
 
                /**
-                * Do anything related to a change in the number of descriptions
-                */
-               recountDescriptions: function () {
-                       // if there is some maximum number of descriptions, 
deal with that here
-                       $( this.descriptionAdder ).text( mw.message( 
'mwe-upwiz-desc-add-' + ( this.descriptions.length === 0 ? '0' : 'n' ) ).text() 
);
-               },
-
-               /**
-                * Add a new description
-                */
-               addDescription: function ( required, languageCode, allowRemove, 
initialValue ) {
-                       var description,
-                               details = this;
-
-                       if ( required === undefined ) {
-                               required = false;
-                       }
-                       if ( allowRemove === undefined ) {
-                               allowRemove = true;
-                       }
-
-                       description = new mw.UploadWizardDescription( 
languageCode, required, initialValue );
-
-                       if ( !required && allowRemove ) {
-                               $( description.div  ).prepend(
-                                       new OO.ui.ButtonWidget( {
-                                               label: '',
-                                               title: mw.message( 
'mwe-upwiz-remove-description' ).escaped(),
-                                               flags: 'destructive',
-                                               classes: 
'mwe-upwiz-remove-ctrl',
-                                               icon: 'remove',
-                                               framed: false
-                                       } ).on( 'click', function () {
-                                               details.removeDescription( 
description );
-                                       } ).$element
-                               );
-                       }
-
-                       $( this.descriptionsDiv ).append( description.div  );
-
-                       // must defer adding rules until it's in a form
-                       // sigh, this would be simpler if we refactored to be 
more jquery style, passing DOM element downward
-                       description.addValidationRules( required );
-
-                       this.descriptions.push( description );
-                       this.recountDescriptions();
-               },
-
-               /**
-                * Remove a description
-                *
-                * @param {jQuery} description Description HTML from which to 
remove the `div`
-                */
-               removeDescription: function ( description ) {
-                       $( description.div ).remove();
-
-                       this.descriptions = $.grep(
-                               this.descriptions,
-                               function ( d ) {
-                                       return d !== description;
-                               }
-                       );
-
-                       this.recountDescriptions();
-               },
-
-               removeAllDescriptions: function () {
-                       $( this.descriptionsDiv ).children().remove();
-                       this.descriptions = [];
-                       this.recountDescriptions();
-               },
-
-               /**
                 * Display an error with details
                 * XXX this is a lot like upload ui's error -- should merge
                 */
@@ -1185,25 +1102,31 @@
                 * or from the metadata.
                 */
                prefillDescription: function () {
-                       var m, desc, descText;
+                       var m, descText;
 
                        if (
-                               this.descriptions[ 0 ].getDescriptionText() === 
'' &&
+                               this.descriptionsDetails.getWikiText() === '' &&
                                this.upload.file !== undefined
                        ) {
                                m = this.upload.imageinfo.metadata;
-                               desc = this.descriptions[ 0 ];
                                descText = this.upload.file.description ||
                                        ( m && m.imagedescription && 
m.imagedescription[ 0 ] && m.imagedescription[ 0 ].value );
 
                                if ( descText ) {
-                                       desc.setText( descText );
+                                       // strip out any HTML tags
+                                       descText = descText.replace( 
/<[^>]+>/g, '' );
+                                       // & and " are escaped by Flickr, so we 
need to unescape
+                                       descText = descText.replace( /&amp;/g, 
'&' ).replace( /&quot;/g, '"' );
 
-                                       // In future, when using a AJAX service 
for language detection
-                                       // use `desc.lockLanguageMenu();` and 
`desc.unlockLanguageMenu();`
-                                       // to prevent interaction by the user.
-                                       // For now, stick to the content 
language.
-                                       desc.setLanguage( mw.config.get( 
'wgContentLanguage' ) );
+                                       this.descriptionsDetails.setSerialized( 
{
+                                               descriptions: [
+                                                       {
+                                                               // The language 
is probably wrong in many cases...
+                                                               language: 
mw.config.get( 'wgContentLanguage' ),
+                                                               description: 
descText.trim()
+                                                       }
+                                               ]
+                                       } );
                                }
                        }
                },
@@ -1336,16 +1259,7 @@
                                        'other versions': ''
                                };
 
-                               // sanity check the descriptions -- do not have 
two in the same lang
-                               // all should be a known lang
-                               if ( this.descriptions.length === 0 ) {
-                                       window.alert( 'something has gone 
horribly wrong, unimplemented error check for zero descriptions' );
-                                       // XXX ruh roh
-                                       // we should not even allow them to 
press the button ( ? ) but then what about the queue...
-                               }
-                               $.each( this.descriptions, function ( i, desc ) 
{
-                                       information.description += 
desc.getWikiText();
-                               } );
+                               information.description = 
this.descriptionsDetails.getWikiText();
 
                                $.each( this.fields, function ( i, $field ) {
                                        if ( !mw.isEmpty( $field.val() ) ) {
diff --git a/resources/ui/uw.ui.Thanks.js b/resources/ui/uw.ui.Thanks.js
index bad8be3..96da4f1 100644
--- a/resources/ui/uw.ui.Thanks.js
+++ b/resources/ui/uw.ui.Thanks.js
@@ -104,7 +104,7 @@
                thumbWikiText = '[[' + [
                                upload.title.toText(),
                                'thumb',
-                               upload.details.descriptions[ 0 
].getDescriptionText()
+                               upload.details.getThumbnailCaption()
                        ].join( '|' ) + ']]';
 
                $thanksDiv = $( '<div>' )
diff --git a/resources/uploadWizard.css b/resources/uploadWizard.css
index 2864e1d..564fd6f 100644
--- a/resources/uploadWizard.css
+++ b/resources/uploadWizard.css
@@ -493,32 +493,10 @@
        padding-top:  0.3em;
 }
 
-.mwe-upwiz-desc-lang-select {
-       width: 11.5em;
-       font-family: sans-serif;
-       font-size: small;
-}
-
-.mwe-upwiz-desc-lang-text {
-       width: 31.5em;
-       margin-left: 0.5em;
-       font-family: sans-serif; /* XXX is this right? */
-       font-size: small;
-}
-
-.mwe-upwiz-details-descriptions-add {
-       margin-left: 0;
-}
-
-.mwe-upwiz-details-descriptions-add {
-       margin-bottom: 1em;
-}
-
 .mwe-grow-textarea, .mwe-long-textarea {
        font-family: sans-serif;
        font-size: small;
 }
-
 
 .mwe-title {
        font-family: sans-serif;
diff --git a/resources/uw/model/uw.model.Description.js 
b/resources/uw/model/uw.model.Description.js
deleted file mode 100644
index 9dd8823..0000000
--- a/resources/uw/model/uw.model.Description.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * This file is part of the MediaWiki extension UploadWizard.
- *
- * UploadWizard is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * UploadWizard is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with UploadWizard.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-( function ( mw, uw ) {
-       /**
-        * @class mw.uw.model.Description
-        * @extends mw.uw.model.Model
-        * @constructor
-        * @param {string} [language]
-        * @param {string} [text='']
-        * @param {Object} [languageTemplateFixups]
-        */
-       uw.model.Description = function UWModelDescription(
-               language,
-               text,
-               languageTemplateFixups
-       ) {
-               if ( text === undefined ) {
-                       text = '';
-               }
-
-               /**
-                * @property {string} language The language this description is 
in.
-                */
-               this.language = language;
-
-               /**
-                * @property {string} text
-                */
-               this.text = text;
-
-               /**
-                * @property {Object} languageTemplateFixups
-                */
-               this.languageTemplateFixups = languageTemplateFixups;
-       };
-
-       /**
-        * Gets the wikitext value of the description.
-        *
-        * @return {string}
-        */
-       uw.model.Description.prototype.getValue = function () {
-               // Assume that form validation has already told the user to
-               // enter a description if this field is required. Else this
-               // means "remove this description".
-               if ( this.text.length === 0 ) {
-                       return '';
-               }
-
-               return '{{' + this.language + '|1=' + this.text + '}}';
-       };
-
-       /**
-        * Sets the language.
-        *
-        * @param {string} language
-        */
-       uw.model.Description.prototype.setLanguage = function ( language ) {
-               var fix;
-
-               if ( this.languageTemplateFixups ) {
-                       fix = this.languageTemplateFixups;
-               }
-
-               if ( fix && fix[ language ] ) {
-                       language = fix[ language ];
-               }
-
-               this.language = language;
-       };
-
-       /**
-        * Sets the text.
-        *
-        * @param {string} text
-        */
-       uw.model.Description.prototype.setText = function ( text ) {
-               this.text = text;
-       };
-
-}( mediaWiki, mediaWiki.uploadWizard ) );
diff --git a/resources/uw/model/uw.model.base.js 
b/resources/uw/model/uw.model.base.js
deleted file mode 100644
index ba0386d..0000000
--- a/resources/uw/model/uw.model.base.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * This file is part of the MediaWiki extension UploadWizard.
- *
- * UploadWizard is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * UploadWizard is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with UploadWizard.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-// create UploadWizard model namespace
-mediaWiki.uploadWizard.model = {};
diff --git a/tests/qunit/mw.uw.model.Description.test.js 
b/tests/qunit/mw.uw.model.Description.test.js
deleted file mode 100644
index 7a32861..0000000
--- a/tests/qunit/mw.uw.model.Description.test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * This file is part of the MediaWiki extension UploadWizard.
- *
- * UploadWizard is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * UploadWizard is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with UploadWizard.  If not, see <http://www.gnu.org/licenses/>.
- */
-( function ( uw ) {
-       QUnit.module( 'uw.model.Description', QUnit.newMwEnvironment( {} ) );
-
-       QUnit.test( 'getValue', 3, function ( assert ) {
-               var desc = new uw.model.Description();
-
-               assert.strictEqual( desc.getValue(), '', 'Empty value returns 
empty string.' );
-
-               desc.setText( 'Blah' );
-               assert.strictEqual( desc.getValue(), '{{undefined|1=Blah}}', 
'Setting value returns template call to language template.' );
-
-               desc.setLanguage( 'en' );
-               assert.strictEqual( desc.getValue(), '{{en|1=Blah}}', 'Setting 
language returns template call to that language template.' );
-       } );
-}( mediaWiki.uploadWizard ) );

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

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