r70924 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r70923‎ | r70924 | r70925 >
Date:00:31, 12 August 2010
Author:neilk
Status:deferred (Comments)
Tags:
Comment:
new resource loading strategies (without JS2)
Modified paths:
  • /trunk/extensions/UploadWizard/README (modified) (history)
  • /trunk/extensions/UploadWizard/SpecialUploadWizard.php (modified) (history)
  • /trunk/extensions/UploadWizard/UploadWizard.i18n.php (modified) (history)
  • /trunk/extensions/UploadWizard/UploadWizard.php (modified) (history)
  • /trunk/extensions/UploadWizard/UploadWizardPage.js (modified) (history)
  • /trunk/extensions/UploadWizard/js (deleted) (history)
  • /trunk/extensions/UploadWizard/loader.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources (added) (history)
  • /trunk/extensions/UploadWizard/resources/arrow-head.png (added) (history)
  • /trunk/extensions/UploadWizard/resources/arrow-tail.png (added) (history)
  • /trunk/extensions/UploadWizard/resources/calendar.gif (added) (history)
  • /trunk/extensions/UploadWizard/resources/checkmark.gif (added) (history)
  • /trunk/extensions/UploadWizard/resources/inactive-arrow-divider.png (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery.arrowSteps.css (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery.autoSuggest.css (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery.mwCoolCats.css (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery.tipsy.css (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery.tipsy.error.gif (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery.tipsy.gif (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery.tipsy.help.gif (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.arrowSteps.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.autoSuggest.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.morphCrossfade.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.mwCoolCats.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.tipsy.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.validate.additional-methods.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/jquery.validate.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/plugins/jquery.arrowSteps.js (deleted) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/plugins/jquery.autoSuggest.js (deleted) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/plugins/jquery.morphCrossfade.js (deleted) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/plugins/jquery.mwCoolCats.js (deleted) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/plugins/jquery.tipsy.js (deleted) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/plugins/jquery.validate.additional-methods.js (deleted) (history)
  • /trunk/extensions/UploadWizard/resources/jquery/plugins/jquery.validate.js (deleted) (history)
  • /trunk/extensions/UploadWizard/resources/mw.ApiUploadHandler.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.DestinationChecker.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.Log.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/mw.UploadWizard.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.Utilities.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/mw.UtilitiesTime.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/mw.units.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/spinner-orange.gif (added) (history)
  • /trunk/extensions/UploadWizard/resources/toggle-open.png (added) (history)
  • /trunk/extensions/UploadWizard/resources/toggle.png (added) (history)
  • /trunk/extensions/UploadWizard/resources/uploadWizard.css (added) (history)
  • /trunk/extensions/UploadWizard/styles/arrow-head.png (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/arrow-tail.png (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/calendar.gif (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/checkmark.gif (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/inactive-arrow-divider.png (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/jquery.arrowSteps.css (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/jquery.autoSuggest.css (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/jquery.mwCoolCats.css (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/jquery.tipsy.css (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/jquery.tipsy.error.gif (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/jquery.tipsy.gif (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/jquery.tipsy.help.gif (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/spinner-orange.gif (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/toggle-open.png (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/toggle.png (deleted) (history)
  • /trunk/extensions/UploadWizard/styles/uploadWizard.css (deleted) (history)

Diff [purge]

Index: trunk/extensions/UploadWizard/UploadWizard.i18n.php
@@ -12,6 +12,15 @@
1313 * @author Neil Kandalgaonkar
1414 */
1515 $messages['en'] = array(
 16+ 'linktest' => '[$1 this is a link]',
 17+ 'pluraltest' => 'This is {{PLURAL:$1|one|many}} things',
 18+ 'magictest' => 'From {{SITENAME}}',
 19+ 'namespacedtest' => '{{ns:project}}:Copyrights',
 20+ 'extremelycomplextest' => 'There is currently no text in this page.
 21+You can [[Special:Search/{{PAGENAME}}|search for this page title]] in other pages,
 22+<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],
 23+or [{{fullurl:{{FULLPAGENAME}}|action=edit}} edit this page]</span>.',
 24+ 'internallinktest' => '[[Special:SpecialPages|{{int:specialpages}}]]',
1625 'uploadwizard' => 'Upload wizard',
1726 'uploadwizard-desc' => 'Upload wizard, developed for the Multimedia Usability grant',
1827 'mwe-loading-upwiz' => 'Loading upload wizard',
@@ -3493,4 +3502,3 @@
34943503 'mwe-upwiz-categories-add' => '新增',
34953504 'mwe-upwiz-category-remove' => '刪除這個類別',
34963505 );
3497 -
Index: trunk/extensions/UploadWizard/resources/mw.UtilitiesTime.js
@@ -0,0 +1,90 @@
 2+/**
 3+ * Given a float number of seconds, returns npt format response. ( ignore
 4+ * days for now )
 5+ *
 6+ * @param {Float}
 7+ * sec Seconds
 8+ * @param {Boolean}
 9+ * show_ms If milliseconds should be displayed.
 10+ * @return {Float} String npt format
 11+ */
 12+mw.seconds2npt = function( sec, show_ms ) {
 13+ if ( isNaN( sec ) ) {
 14+ mw.log("Warning: trying to get npt time on NaN:" + sec);
 15+ return '0:00:00';
 16+ }
 17+
 18+ var tm = mw.seconds2Measurements( sec )
 19+
 20+ // Round the number of seconds to the required number of significant
 21+ // digits
 22+ if ( show_ms ) {
 23+ tm.seconds = Math.round( tm.seconds * 1000 ) / 1000;
 24+ } else {
 25+ tm.seconds = Math.round( tm.seconds );
 26+ }
 27+ if ( tm.seconds < 10 ){
 28+ tm.seconds = '0' + tm.seconds;
 29+ }
 30+ if( tm.hours == 0 ){
 31+ hoursStr = ''
 32+ } else {
 33+ if ( tm.minutes < 10 )
 34+ tm.minutes = '0' + tm.minutes;
 35+
 36+ hoursStr = tm.hours + ":";
 37+ }
 38+ return hoursStr + tm.minutes + ":" + tm.seconds;
 39+}
 40+
 41+/**
 42+ * Given seconds return array with 'days', 'hours', 'min', 'seconds'
 43+ *
 44+ * @param {float}
 45+ * sec Seconds to be converted into time measurements
 46+ */
 47+mw.seconds2Measurements = function ( sec ){
 48+ var tm = {};
 49+ tm.days = Math.floor( sec / ( 3600 * 24 ) )
 50+ tm.hours = Math.floor( sec / 3600 );
 51+ tm.minutes = Math.floor( ( sec / 60 ) % 60 );
 52+ tm.seconds = sec % 60;
 53+ return tm;
 54+}
 55+
 56+/**
 57+ * Take hh:mm:ss,ms or hh:mm:ss.ms input, return the number of seconds
 58+ *
 59+ * @param {String}
 60+ * npt_str NPT time string
 61+ * @return {Float} Number of seconds
 62+ */
 63+mw.npt2seconds = function ( npt_str ) {
 64+ if ( !npt_str ) {
 65+ // mw.log('npt2seconds:not valid ntp:'+ntp);
 66+ return false;
 67+ }
 68+ // Strip {npt:}01:02:20 or 32{s} from time if present
 69+ npt_str = npt_str.replace( /npt:|s/g, '' );
 70+
 71+ var hour = 0;
 72+ var min = 0;
 73+ var sec = 0;
 74+
 75+ times = npt_str.split( ':' );
 76+ if ( times.length == 3 ) {
 77+ sec = times[2];
 78+ min = times[1];
 79+ hour = times[0];
 80+ } else if ( times.length == 2 ) {
 81+ sec = times[1];
 82+ min = times[0];
 83+ } else {
 84+ sec = times[0];
 85+ }
 86+ // Sometimes a comma is used instead of period for ms
 87+ sec = sec.replace( /,\s?/, '.' );
 88+ // Return seconds float
 89+ return parseInt( hour * 3600 ) + parseInt( min * 60 ) + parseFloat( sec );
 90+}
 91+
Property changes on: trunk/extensions/UploadWizard/resources/mw.UtilitiesTime.js
___________________________________________________________________
Added: svn:eol-style
192 + native
Index: trunk/extensions/UploadWizard/resources/mw.MockUploadHandler.js
@@ -0,0 +1,37 @@
 2+// TODO copy interface from ApiUploadHandler -- it changed
 3+
 4+// Currently this doesn't at all follow the interface that Mdale made in UploadHandler
 5+// will have to figure this out.
 6+
 7+// this should be loaded with test suites when appropriate. separate file.
 8+mw.MockUploadHandler = function(upload) {
 9+ this.upload = upload;
 10+ this.nextState = null;
 11+ this.progress = 0.0;
 12+
 13+};
 14+
 15+mw.MockUploadHandler.prototype = {
 16+
 17+ start: function () {
 18+ var _this = this;
 19+ _this.beginTime = (new Date()).getTime();
 20+ _this.nextState = _this.cont;
 21+ _this.nextState();
 22+ },
 23+
 24+ cont: function () {
 25+ var _this = this;
 26+ var delta = 0.0001; // static?
 27+ _this.progress += ( Math.random() * 0.1 );
 28+ _this.upload.setTransportProgress(_this.progress);
 29+ if (1.0 - _this.progress < delta) {
 30+ _this.upload.setTransported();
 31+ } else {
 32+ setTimeout( function() { _this.nextState(); }, 200 );
 33+ }
 34+ }
 35+
 36+};
 37+
 38+
Property changes on: trunk/extensions/UploadWizard/resources/mw.MockUploadHandler.js
___________________________________________________________________
Added: svn:eol-style
139 + native
Index: trunk/extensions/UploadWizard/resources/jquery.tipsy.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/UploadWizard/resources/jquery.tipsy.gif
___________________________________________________________________
Added: svn:mime-type
240 + application/octet-stream
Index: trunk/extensions/UploadWizard/resources/spinner-orange.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/UploadWizard/resources/spinner-orange.gif
___________________________________________________________________
Added: svn:mime-type
341 + application/octet-stream
Index: trunk/extensions/UploadWizard/resources/uploadWizard.css
@@ -0,0 +1,558 @@
 2+
 3+
 4+/* min-width is about 550px, maybe about 52em
 5+/* max-width is about 875px about 87em?
 6+#upload-wizard {
 7+
 8+}
 9+*/
 10+
 11+
 12+form.mwe-upwiz-form {
 13+ display: inline;
 14+}
 15+
 16+#upload-wizard {
 17+ margin-top: 18px;
 18+ min-width: 32em;
 19+ max-width: 64em;
 20+}
 21+
 22+/*
 23+.upload-section {
 24+ padding: 1em;
 25+ margin-bottom: 0.5em;
 26+ margin-top: 0.5em;
 27+ border: 1px solid #e0e0e0;
 28+}
 29+*/
 30+
 31+
 32+.mwe-upwiz-clearing {
 33+ clear: left;
 34+ width: 100%;
 35+}
 36+
 37+
 38+#mwe-upwiz-content {
 39+ padding: 1em;
 40+}
 41+
 42+.mwe-upwiz-add-files-0, #mwe-upwiz-files {
 43+ margin-top: 3em;
 44+ margin-bottom: 3em;
 45+}
 46+
 47+.mwe-upwiz-add-files-0 {
 48+ text-align: center;
 49+ font-size: large;
 50+}
 51+
 52+#mwe-upwiz-add-file {
 53+}
 54+
 55+/* NOT a pseudoclass */
 56+#mwe-upwiz-add-file.hover {
 57+ text-decoration: underline;
 58+}
 59+
 60+/* perhaps a general class for links that are actually "buttons" */
 61+#mwe-upwiz-add-file, .mwe-upwiz-remove-ctrl, .mwe-upwiz-more-options {
 62+ outline: none;
 63+ cursor: pointer;
 64+}
 65+
 66+/* CSS styling hack for file inputs - http://www.quirksmode.org/dom/inputfile.html */
 67+.mwe-upwiz-file-ctrl-container {
 68+ position: absolute;
 69+ overflow: hidden;
 70+}
 71+
 72+.mwe-upwiz-file-input, .mwe-upwiz-visible-file {
 73+ cursor: pointer;
 74+}
 75+
 76+/* file inputs are freakishly large to overflow the containing div -- we get a div
 77+ that can act as a more styleable single-click file input */
 78+.mwe-upwiz-file-input, .disabler {
 79+ font-size: 100px;
 80+ -moz-opacity: 0.3;
 81+ filter:alpha(opacity: 0);
 82+ opacity: 0;
 83+ z-index: 2;
 84+}
 85+
 86+.mwe-upwiz-file.filled {
 87+ position: relative;
 88+}
 89+
 90+.mwe-upwiz-file.odd .mwe-upwiz-visible-file {
 91+ background: #f5f5f5;
 92+}
 93+
 94+
 95+
 96+.mwe-upwiz-remove-ctrl {
 97+}
 98+
 99+/* XXX this is highly specific to our installation
 100+.mwe-upwiz-remove-ctrl.hover {
 101+.ui-icon { background-image: url(/w/extensions/UsabilityInitiative/css/vector/images/ui-icons_cd0a0a_256x240.png) }
 102+}
 103+*/
 104+
 105+.mwe-upwiz-visible-file .mwe-upwiz-remove-ctrl {
 106+ float: right;
 107+ visibility: hidden;
 108+ margin: 0.25em;
 109+ padding: 0.25em;
 110+}
 111+
 112+.mwe-upwiz-file-indicator, .mwe-upwiz-count {
 113+ float: right;
 114+ margin-left: 0.5em;
 115+ padding: 0.5em 0.5em 0.5em 20px; /* 20px for the icon */
 116+}
 117+
 118+.mwe-upwiz-visible-file .mwe-upwiz-file-indicator {
 119+ visibility: hidden;
 120+}
 121+
 122+.mwe-upwiz-visible-file {
 123+ display: none;
 124+}
 125+
 126+.mwe-upwiz-file.filled .mwe-upwiz-visible-file {
 127+ display: block;
 128+}
 129+
 130+.mwe-upwiz-visible-file-filename {
 131+ padding: 0.5em;
 132+ margin-right: 40px;
 133+ overflow: hidden;
 134+}
 135+
 136+.mwe-upwiz-progress-bar-etr-container {
 137+ /* needed ? */
 138+}
 139+
 140+
 141+.mwe-upwiz-add-files-n {
 142+ float: left;
 143+ margin-top: 5px;
 144+ margin-left: 4px;
 145+}
 146+
 147+#mwe-upwiz-add-file-container.mwe-upwiz-add-files-n, .mwe-upwiz-progress-bar-etr {
 148+ width: 300px;
 149+ padding-left: 5px;
 150+}
 151+
 152+
 153+#mwe-upwiz-add-file-container.mwe-upwiz-add-files-n {
 154+ float: left;
 155+}
 156+
 157+.mwe-upwiz-visible-file {
 158+ width: 100%; /* of containing mwe-upwiz-file */
 159+ white-space: nowrap;
 160+ overflow: hidden;
 161+}
 162+
 163+.mwe-upwiz-file.hover .mwe-upwiz-visible-file {
 164+ background: #e0f0ff !important;
 165+}
 166+
 167+.mwe-upwiz-file.hover .mwe-upwiz-remove-ctrl {
 168+ visibility: visible;
 169+}
 170+
 171+/* XXX we probably have a standard for this */
 172+.helper {
 173+ color: #cccccc; /* or whatever we do for greyed out text */
 174+ font-variant: italic, oblique;
 175+ text-align: center;
 176+}
 177+
 178+
 179+#mwe-upwiz-files {
 180+ margin-right: 8em;
 181+ margin-left: 8em;
 182+}
 183+
 184+.mwe-upwiz-file {
 185+}
 186+
 187+#mwe-upwiz-upload-ctrls {
 188+ margin-top: 1em;
 189+}
 190+
 191+#mwe-upwiz-add-file-container {
 192+ /* empty; this changes a lot */
 193+}
 194+
 195+.mwe-upwiz-details-descriptions .mwe-upwiz-remove-ctrl {
 196+ vertical-align: top;
 197+ display: inline-block;
 198+}
 199+
 200+a[disabled=true] {
 201+ color: #999999;
 202+ text-decoration: none;
 203+ cursor: default;
 204+}
 205+
 206+a[disabled=true]:hover {
 207+ text-decoration: none;
 208+}
 209+
 210+.mwe-upwiz-status-progress {
 211+ background: url(spinner-orange.gif) no-repeat left center;
 212+ font-weight: bold;
 213+ color: #ff9900;
 214+}
 215+
 216+.mwe-upwiz-status-completed {
 217+ background: url(checkmark.gif) no-repeat left center;
 218+ font-weight: bold;
 219+ color: #009900;
 220+}
 221+
 222+.mwe-upwiz-progress {
 223+ margin-top: 15px;
 224+}
 225+
 226+.mwe-upwiz-progress-bar-etr {
 227+ float: left;
 228+}
 229+
 230+.mwe-upwiz-etr {
 231+ text-align: center;
 232+}
 233+
 234+
 235+.mwe-upwiz-upload-warning {
 236+ background: #ffffe0;
 237+}
 238+
 239+.mwe-upwiz-details-error {
 240+ display: none;
 241+ background: #ffffe0;
 242+}
 243+
 244+.mwe-upwiz-thumbnail, .mwe-upwiz-thumbnail-small {
 245+ border: 1px solid #cccccc;
 246+ text-align: center;
 247+ background: #ffffff;
 248+}
 249+
 250+.mwe-upwiz-thumbnail {
 251+ padding: 0.5em;
 252+ width: 120px;
 253+}
 254+
 255+.mwe-upwiz-thumbnail-side {
 256+ float: left;
 257+ margin-bottom: 1em;
 258+ margin-right: 1em;
 259+}
 260+
 261+.mwe-upwiz-thumbnail-small {
 262+ padding: 0.25em;
 263+ width: 60px;
 264+}
 265+
 266+#mwe-upwiz-deeds-thumbnails {
 267+ text-align: center;
 268+ margin: 1em 0;
 269+ background: #f0f0f0;
 270+}
 271+
 272+#mwe-upwiz-deeds-thumbnails .mwe-upwiz-thumbnail-small {
 273+ display: inline-block;
 274+ margin: 1em;
 275+ vertical-align: middle;
 276+}
 277+
 278+/* I don't like that this has to have width, to ensure that all the floats work out correctly.*/
 279+.mwe-upwiz-data {
 280+ float: left;
 281+ width: 46em;
 282+}
 283+
 284+
 285+/* XXX add spinner gif instead, maybe */
 286+.busy {
 287+ /* background: yellow; */
 288+}
 289+
 290+.mwe-upwiz-stepdiv {
 291+ height: 0px;
 292+ overflow: hidden;
 293+}
 294+
 295+
 296+
 297+.shim {
 298+ float:right;
 299+ width: 1px;
 300+}
 301+
 302+.clearShim {
 303+ clear: both;
 304+ height: 1px;
 305+ overflow: hidden;
 306+}
 307+
 308+.mwe-checkbox-hang-indent {
 309+ float: left;
 310+ width: 24px;
 311+ margin-top: 6px;
 312+}
 313+
 314+.mwe-checkbox-hang-indent-text {
 315+ margin-left: 24px;
 316+}
 317+
 318+.mwe-small-print {
 319+ font-size: x-small;
 320+}
 321+
 322+.mwe-upwiz-deed {
 323+ margin-left: 24px;
 324+}
 325+
 326+.mwe-upwiz-deed.selected .mwe-upwiz-deed-name {
 327+ font-weight: bold;
 328+}
 329+
 330+.mwe-more-options, .mwe-upwiz-macro-deeds-return, .mwe-upwiz-deed-header-link {
 331+ cursor: pointer;
 332+}
 333+
 334+.mwe-more-options {
 335+ padding-bottom: 4px;
 336+}
 337+
 338+.mwe-upwiz-deed-license {
 339+ margin-left: 24px;
 340+}
 341+
 342+#mwe-upwiz-macro-deeds {
 343+ margin-top: 12px;
 344+ margin-bottom: 24px;
 345+}
 346+
 347+#mwe-upwiz-macro-files {
 348+ margin-top: 12px;
 349+}
 350+
 351+.mwe-upwiz-info-file {
 352+ margin-bottom: 1em;
 353+}
 354+
 355+.mwe-upwiz-details-input {
 356+ width: 33em;
 357+ float: left;
 358+}
 359+
 360+.mwe-upwiz-details-fieldname {
 361+ width: 10em;
 362+ padding-top: 0.5em;
 363+ float: left;
 364+}
 365+
 366+.mwe-upwiz-details-input-error {
 367+ padding-left: 10em;
 368+ /* XXX the following rules pop it open at a fixed width so layout doesn't change :( */
 369+ /* min-height: 3em;
 370+ position: relative; */
 371+}
 372+
 373+/* XXX the following rule keeps errors on the details page at the bottom of a fixed-height space, see above */
 374+/*
 375+.mwe-upwiz-details-input-error label.mwe-validator-error,
 376+.mwe-upwiz-details-input-error label.mwe-error {
 377+ position: absolute;
 378+ bottom: 0px;
 379+}
 380+*/
 381+
 382+
 383+.mwe-upwiz-desc-lang-select {
 384+ width: 11em;
 385+ font-family: sans-serif;
 386+ font-size: small;
 387+}
 388+
 389+.mwe-upwiz-desc-lang-text {
 390+ width: 20em;
 391+ overflow: hidden;
 392+ font-family: sans-serif; /* XXX is this right? */
 393+ font-size: small;
 394+}
 395+
 396+.mwe-upwiz-details-descriptions-add {
 397+ margin-left: 10em; /* width of mwe-upwiz-details-fieldname */
 398+}
 399+
 400+.mwe-upwiz-details-descriptions-add {
 401+ margin-bottom: 1em;
 402+}
 403+
 404+.mwe-grow-textarea, .mwe-long-textarea {
 405+ overflow: hidden;
 406+ font-family: sans-serif; /* XXX is this right? */
 407+ font-size: small;
 408+}
 409+
 410+.mwe-long-textarea {
 411+ width: 31em;
 412+}
 413+
 414+fieldset .mwe-long-textarea {
 415+ width: 17em;
 416+}
 417+
 418+
 419+.mwe-upwiz-details-fieldname-input {
 420+ margin-bottom: 1em;
 421+}
 422+
 423+.mwe-upwiz-details-filename. {
 424+ overflow: hidden;
 425+ width: 350px;
 426+}
 427+
 428+.mwe-upwiz-other-textarea {
 429+ /* width: 40.3em; */
 430+}
 431+
 432+fieldset.mwe-fieldset {
 433+ width: 450px;
 434+ border: 1px solid #cccccc;
 435+ padding-left: 12px;
 436+ padding-right: 12px;
 437+}
 438+
 439+legend.mwe-legend {
 440+ padding: 0.5em 0.5em 0.5em 0.33em;
 441+ color: #666666;
 442+}
 443+
 444+.masked-hidden {
 445+ visibility: hidden !important;
 446+}
 447+
 448+.mwe-upwiz-thirdparty-fields {
 449+ margin-bottom: .5em;
 450+}
 451+
 452+.mwe-upwiz-thirdparty-fields label {
 453+ width: 9em;
 454+ display: inline-block;
 455+ padding-bottom: .5em;
 456+}
 457+
 458+.mwe-upwiz-thirdparty-fields textarea {
 459+ margin: 0
 460+}
 461+
 462+.mwe-upwiz-thirdparty-license {
 463+ margin-top: 8px;
 464+}
 465+
 466+.mwe-upwiz-deed-form-internal {
 467+ padding: 0.5em 0 1.5em 3em;
 468+}
 469+
 470+
 471+.mwe-upwiz-copyright-info label {
 472+ display: inline-block;
 473+ padding-left: 15px;
 474+ text-indent: -15px;
 475+ margin-right: 15px;
 476+}
 477+
 478+/* the hanging indent checkboxes */
 479+.mwe-upwiz-copyright-info input.mwe-accept-deed {
 480+ width: 13px;
 481+ height: 13px;
 482+ padding: 0;
 483+ margin: 0px 3px 0px 0px;
 484+ vertical-align: bottom;
 485+ position: relative;
 486+ top: -1px;
 487+}
 488+
 489+
 490+.mwe-upwiz-custom-deed {
 491+ margin-top: 5px;
 492+}
 493+
 494+.mwe-upwiz-buttons {
 495+ margin-top: 1em;
 496+ padding-top: 10px;
 497+ border-top: 1px solid #e0e0e0;
 498+ text-align: right; /* works for now, only one 'next' button */
 499+}
 500+
 501+a.mwe-upwiz-tooltip-link {
 502+ cursor: pointer;
 503+}
 504+
 505+.mwe-error, .mwe-validator-error {
 506+ color: #ff0000;
 507+}
 508+
 509+.mwe-upwiz-deed-thirdparty .mwe-upwiz-deed-form-internal label.mwe-error,
 510+.mwe-upwiz-deed-thirdparty .mwe-upwiz-deed-form-internal label.mwe-validator-error {
 511+ margin-left: 9em;
 512+}
 513+
 514+/* XXX fix this later -- we get non-rounded corners somehow */
 515+input[type='text'].mwe-error, textarea.mwe-error,
 516+input[type='text'].mwe-validator-error, textarea.mwe-validator-error {
 517+ color: black;
 518+ border-color: #ff0000;
 519+}
 520+
 521+.mwe-upwiz-toggler {
 522+ margin-bottom: 0;
 523+ padding: 4px 0 3px 18px;
 524+ background: url('toggle.png') no-repeat left center;
 525+}
 526+
 527+.mwe-upwiz-toggler-open {
 528+ background: url('toggle-open.png') no-repeat left center;
 529+}
 530+
 531+.mwe-upwiz-toggled {
 532+ margin-top: 1em;
 533+}
 534+
 535+.mwe-upwiz-thanks {
 536+ margin-bottom: 2em;
 537+}
 538+
 539+/* may change in RTL */
 540+.mwe-upwiz-required-field {
 541+ /* font-weight: bold; */
 542+}
 543+
 544+.mwe-upwiz-required-marker {
 545+ color: #0099cc;
 546+}
 547+
 548+.mwe-readonly {
 549+ background-color: #ffffff;
 550+}
 551+
 552+.mwe-date-display {
 553+ width: 100%;
 554+ background: #ffffff url('calendar.gif') no-repeat right center;
 555+}
 556+
 557+.ui-datepicker-current-day a.ui-state-active {
 558+ background: #ffff99;
 559+}
Property changes on: trunk/extensions/UploadWizard/resources/uploadWizard.css
___________________________________________________________________
Added: svn:eol-style
1560 + native
Index: trunk/extensions/UploadWizard/resources/checkmark.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/UploadWizard/resources/checkmark.gif
___________________________________________________________________
Added: svn:mime-type
2561 + application/octet-stream
Index: trunk/extensions/UploadWizard/resources/mw.units.js
@@ -0,0 +1,35 @@
 2+/**
 3+ * Format a size in bytes for output, using an appropriate
 4+ * unit (B, KB, MB or GB) according to the magnitude in question
 5+ *
 6+ * @param size Size to format
 7+ * @return string Plain text (not HTML)
 8+ */
 9+mw.units.bytes = function ( size ) {
 10+ // For small sizes no decimal places are necessary
 11+ var round = 0;
 12+ var msg = '';
 13+ if ( size > 1024 ) {
 14+ size = size / 1024;
 15+ if ( size > 1024 ) {
 16+ size = size / 1024;
 17+ // For MB and bigger two decimal places are smarter
 18+ round = 2;
 19+ if ( size > 1024 ) {
 20+ size = size / 1024;
 21+ msg = 'mwe-size-gigabytes';
 22+ } else {
 23+ msg = 'mwe-size-megabytes';
 24+ }
 25+ } else {
 26+ msg = 'mwe-size-kilobytes';
 27+ }
 28+ } else {
 29+ msg = 'mwe-size-bytes';
 30+ }
 31+ // JavaScript does not let you choose the precision when rounding
 32+ var p = Math.pow( 10, round );
 33+ size = Math.round( size * p ) / p;
 34+ return gM( msg , size );
 35+};
 36+
Property changes on: trunk/extensions/UploadWizard/resources/mw.units.js
___________________________________________________________________
Added: svn:eol-style
137 + native
Index: trunk/extensions/UploadWizard/resources/inactive-arrow-divider.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/UploadWizard/resources/inactive-arrow-divider.png
___________________________________________________________________
Added: svn:mime-type
238 + image/png
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizard.js
@@ -0,0 +1,3439 @@
 2+/**
 3+ * Sort of an abstract class for deeds
 4+ */
 5+mw.UploadWizardDeed = function() {
 6+ var _this = this;
 7+ // prevent from instantiating directly?
 8+ return false;
 9+};
 10+
 11+mw.UploadWizardDeed.prototype = {
 12+ valid: function() {
 13+ return false;
 14+ },
 15+
 16+ setFormFields: function() { },
 17+
 18+ getSourceWikiText: function() {
 19+ return $j( this.sourceInput ).val();
 20+ },
 21+
 22+ getAuthorWikiText: function() {
 23+ return $j( this.authorInput ).val();
 24+ },
 25+
 26+ /**
 27+ * Get wikitext representing the licenses selected in the license object
 28+ * @return wikitext of all applicable license templates.
 29+ */
 30+ getLicenseWikiText: function() {
 31+ var _this = this;
 32+ var wikiText = '';
 33+ $j.each ( _this.licenseInput.getTemplates(), function( i, template ) {
 34+ wikiText += "{{" + template + "}}\n";
 35+ } );
 36+
 37+ return wikiText;
 38+ }
 39+
 40+};
 41+
 42+
 43+/**
 44+ * this is a progress bar for monitoring multiple objects, giving summary view
 45+ */
 46+mw.GroupProgressBar = function( selector, text, uploads, endState, progressProperty, weightProperty ) {
 47+ var _this = this;
 48+
 49+ // XXX need to figure out a way to put text inside bar
 50+ _this.$selector = $j( selector );
 51+ _this.$selector.html(
 52+ '<div class="mwe-upwiz-progress">'
 53+ + '<div class="mwe-upwiz-progress-bar-etr-container">'
 54+ + '<div class="mwe-upwiz-progress-bar-etr" style="display: none">'
 55+ + '<div class="mwe-upwiz-progress-bar"></div>'
 56+ + '<div class="mwe-upwiz-etr"></div>'
 57+ + '</div>'
 58+ + '</div>'
 59+ + '<div class="mwe-upwiz-count"></div>'
 60+ + '</div>'
 61+ );
 62+
 63+ _this.$selector.find( '.mwe-upwiz-progress-bar' ).progressbar( { value : 0 } );
 64+
 65+ _this.uploads = uploads;
 66+ _this.endState = endState;
 67+ _this.progressProperty = progressProperty;
 68+ _this.weightProperty = weightProperty;
 69+ _this.beginTime = undefined;
 70+
 71+};
 72+
 73+mw.GroupProgressBar.prototype = {
 74+
 75+ /**
 76+ * Show the progress bar with a slideout motion
 77+ */
 78+ showBar: function() {
 79+ this.$selector.find( '.mwe-upwiz-progress-bar-etr' ).fadeIn( 200 );
 80+ },
 81+
 82+ /**
 83+ * loop around the uploads, summing certain properties for a weighted total fraction
 84+ */
 85+ start: function() {
 86+ var _this = this;
 87+
 88+ var totalWeight = 0.0;
 89+ $j.each( _this.uploads, function( i, upload ) {
 90+ totalWeight += upload[_this.weightProperty];
 91+ } );
 92+
 93+ _this.setBeginTime();
 94+ var shown = false;
 95+
 96+ var displayer = function() {
 97+ var fraction = 0.0;
 98+ var endStateCount = 0;
 99+ var hasData = false;
 100+ $j.each( _this.uploads, function( i, upload ) {
 101+ if ( upload.state == _this.endState ) {
 102+ endStateCount++;
 103+ }
 104+ if (upload[_this.progressProperty] !== undefined) {
 105+ fraction += upload[_this.progressProperty] * ( upload[_this.weightProperty] / totalWeight );
 106+ if (upload[_this.progressProperty] > 0 ) {
 107+ hasData = true;
 108+ }
 109+ }
 110+ } );
 111+ mw.log( 'hasdata:' + hasData + ' endstatecount:' + endStateCount );
 112+ // sometimes, the first data we have just tells us that it's over. So only show the bar
 113+ // if we have good data AND the fraction is less than 1.
 114+ if ( hasData && fraction < 1.0 ) {
 115+ if ( ! shown ) {
 116+ _this.showBar();
 117+ shown = true;
 118+ }
 119+ _this.showProgress( fraction );
 120+ }
 121+ _this.showCount( endStateCount );
 122+
 123+ if ( endStateCount < _this.uploads.length ) {
 124+ setTimeout( displayer, 200 );
 125+ } else {
 126+ _this.showProgress( 1.0 );
 127+ // not necessary to hide bar since we're going to the next step.
 128+ /* setTimeout( function() { _this.hideBar(); }, 500 ); */
 129+ }
 130+ };
 131+ displayer();
 132+ },
 133+
 134+
 135+ /**
 136+ * Hide the progress bar with a slideup motion
 137+ */
 138+ hideBar: function() {
 139+ this.$selector.find( '.mwe-upwiz-progress-bar-etr' ).fadeOut( 200 );
 140+ },
 141+
 142+ /**
 143+ * sets the beginning time (useful for figuring out estimated time remaining)
 144+ * if time parameter omitted, will set beginning time to now
 145+ *
 146+ * @param time optional; the time this bar is presumed to have started (epoch milliseconds)
 147+ */
 148+ setBeginTime: function( time ) {
 149+ this.beginTime = time ? time : ( new Date() ).getTime();
 150+ },
 151+
 152+
 153+ /**
 154+ * Show overall progress for the entire UploadWizard
 155+ * The current design doesn't have individual progress bars, just one giant one.
 156+ * We did some tricky calculations in startUploads to try to weight each individual file's progress against
 157+ * the overall progress.
 158+ * @param fraction the amount of whatever it is that's done whatever it's done
 159+ */
 160+ showProgress: function( fraction ) {
 161+ var _this = this;
 162+
 163+ _this.$selector.find( '.mwe-upwiz-progress-bar' ).progressbar( 'value', parseInt( fraction * 100, 10 ) );
 164+
 165+ var remainingTime = _this.getRemainingTime( fraction );
 166+
 167+ if ( remainingTime !== null ) {
 168+ var t = mw.seconds2Measurements( parseInt( remainingTime / 1000, 10 ) );
 169+ var timeString;
 170+ if (t.hours === 0) {
 171+ if (t.minutes === 0) {
 172+ if (t.seconds === 0) {
 173+ timeString = gM( 'mwe-upwiz-finished' );
 174+ } else {
 175+ timeString = gM( 'mwe-upwiz-secs-remaining', t.seconds );
 176+ }
 177+ } else {
 178+ timeString = gM( 'mwe-upwiz-mins-secs-remaining', t.minutes, t.seconds );
 179+ }
 180+ } else {
 181+ timeString = gM( 'mwe-upwiz-hrs-mins-secs-remaining', t.hours, t.minutes, t.seconds );
 182+ }
 183+ _this.$selector.find( '.mwe-upwiz-etr' ).html( timeString );
 184+ }
 185+ },
 186+
 187+ /**
 188+ * Calculate remaining time for all uploads to complete.
 189+ *
 190+ * @param fraction fraction of progress to show
 191+ * @return estimated time remaining (in milliseconds)
 192+ */
 193+ getRemainingTime: function ( fraction ) {
 194+ var _this = this;
 195+ if ( _this.beginTime ) {
 196+ var elapsedTime = ( new Date() ).getTime() - _this.beginTime;
 197+ if ( fraction > 0.0 && elapsedTime > 0 ) { // or some other minimums for good data
 198+ var rate = fraction / elapsedTime;
 199+ return parseInt( ( 1.0 - fraction ) / rate, 10 );
 200+ }
 201+ }
 202+ return null;
 203+ },
 204+
 205+
 206+ /**
 207+ * Show the overall count as we upload
 208+ * @param count -- the number of items that have done whatever has been done e.g. in "uploaded 2 of 5", this is the 2
 209+ */
 210+ showCount: function( count ) {
 211+ var _this = this;
 212+ _this.$selector
 213+ .find( '.mwe-upwiz-count' )
 214+ .html( gM( 'mwe-upwiz-upload-count', [ count, _this.uploads.length ] ) );
 215+ }
 216+
 217+
 218+};
 219+
 220+
 221+
 222+//mw.setConfig('uploadHandlerClass', mw.MockUploadHandler); // ApiUploadHandler?
 223+
 224+// available licenses should be a configuration of the MediaWiki instance,
 225+// not hardcoded here.
 226+// but, MediaWiki has no real concept of a License as a first class object -- there are templates and then specially - parsed
 227+// texts to create menus -- hack on top of hacks -- a bit too much to deal with ATM
 228+/**
 229+ * Create a group of checkboxes for licenses. N.b. the licenses are named after the templates they invoke.
 230+ * @param div
 231+ * @param values (optional) array of license key names to activate by default
 232+ */
 233+mw.UploadWizardLicenseInput = function( selector, values ) {
 234+ var _this = this;
 235+
 236+ var widgetCount = mw.UploadWizardLicenseInput.prototype.count++;
 237+
 238+ _this.inputs = [];
 239+
 240+ // TODO incompatibility check of this license versus others
 241+
 242+ _this.$selector = $j( selector );
 243+ _this.$selector.append( $j( '<div class="mwe-error"></div>' ) );
 244+
 245+ $j.each( mw.UploadWizard.config[ 'licenses' ], function( i, licenseConfig ) {
 246+ var template = licenseConfig.template;
 247+ var messageKey = licenseConfig.messageKey;
 248+
 249+ var name = 'license_' + template;
 250+ var id = 'licenseInput' + widgetCount + '_' + name;
 251+ var $input = $j( '<input />' )
 252+ .attr( { id: id, name: name, type: 'checkbox', value: template } )
 253+ // we use the selector because events can't be unbound unless they're in the DOM.
 254+ .click( function() { _this.$selector.trigger( 'changeLicenses' ); } );
 255+ _this.inputs.push( $input );
 256+ _this.$selector.append(
 257+ $input,
 258+ $j( '<label />' ).attr( { 'for': id } ).html( gM( messageKey ) ),
 259+ $j( '<br/>' )
 260+ );
 261+ } );
 262+
 263+ if ( values ) {
 264+ _this.setValues( values );
 265+ }
 266+
 267+ return _this;
 268+};
 269+
 270+mw.UploadWizardLicenseInput.prototype = {
 271+ count: 0,
 272+
 273+ /**
 274+ * Sets the value(s) of a license input.
 275+ * @param object of license-key to boolean values, e.g. { cc_by_sa_30: true, gfdl: true }
 276+ */
 277+ setValues: function( licenseValues ) {
 278+ var _this = this;
 279+ $j.each( _this.inputs, function( i, $input ) {
 280+ var template = $input.val();
 281+ $input.attr( 'checked', ~~!!licenseValues[template] );
 282+ } );
 283+ // we use the selector because events can't be unbound unless they're in the DOM.
 284+ _this.$selector.trigger( 'changeLicenses' );
 285+ },
 286+
 287+ /**
 288+ * Set the default configured licenses
 289+ */
 290+ setDefaultValues: function() {
 291+ var _this = this;
 292+ var values = {};
 293+ $j.each( mw.UploadWizard.config[ 'licenses' ], function( i, licenseConfig ) {
 294+ values[ licenseConfig.template ] = licenseConfig['default'];
 295+ } );
 296+ _this.setValues( values );
 297+ },
 298+
 299+ /**
 300+ * Gets the templates associated with checked inputs
 301+ * @return array of template names
 302+ */
 303+ getTemplates: function() {
 304+ return $j( this.inputs )
 305+ .filter( function() { return this.is( ':checked' ); } )
 306+ .map( function() { return this.val(); } );
 307+ },
 308+
 309+ /**
 310+ * Check if a valid value is set, also look for incompatible choices.
 311+ * Side effect: if no valid value, add notes to the interface. Add listeners to interface, to revalidate and remove notes.
 312+ * @return boolean; true if a value set, false otherwise
 313+ */
 314+ valid: function() {
 315+ var _this = this;
 316+ var isValid = true;
 317+
 318+ if ( ! _this.isSet() ) {
 319+ isValid = false;
 320+ errorHtml = gM( 'mwe-upwiz-deeds-need-license' );
 321+ }
 322+
 323+ // XXX something goes here for licenses incompatible with each other
 324+
 325+ var $errorEl = this.$selector.find( '.mwe-error' );
 326+ if (isValid) {
 327+ $errorEl.fadeOut();
 328+ } else {
 329+ // we bind to $selector because unbind() doesn't work on non-DOM objects
 330+ _this.$selector.bind( 'changeLicenses.valid', function() {
 331+ _this.$selector.unbind( 'changeLicenses.valid' );
 332+ _this.valid();
 333+ } );
 334+ $errorEl.html( errorHtml ).show();
 335+ }
 336+
 337+ return isValid;
 338+ },
 339+
 340+
 341+ /**
 342+ * Returns true if any license is set
 343+ * @return boolean
 344+ */
 345+ isSet: function() {
 346+ return this.getTemplates().length > 0;
 347+ }
 348+
 349+};
 350+
 351+
 352+/**
 353+ * Represents the upload -- in its local and remote state. (Possibly those could be separate objects too...)
 354+ * This is our 'model' object if we are thinking MVC. Needs to be better factored, lots of feature envy with the UploadWizard
 355+ * states:
 356+ * 'new' 'transporting' 'transported' 'details' 'submitting-details' 'complete'
 357+ * should fork this into two -- local and remote, e.g. filename
 358+ */
 359+mw.UploadWizardUpload = function( filesDiv ) {
 360+ var _this = this;
 361+ _this.state = 'new';
 362+ _this.transportWeight = 1; // default
 363+ _this.detailsWeight = 1; // default
 364+ _this._thumbnails = {};
 365+ _this.imageinfo = {};
 366+ _this.title = undefined;
 367+ _this.filename = undefined;
 368+ _this.originalFilename = undefined;
 369+ _this.mimetype = undefined;
 370+ _this.extension = undefined;
 371+
 372+ // details
 373+ _this.ui = new mw.UploadWizardUploadInterface( _this, filesDiv );
 374+
 375+ // handler -- usually ApiUploadHandler
 376+ // _this.handler = new ( mw.UploadWizard.config[ 'uploadHandlerClass' ] )( _this );
 377+ // _this.handler = new mw.MockUploadHandler( _this );
 378+ _this.handler = new mw.ApiUploadHandler( _this );
 379+};
 380+
 381+mw.UploadWizardUpload.prototype = {
 382+
 383+ acceptDeed: function( deed ) {
 384+ var _this = this;
 385+ _this.deed.applyDeed( _this );
 386+ },
 387+
 388+ /**
 389+ * start
 390+ */
 391+ start: function() {
 392+ var _this = this;
 393+ _this.setTransportProgress(0.0);
 394+ _this.ui.start();
 395+ _this.handler.start();
 396+ },
 397+
 398+ /**
 399+ * remove this upload. n.b. we trigger a removeUpload this is usually triggered from
 400+ */
 401+ remove: function() {
 402+ if ( this.details && this.details.div ) {
 403+ this.details.div.remove();
 404+ }
 405+ if ( this.thanksDiv ) {
 406+ this.thanksDiv.remove();
 407+ }
 408+ // we signal to the wizard to update itself, which has to delete the final vestige of
 409+ // this upload (the ui.div). We have to do this silly dance because we
 410+ // trigger through the div. Triggering through objects doesn't always work.
 411+ $j( this.ui.div ).trigger( 'removeUploadEvent' );
 412+ },
 413+
 414+
 415+ /**
 416+ * Wear our current progress, for observing processes to see
 417+ * XXX this is kind of a misnomer; this event is not firing except for the very first time.
 418+ * @param fraction
 419+ */
 420+ setTransportProgress: function ( fraction ) {
 421+ var _this = this;
 422+ _this.state = 'transporting';
 423+ _this.transportProgress = fraction;
 424+ $j( _this.ui.div ).trigger( 'transportProgressEvent' );
 425+ },
 426+
 427+ /**
 428+ * To be executed when an individual upload finishes. Processes the result and updates step 2's details
 429+ * @param result the API result in parsed JSON form
 430+ */
 431+ setTransported: function( result ) {
 432+ var _this = this;
 433+ _this.state = 'transported';
 434+ _this.transportProgress = 1;
 435+ $j( _this.ui.div ).trigger( 'transportedEvent' );
 436+
 437+ if ( result.upload && result.upload.imageinfo && result.upload.imageinfo.descriptionurl ) {
 438+ // success
 439+ _this.extractUploadInfo( result );
 440+ _this.deedPreview.setup();
 441+ _this.details.populate();
 442+
 443+ } else if ( result.upload && result.upload.sessionkey ) {
 444+ // there was a warning - type error which prevented it from adding the result to the db
 445+ if ( result.upload.warnings.duplicate ) {
 446+ var duplicates = result.upload.warnings.duplicate;
 447+ _this.details.errorDuplicate( result.upload.sessionkey, duplicates );
 448+ }
 449+
 450+ // and other errors that result in a stash
 451+ } else {
 452+ alert("failure!");
 453+ // we may want to tag or otherwise queue it as an upload to retry
 454+ }
 455+
 456+
 457+ },
 458+
 459+
 460+ /**
 461+ * call when the file is entered into the file input
 462+ * get as much data as possible -- maybe exif, even thumbnail maybe
 463+ */
 464+ extractLocalFileInfo: function( localFilename ) {
 465+ var _this = this;
 466+ if (false) { // FileAPI, one day
 467+ _this.transportWeight = getFileSize();
 468+ }
 469+ _this.extension = mw.UploadWizardUtil.getExtension( localFilename );
 470+ // XXX add filename, original filename, extension, whatever else is interesting.
 471+ },
 472+
 473+
 474+ /**
 475+ * Accept the result from a successful API upload transport, and fill our own info
 476+ *
 477+ * @param result The JSON object from a successful API upload result.
 478+ */
 479+ extractUploadInfo: function( result ) {
 480+ var _this = this;
 481+
 482+ _this.filename = result.upload.filename;
 483+ _this.title = wgFormattedNamespaces[wgNamespaceIds['file']] + ':' + _this.filename;
 484+
 485+ _this.extractImageInfo( result.upload.imageinfo );
 486+
 487+ },
 488+
 489+ /**
 490+ * Extract image info into our upload object
 491+ * Image info is obtained from various different API methods
 492+ * @param imageinfo JSON object obtained from API result.
 493+ */
 494+ extractImageInfo: function( imageinfo ) {
 495+ var _this = this;
 496+ for ( var key in imageinfo ) {
 497+ // we get metadata as list of key-val pairs; convert to object for easier lookup. Assuming that EXIF fields are unique.
 498+ if ( key == 'metadata' ) {
 499+ _this.imageinfo.metadata = {};
 500+ if ( imageinfo.metadata && imageinfo.metadata.length ) {
 501+ $j.each( imageinfo.metadata, function( i, pair ) {
 502+ if ( pair !== undefined ) {
 503+ _this.imageinfo.metadata[pair['name'].toLowerCase()] = pair['value'];
 504+ }
 505+ } );
 506+ }
 507+ } else {
 508+ _this.imageinfo[key] = imageinfo[key];
 509+ }
 510+ }
 511+
 512+ // we should already have an extension, but if we don't...
 513+ if ( _this.extension === undefined ) {
 514+ var extension = mw.UploadWizardUtil.getExtension( _this.imageinfo.url );
 515+ if ( !extension ) {
 516+ if ( _this.imageinfo.mimetype ) {
 517+ if ( mw.UploadWizardUtil.mimetypeToExtension[ _this.imageinfo.mimetype ] ) {
 518+ extension = mw.UploadWizardUtil.mimetypeToExtension[ _this.imageinfo.mimetype ];
 519+ }
 520+ }
 521+ }
 522+ }
 523+ },
 524+
 525+ /**
 526+ * Supply information to create a thumbnail for this Upload. Runs async, with a callback.
 527+ * It is assumed you don't call this until it's been transported.
 528+ *
 529+ * XXX should check if we really need this second API call or if we can get MediaWiki to make us a thumbnail URL upon upload
 530+ *
 531+ * @param width - desired width of thumbnail (height will scale to match)
 532+ * @param callback - callback to execute once thumbnail has been obtained -- must accept object with properties of width, height, and url.
 533+ */
 534+ getThumbnail: function( width, callback ) {
 535+ var _this = this;
 536+ if ( _this._thumbnails[ "width" + width ] !== undefined ) {
 537+ callback( _this._thumbnails[ "width" + width ] );
 538+ return;
 539+ }
 540+
 541+ var apiUrl = mw.UploadWizard.config.apiUrl;
 542+
 543+ var params = {
 544+ 'titles': _this.title,
 545+ 'prop': 'imageinfo',
 546+ 'iiurlwidth': width,
 547+ 'iiprop': 'url'
 548+ };
 549+
 550+ mw.getJSON( apiUrl, params, function( data ) {
 551+ if ( !data || !data.query || !data.query.pages ) {
 552+ mw.log(" No data? ");
 553+ // XXX do something about the thumbnail spinner, maybe call the callback with a broken image.
 554+ return;
 555+ }
 556+
 557+ if ( data.query.pages[-1] ) {
 558+ // XXX do something about the thumbnail spinner, maybe call the callback with a broken image.
 559+ return;
 560+ }
 561+ for ( var page_id in data.query.pages ) {
 562+ var page = data.query.pages[ page_id ];
 563+ if ( ! page.imageinfo ) {
 564+ alert("imageinfo missing");
 565+ // not found? error
 566+ } else {
 567+ var imageInfo = page.imageinfo[0];
 568+ var thumbnail = {
 569+ width: imageInfo.thumbwidth,
 570+ height: imageInfo.thumbheight,
 571+ url: imageInfo.thumburl
 572+ };
 573+ _this._thumbnails[ "width" + width ] = thumbnail;
 574+ callback( thumbnail );
 575+ }
 576+ }
 577+ } );
 578+
 579+ },
 580+
 581+
 582+ /**
 583+ * look up thumbnail info and set it in HTML, with loading spinner
 584+ * it might be interesting to make this more of a publish/subscribe thing, since we have to do this 3x
 585+ * the callbacks may pile up, getting unnecessary info
 586+ *
 587+ * @param selector
 588+ * @param width
 589+ */
 590+ setThumbnail: function( selector, width ) {
 591+ var _this = this;
 592+ if ( typeof width === 'undefined' || width === null || width <= 0 ) {
 593+ width = mw.UploadWizard.config[ 'thumbnailWidth' ];
 594+ }
 595+ width = parseInt( width, 10 );
 596+
 597+ var callback = function( thumbnail ) {
 598+ // side effect: will replace thumbnail's loadingSpinner
 599+ $j( selector ).html(
 600+ $j('<a/>')
 601+ .attr( { 'href': _this.imageinfo.descriptionurl,
 602+ 'target' : '_new' } )
 603+ .append(
 604+ $j( '<img/>' )
 605+ .attr( 'width', thumbnail.width )
 606+ .attr( 'height', thumbnail.height )
 607+ .attr( 'src', thumbnail.url ) ) );
 608+ };
 609+
 610+ $j( selector ).loadingSpinner();
 611+ _this.getThumbnail( width, callback );
 612+ }
 613+
 614+
 615+
 616+};
 617+
 618+/**
 619+ * Create an interface fragment corresponding to a file input, suitable for Upload Wizard.
 620+ * @param upload
 621+ * @param div to insert file interface
 622+ * @param addInterface interface to add a new one (assumed that we start out there)
 623+ */
 624+mw.UploadWizardUploadInterface = function( upload, filesDiv ) {
 625+ var _this = this;
 626+
 627+ _this.upload = upload;
 628+
 629+ // may need to collaborate with the particular upload type sometimes
 630+ // for the interface, as well as the uploadwizard. OY.
 631+ _this.div = $j('<div class="mwe-upwiz-file"></div>').get(0);
 632+ _this.isFilled = false;
 633+
 634+ _this.fileInputCtrl = $j('<input size="1" class="mwe-upwiz-file-input" name="file" type="file"/>')
 635+ .change( function() { _this.fileChanged(); } )
 636+ .get(0);
 637+
 638+
 639+ visibleFilenameDiv = $j('<div class="mwe-upwiz-visible-file"></div>')
 640+ .append(
 641+ $j.fn.removeCtrl( 'mwe-upwiz-remove-upload', function() { _this.upload.remove(); } ),
 642+
 643+ $j( '<div class="mwe-upwiz-file-indicator"></div>' ),
 644+
 645+ $j( '<div class="mwe-upwiz-visible-file-filename">' )
 646+ .append(
 647+ $j( '<span class="ui-icon ui-icon-document" style="display: inline-block;" />' ),
 648+ $j( '<span class="mwe-upwiz-visible-file-filename-text"/>' )
 649+ )
 650+
 651+ );
 652+
 653+ //_this.errorDiv = $j('<div class="mwe-upwiz-upload-error mwe-upwiz-file-indicator" style="display: none;"></div>').get(0);
 654+
 655+ _this.filenameCtrl = $j('<input type="hidden" name="filename" value=""/>').get(0);
 656+
 657+ // this file Ctrl container is placed over other interface elements, intercepts clicks and gives them to the file input control.
 658+ // however, we want to pass hover events to interface elements that we are over, hence the bindings.
 659+ // n.b. not using toggleClass because it often gets this event wrong -- relies on previous state to know what to do
 660+ _this.fileCtrlContainer = $j('<div class="mwe-upwiz-file-ctrl-container">');
 661+/*
 662+ .bind( 'mouseenter', function(e) { _this.addFileCtrlHover(e); } )
 663+ .bind( 'mouseleave', function(e) { _this.removeFileCtrlHover(e); } );
 664+*/
 665+
 666+ // the css trickery (along with css)
 667+ // here creates a giant size file input control which is contained within a div and then
 668+ // clipped for overflow. The effect is that we have a div (ctrl-container) we can position anywhere
 669+ // which works as a file input. It will be set to opacity:0 and then we can do whatever we want with
 670+ // interface "below".
 671+ // XXX caution -- if the add file input changes size we won't match, unless we add some sort of event to catch this.
 672+ _this.form = $j('<form class="mwe-upwiz-form"></form>')
 673+ .append( visibleFilenameDiv )
 674+ .append( _this.fileCtrlContainer
 675+ .append( _this.fileInputCtrl )
 676+ )
 677+ .append( _this.filenameCtrl ).get( 0 );
 678+
 679+
 680+ $j( _this.div ).append( _this.form );
 681+
 682+ // XXX evil hardcoded
 683+ // we don't really need filesdiv if we do it this way?
 684+ $j( _this.div ).insertBefore( '#mwe-upwiz-upload-ctrls' ); // append( _this.div );
 685+
 686+ // _this.progressBar = ( no progress bar for individual uploads yet )
 687+ // we bind to the ui div since unbind doesn't work for non-DOM objects
 688+ $j( _this.div ).bind( 'transportProgressEvent', function(e) { _this.showTransportProgress(); } );
 689+ $j( _this.div ).bind( 'transportedEvent', function(e) { _this.showTransported(); } );
 690+
 691+};
 692+
 693+
 694+mw.UploadWizardUploadInterface.prototype = {
 695+ /**
 696+ * Things to do to this interface once we start uploading
 697+ */
 698+ start: function() {
 699+ var _this = this;
 700+ // remove hovering
 701+ $j( _this.div )
 702+ .unbind( 'mouseenter mouseover mouseleave mouseout' );
 703+
 704+ // remove delete control
 705+ $j( _this.div )
 706+ .find( '.mwe-upwiz-remove-ctrl' )
 707+ .unbind( 'mouseenter mouseover mouseleave mouseout' )
 708+ .remove();
 709+ },
 710+
 711+ busy: function() {
 712+ var _this = this;
 713+ // for now we implement this as looking like "100% progress"
 714+ // e.g. an animated bar that takes up all the space
 715+ // _this.showTransportProgress();
 716+ },
 717+
 718+ /**
 719+ *
 720+ */
 721+ showIndicatorMessage: function( classToRemove, classToAdd, msgKey ) {
 722+ var _this = this;
 723+ var $indicator = $j( _this.div ).find( '.mwe-upwiz-file-indicator' );
 724+ if ( classToRemove ) {
 725+ $indicator.removeClass( classToRemove );
 726+ }
 727+ if ( classToAdd ) {
 728+ $indicator.addClass( classToAdd );
 729+ }
 730+ $indicator.html( gM( msgKey ) );
 731+ $j( _this.div ).find( '.mwe-upwiz-visible-file-filename' )
 732+ .css( 'margin-right', ( $indicator.outerWidth() + 24 ).toString() + 'px' );
 733+ $indicator.css( 'visibility', 'visible' );
 734+ },
 735+
 736+ /**
 737+ * Put the visual state of an individual upload ito "progress"
 738+ * @param fraction The fraction of progress. Float between 0 and 1
 739+ */
 740+ showTransportProgress: function() {
 741+ this.showIndicatorMessage( null, 'mwe-upwiz-status-progress', 'mwe-upwiz-uploading' );
 742+ // update individual progress bar with fraction?
 743+ },
 744+
 745+ /**
 746+ * Execute when this upload is transported; cleans up interface.
 747+ */
 748+ showTransported: function() {
 749+ this.showIndicatorMessage( 'mwe-upwiz-status-progress', 'mwe-upwiz-status-completed', 'mwe-upwiz-transported' );
 750+ },
 751+
 752+ /**
 753+ * Run this when the value of the file input has changed. Check the file for various forms of goodness.
 754+ * If okay, then update the visible filename (due to CSS trickery the real file input is invisible)
 755+ */
 756+ fileChanged: function() {
 757+ var _this = this;
 758+ _this.clearErrors();
 759+ _this.upload.extractLocalFileInfo( $j( _this.fileInputCtrl ).val() );
 760+ if ( _this.isGoodExtension( _this.upload.extension ) ) {
 761+ _this.updateFilename();
 762+ } else {
 763+ //_this.error( 'bad-filename-extension', ext );
 764+ alert("bad extension");
 765+ }
 766+ },
 767+
 768+ /**
 769+ * Move the file input to cover a certain element on the page.
 770+ * We use invisible file inputs because this is the only way to style a file input
 771+ * or otherwise get it to do what you want.
 772+ * It is helpful to sometimes move them to cover certain elements on the page, and
 773+ * even to pass events like hover
 774+ * @param selector jquery-compatible selector, for a single element
 775+ */
 776+ moveFileInputToCover: function( selector, offset ) {
 777+
 778+ //mw.log( "moving to cover " + selector );
 779+ var _this = this;
 780+ var $covered = $j( selector );
 781+
 782+ var topOffset, rightOffset, bottomOffset, leftOffset;
 783+ topOffset = rightOffset = bottomOffset = leftOffset = 0;
 784+ if (typeof offset != 'undefined' ) {
 785+ topOffset = offset[0];
 786+ rightOffset = offset[1];
 787+ bottomOffset = offset[2];
 788+ leftOffset = offset[3];
 789+ }
 790+ var widthOffset = rightOffset - leftOffset;
 791+ var heightOffset = bottomOffset - topOffset;
 792+ //mw.log( "position: " );
 793+ //mw.log( $covered.position() );
 794+ var position = $covered.position();
 795+ _this.fileCtrlContainer
 796+ .css( $covered.position() )
 797+ .width( $covered.outerWidth() )
 798+ .height( $covered.outerHeight() );
 799+ /*
 800+ {
 801+ 'top': (position['top'] + topOffset).toString() + 'px',
 802+ 'left': (position['left'] + leftOffset).toString() + 'px'
 803+ } )
 804+ .width( ($covered.outerWidth() + widthOffset).toString() + 'px' )
 805+ .height( ($covered.outerHeight() + heightOffset).toString() + 'px' );
 806+ */
 807+
 808+ // shift the file input over with negative margins,
 809+ // internal to the overflow-containing div, so the div shows all button
 810+ // and none of the textfield-like input
 811+ $j( _this.fileInputCtrl ).css( {
 812+ 'margin-left': '-' + ~~( $j( _this.fileInputCtrl).width() - $covered.outerWidth() - 10 ) + 'px',
 813+ 'margin-top' : '-' + ~~( $j( _this.fileInputCtrl).height() - $covered.outerHeight() - 10 ) + 'px'
 814+ } );
 815+
 816+
 817+ },
 818+
 819+ /**
 820+ * this does two things:
 821+ * 1 ) since the file input has been hidden with some clever CSS ( to avoid x-browser styling issues ),
 822+ * update the visible filename
 823+ *
 824+ * 2 ) update the filename desired when added to MediaWiki. This should be RELATED to the filename on the filesystem,
 825+ * but it should be silently fixed so that it does not trigger uniqueness conflicts. i.e. if server has cat.jpg we change ours to cat_2.jpg.
 826+ * This is hard to do in a scalable fashion on the client; we don't want to do 12 api calls to get cat_12.jpg.
 827+ * Ideally we should ask the SERVER for a decently unique filename related to our own.
 828+ * So, at the moment, this is hacked with a guaranteed - unique filename instead.
 829+ */
 830+ updateFilename: function() {
 831+ var _this = this;
 832+ var path = $j(_this.fileInputCtrl).attr('value');
 833+
 834+
 835+ // visible filename
 836+ $j( _this.form ).find( '.mwe-upwiz-visible-file-filename-text' ).html( path );
 837+
 838+ // desired filename
 839+ var filename = _this.convertPathToFilename( path );
 840+ _this.upload.originalFilename = filename;
 841+ // this is a hack to get a filename guaranteed unique.
 842+ uniqueFilename = mw.UploadWizard.config[ 'userName' ] + "_" + ( new Date() ).getTime() + "_" + filename;
 843+ $j( _this.filenameCtrl ).attr( 'value', uniqueFilename );
 844+
 845+ if ( ! _this.isFilled ) {
 846+ var $div = $j( _this.div );
 847+ _this.isFilled = true;
 848+ $div.addClass( 'filled' );
 849+
 850+
 851+ // cover the div with the file input.
 852+ // we use the visible-file div because it has the same offsetParent as the file input
 853+ // the second argument offsets the fileinput to the right so there's room for the close icon to get mouse events
 854+ _this.moveFileInputToCover(
 855+ $div.find( '.mwe-upwiz-visible-file-filename' )
 856+ );
 857+
 858+ // Highlight the file on mouseover (and also show controls like the remove control).
 859+ //
 860+ // On Firefox there are bugs related to capturing mouse events on inputs, so we seem to miss the
 861+ // mouseenter or mouseleave events randomly. It's only really bad if we miss mouseleave,
 862+ // and have two highlights visible. so we add another call to REALLY make sure that other highlights
 863+ // are deactivated.
 864+ // http://code.google.com/p/fbug/issues/detail?id=2075
 865+ //
 866+ // ALSO: When file inputs are adjacent, Firefox misses the "mouseenter" and "mouseleave" events.
 867+ // Consequently we have to bind to "mouseover" and "mouseout" as well even though that's not as efficient.
 868+ $div.bind( 'mouseenter mouseover', function() {
 869+ $div.addClass( 'hover' );
 870+ $j( '#mwe-upwiz-files' )
 871+ .children()
 872+ .filter( function() { return this !== _this.div; } )
 873+ .removeClass('hover');
 874+ }, false );
 875+ $div.bind( 'mouseleave mouseout', function() {
 876+ $div.removeClass( 'hover' );
 877+ }, false );
 878+ $j( _this.div ).trigger( 'filled' );
 879+ } else {
 880+ $j( _this.div ).trigger( 'filenameAccepted' );
 881+ }
 882+ },
 883+
 884+ /**
 885+ * Remove any complaints we had about errors and such
 886+ * XXX this should be changed to something Theme compatible
 887+ */
 888+ clearErrors: function() {
 889+ var _this = this;
 890+ $j( _this.div ).removeClass( 'mwe-upwiz-upload-error ');
 891+ $j( _this.errorDiv ).hide().empty();
 892+ },
 893+
 894+ /**
 895+ * Show an error with the upload
 896+ */
 897+ error: function() {
 898+ var _this = this;
 899+ var args = Array.prototype.slice.call( arguments ); // copies arguments into a real array
 900+ var msg = 'mwe-upwiz-upload-error-' + args[0];
 901+ $j( _this.errorDiv ).append( $j( '<p class="mwe-upwiz-upload-error">' + gM( msg, args.slice( 1 ) ) + '</p>') );
 902+ // apply a error style to entire did
 903+ $j( _this.div ).addClass( 'mwe-upwiz-upload-error' );
 904+ $j( _this.errorDiv ).show();
 905+ },
 906+
 907+ /**
 908+ * Get the extension of the path in fileInputCtrl
 909+ * @return extension as string
 910+ */
 911+ getExtension: function() {
 912+ var _this = this;
 913+ var path = $j(_this.fileInputCtrl).attr('value');
 914+ return mw.UploadWizardUtil.getExtension(path);
 915+ },
 916+
 917+ /**
 918+ * XXX this is common utility code
 919+ * used when converting contents of a file input and coming up with a suitable "filename" for mediawiki
 920+ * test: what if path is length 0
 921+ * what if path is all separators
 922+ * what if path ends with a separator character
 923+ * what if it ends with multiple separator characters
 924+ *
 925+ * @param path
 926+ * @return filename suitable for mediawiki as string
 927+ */
 928+ convertPathToFilename: function( path ) {
 929+ if (path === undefined || path === '') {
 930+ return '';
 931+ }
 932+
 933+ var lastFileSeparatorIdx = Math.max(path.lastIndexOf( '/' ), path.lastIndexOf( '\\' ));
 934+ // lastFileSeparatorIdx is now -1 if no separator found, or some index in the string.
 935+ // so, +1, that is either 0 ( beginning of string ) or the character after last separator.
 936+ // caution! could go past end of string... need to be more careful
 937+ var filename = path.substr( lastFileSeparatorIdx + 1 );
 938+ return mw.UploadWizardUtil.pathToTitle( filename );
 939+
 940+
 941+
 942+ },
 943+
 944+ /**
 945+ * XXX this is common utility code
 946+ * copied because we'll probably need it... stripped from old doDestinationFill
 947+ * this is used when checking for "bad" extensions in a filename.
 948+ * @param ext
 949+ * @return boolean if extension was acceptable
 950+ */
 951+ isGoodExtension: function( ext ) {
 952+ var _this = this;
 953+ var found = false;
 954+ var extensions = mw.UploadWizard.config[ 'fileExtensions' ];
 955+ if ( extensions ) {
 956+ for ( var i = 0; i < extensions.length; i++ ) {
 957+ if ( extensions[i].toLowerCase() == ext ) {
 958+ found = true;
 959+ }
 960+ }
 961+ }
 962+ return found;
 963+ }
 964+
 965+};
 966+
 967+/**
 968+ * Object that represents an indvidual language description, in the details portion of Upload Wizard
 969+ * @param languageCode -- string
 970+ * @param firstRequired -- boolean -- the first description is required and should be validated and displayed a bit differently
 971+ */
 972+mw.UploadWizardDescription = function( languageCode, required ) {
 973+ var _this = this;
 974+ mw.UploadWizardDescription.prototype.count++;
 975+ _this.id = 'description' + mw.UploadWizardDescription.prototype.count;
 976+
 977+ // XXX for some reason this display:block is not making it into HTML
 978+ var errorLabelDiv = $j( '<div class="mwe-upwiz-details-input-error">'
 979+ + '<label generated="true" class="mwe-validator-error" for="' + _this.id + '" />'
 980+ + '</div>' );
 981+
 982+ var fieldnameDiv = $j( '<div class="mwe-upwiz-details-fieldname" />' );
 983+ if ( required ) {
 984+ fieldnameDiv.append( gM( 'mwe-upwiz-desc' ) ).requiredFieldLabel();
 985+ }
 986+
 987+
 988+ // Logic copied from MediaWiki:UploadForm.js
 989+ // Per request from Portuguese and Brazilian users, treat Brazilian Portuguese as Portuguese.
 990+ if (languageCode == 'pt-br') {
 991+ languageCode = 'pt';
 992+ // this was also in UploadForm.js, but without the heartwarming justification
 993+ } else if (languageCode == 'en-gb') {
 994+ languageCode = 'en';
 995+ }
 996+
 997+ _this.languageMenu = mw.LanguageUpWiz.getMenu( 'lang', languageCode );
 998+ $j(_this.languageMenu).addClass( 'mwe-upwiz-desc-lang-select' );
 999+
 1000+ _this.input = $j( '<textarea name="' + _this.id + '" rows="2" cols="36" class="mwe-upwiz-desc-lang-text"></textarea>' )
 1001+ .attr( 'title', gM( 'mwe-upwiz-tooltip-description' ) )
 1002+ .growTextArea()
 1003+ .tipsyPlus( { plus: 'even more stuff' } );
 1004+
 1005+ // descriptions
 1006+ _this.div = $j('<div class="mwe-upwiz-details-descriptions-container ui-helper-clearfix"></div>' )
 1007+ .append( errorLabelDiv, fieldnameDiv, _this.languageMenu, _this.input );
 1008+
 1009+};
 1010+
 1011+mw.UploadWizardDescription.prototype = {
 1012+
 1013+ /* widget count for auto incrementing */
 1014+ count: 0,
 1015+
 1016+ /**
 1017+ * Obtain text of this description, suitable for including into Information template
 1018+ * @return wikitext as a string
 1019+ */
 1020+ getWikiText: function() {
 1021+ var _this = this;
 1022+ var description = $j( _this.input ).val().trim();
 1023+ // we assume that form validation has caught this problem if this is a required field
 1024+ // if not, assume the user is trying to blank a description in another language
 1025+ if ( description.length === 0 ) {
 1026+ return '';
 1027+ }
 1028+ var language = $j( _this.languageMenu ).val().trim();
 1029+ var fix = mw.UploadWizard.config[ "languageTemplateFixups" ];
 1030+ if (fix[language]) {
 1031+ language = fix[language];
 1032+ }
 1033+ return '{{' + language + '|1=' + description + '}}';
 1034+ },
 1035+
 1036+ /**
 1037+ * defer adding rules until it's in a form
 1038+ * @return validator
 1039+ */
 1040+ addValidationRules: function( required ) {
 1041+ // validator must find a form, so we add rules here
 1042+ return this.input.rules( "add", {
 1043+ minlength: mw.UploadWizard.config[ 'minDescriptionLength' ],
 1044+ maxlength: mw.UploadWizard.config[ 'maxDescriptionLength' ],
 1045+ required: required,
 1046+ messages: {
 1047+ required: gM( 'mwe-upwiz-error-blank' ),
 1048+ minlength: gM( 'mwe-upwiz-error-too-short', mw.UploadWizard.config[ 'minDescriptionLength' ] ),
 1049+ maxlength: gM( 'mwe-upwiz-error-too-long', mw.UploadWizard.config[ 'maxDescriptionLength' ] )
 1050+ }
 1051+ } );
 1052+ }
 1053+};
 1054+
 1055+/**
 1056+ * Object that represents the Details (step 2) portion of the UploadWizard
 1057+ * n.b. each upload gets its own details.
 1058+ *
 1059+ * XXX a lot of this construction is not really the jQuery way.
 1060+ * The correct thing would be to have some hidden static HTML
 1061+ * on the page which we clone and slice up with selectors. Inputs can still be members of the object
 1062+ * but they'll be found by selectors, not by creating them as members and then adding them to a DOM structure.
 1063+ *
 1064+ * XXX this should have styles for what mode we're in
 1065+ *
 1066+ * @param UploadWizardUpload
 1067+ * @param containerDiv The div to put the interface into
 1068+ */
 1069+mw.UploadWizardDetails = function( upload, containerDiv ) {
 1070+
 1071+ var _this = this;
 1072+ _this.upload = upload;
 1073+
 1074+ _this.descriptions = [];
 1075+
 1076+ _this.div = $j( '<div class="mwe-upwiz-info-file ui-helper-clearfix"></div>' );
 1077+
 1078+ _this.thumbnailDiv = $j( '<div class="mwe-upwiz-thumbnail mwe-upwiz-thumbnail-side"></div>' );
 1079+
 1080+ _this.dataDiv = $j( '<div class="mwe-upwiz-data"></div>' );
 1081+
 1082+ // descriptions
 1083+ _this.descriptionsDiv = $j( '<div class="mwe-upwiz-details-descriptions"></div>' );
 1084+
 1085+ _this.descriptionAdder = $j( '<a class="mwe-upwiz-more-options"/>' )
 1086+ .html( gM( 'mwe-upwiz-desc-add-0' ) )
 1087+ .click( function( ) { _this.addDescription(); } );
 1088+
 1089+ var descriptionAdderDiv =
 1090+ $j( '<div />' ).append(
 1091+ $j( '<div class="mwe-upwiz-details-fieldname" />' ),
 1092+ $j( '<div class="mwe-upwiz-details-descriptions-add" />' )
 1093+ .append( _this.descriptionAdder )
 1094+ );
 1095+
 1096+ // Commons specific help for titles
 1097+ // http://commons.wikimedia.org/wiki/Commons:File_naming
 1098+ // http://commons.wikimedia.org/wiki/MediaWiki:Filename-prefix-blacklist
 1099+ // XXX make sure they can't use ctrl characters or returns or any other bad stuff.
 1100+ _this.titleId = "title" + _this.upload.index;
 1101+ _this.titleInput = $j( '<textarea type="text" id="' + _this.titleId + '" name="' + _this.titleId + '" rows="1" class="mwe-title mwe-long-textarea"></textarea>' )
 1102+ .attr( 'title', gM( 'mwe-upwiz-tooltip-title' ) )
 1103+ .tipsyPlus()
 1104+ .keyup( function() {
 1105+ _this.setFilenameFromTitle();
 1106+ } )
 1107+ .growTextArea()
 1108+ .destinationChecked( {
 1109+ spinner: function(bool) { _this.toggleDestinationBusy(bool); },
 1110+ preprocess: function( name ) { return _this.getFilenameFromTitle(); }, // XXX this is no longer a pre-process
 1111+ processResult: function( result ) { _this.processDestinationCheck( result ); }
 1112+ } );
 1113+
 1114+ _this.titleErrorDiv = $j('<div class="mwe-upwiz-details-input-error"><label class="mwe-error" for="' + _this.titleId + '" generated="true"/></div>');
 1115+
 1116+ var titleContainerDiv = $j('<div class="mwe-upwiz-details-fieldname-input ui-helper-clearfix"></div>')
 1117+ .append(
 1118+ _this.titleErrorDiv,
 1119+ $j( '<div class="mwe-upwiz-details-fieldname"></div>' )
 1120+ .requiredFieldLabel()
 1121+ .append( gM( 'mwe-upwiz-title' ) ),
 1122+ $j( '<div class="mwe-upwiz-details-input"></div>' ).append( _this.titleInput )
 1123+ );
 1124+
 1125+ _this.deedDiv = $j( '<div class="mwe-upwiz-custom-deed" />' );
 1126+
 1127+ _this.copyrightInfoFieldset = $j('<fieldset class="mwe-fieldset mwe-upwiz-copyright-info"></fieldset>')
 1128+ .hide()
 1129+ .append(
 1130+ $j( '<legend class="mwe-legend">' ).append( gM( 'mwe-upwiz-copyright-info' ) ),
 1131+ _this.deedDiv
 1132+ );
 1133+
 1134+ var $categoriesDiv = $j('<div class="mwe-upwiz-details-fieldname-input ui-helper-clearfix">'
 1135+ + '<div class="mwe-upwiz-details-fieldname"></div>'
 1136+ + '<div class="mwe-upwiz-details-input"></div>'
 1137+ + '</div>' );
 1138+ $categoriesDiv.find( '.mwe-upwiz-details-fieldname' ).append( gM( 'mwe-upwiz-categories' ) );
 1139+ var categoriesId = 'categories' + _this.upload.index;
 1140+ $categoriesDiv.find( '.mwe-upwiz-details-input' )
 1141+ .append( $j( '<input/>' ).attr( { id: categoriesId,
 1142+ name: categoriesId,
 1143+ type: 'text' } )
 1144+ );
 1145+
 1146+ var moreDetailsDiv = $j('<div class="mwe-more-details"></div>');
 1147+
 1148+ var moreDetailsCtrlDiv = $j( '<div class="mwe-upwiz-details-more-options"></div>' );
 1149+
 1150+ var dateInputId = "dateInput" + ( _this.upload.index ).toString();
 1151+ var dateDisplayInputId = "dateDisplayInput" + ( _this.upload.index ).toString();
 1152+
 1153+ var dateErrorDiv = $j('<div class="mwe-upwiz-details-input-error"><label class="mwe-validator-error" for="' + dateInputId + '" generated="true"/></div>');
 1154+
 1155+ /* XXX must localize this by loading jquery.ui.datepicker-XX.js where XX is a language code */
 1156+ /* jQuery.ui.datepicker also modifies first-day-of-week according to language, which is somewhat wrong. */
 1157+ /* $.datepicker.setDefaults() for other settings */
 1158+ _this.dateInput =
 1159+ $j( '<input type="text" id="' + dateInputId + '" name="' + dateInputId + '" type="text" class="mwe-date" size="20"/>' );
 1160+ _this.dateDisplayInput =
 1161+ $j( '<input type="text" id="' + dateDisplayInputId + '" name="' + dateDisplayInputId + '" type="text" class="mwe-date-display" size="20"/>' );
 1162+
 1163+
 1164+ var dateInputDiv = $j( '<div class="mwe-upwiz-details-fieldname-input ui-helper-clearfix"></div>' )
 1165+ .append(
 1166+ dateErrorDiv,
 1167+ $j( '<div class="mwe-upwiz-details-fieldname"></div>' ).append( gM( 'mwe-upwiz-date-created' ) ),
 1168+ $j( '<div class="mwe-upwiz-details-input"></div>' ).append( _this.dateInput, _this.dateDisplayInput ) );
 1169+
 1170+ var otherInformationId = "otherInformation" + _this.upload.index;
 1171+ _this.otherInformationInput = $j( '<textarea id="' + otherInformationId + '" name="' + otherInformationId + '" class="mwe-upwiz-other-textarea"></textarea>' )
 1172+ .growTextArea()
 1173+ .attr( 'title', gM( 'mwe-upwiz-tooltip-other' ) )
 1174+ .tipsyPlus();
 1175+
 1176+ var otherInformationDiv = $j('<div></div>')
 1177+ .append( $j( '<div class="mwe-upwiz-details-more-label">' ).append( gM( 'mwe-upwiz-other' ) ) )
 1178+ .append( _this.otherInformationInput );
 1179+
 1180+
 1181+ $j( moreDetailsDiv ).append(
 1182+ dateInputDiv,
 1183+ // location goes here
 1184+ otherInformationDiv
 1185+ );
 1186+
 1187+ _this.$form = $j( '<form></form>' );
 1188+ _this.$form.append(
 1189+ _this.descriptionsDiv,
 1190+ descriptionAdderDiv,
 1191+ titleContainerDiv,
 1192+ _this.copyrightInfoFieldset,
 1193+ $categoriesDiv,
 1194+ moreDetailsCtrlDiv,
 1195+ moreDetailsDiv
 1196+ );
 1197+
 1198+ $j( _this.dataDiv ).append(
 1199+ _this.$form
 1200+ );
 1201+
 1202+ $j( _this.div ).append(
 1203+ _this.thumbnailDiv,
 1204+ _this.dataDiv
 1205+ );
 1206+
 1207+ _this.$form.validate();
 1208+ _this.$form.find( '.mwe-date' ).rules( "add", {
 1209+ dateISO: true,
 1210+ messages: {
 1211+ dateISO: gM( 'mwe-upwiz-error-date' )
 1212+ }
 1213+ } );
 1214+
 1215+ // we hide the "real" ISO date, and create another "display" date
 1216+ _this.$form.find( '.mwe-date-display' )
 1217+ .datepicker( {
 1218+ dateFormat: 'DD, MM d, yy',
 1219+ //buttonImage: mw.getMwEmbedPath() + 'skins/common/images/calendar.gif',
 1220+ showOn: 'focus',
 1221+ /* buttonImage: '???',
 1222+ buttonImageOnly: true, */
 1223+ changeMonth: true,
 1224+ changeYear: true,
 1225+ showAnim: 'slideDown',
 1226+ altField: '#' + dateInputId,
 1227+ altFormat: 'yy-mm-dd' } )
 1228+ .click( function() { $j( this ).datepicker( 'show' ); } )
 1229+ .readonly();
 1230+
 1231+ _this.$form.find( '.mwe-date' )
 1232+ .bind( 'change', function() { $j( this ).valid(); } )
 1233+ .hide();
 1234+
 1235+ /* if the date is not valid, we need to pop open the "more options". How?
 1236+ guess we'll revalidate it with element */
 1237+
 1238+ mw.UploadWizardUtil.makeToggler( moreDetailsCtrlDiv, moreDetailsDiv );
 1239+
 1240+ _this.addDescription( true, mw.UploadWizard.config[ 'userLanguage' ] );
 1241+ $j( containerDiv ).append( _this.div );
 1242+
 1243+ // make this a category picker
 1244+ $categoriesDiv.find( '.mwe-upwiz-details-input' )
 1245+ .find( 'input' )
 1246+ .mwCoolCats( { buttontext: gM( 'mwe-upwiz-categories-add' ) } );
 1247+
 1248+};
 1249+
 1250+mw.UploadWizardDetails.prototype = {
 1251+
 1252+ /**
 1253+ * check entire form for validity
 1254+ */
 1255+ // return boolean if we are ready to go.
 1256+ // side effect: add error text to the page for fields in an incorrect state.
 1257+ // we must call EVERY valid() function due to side effects; do not short-circuit.
 1258+ valid: function() {
 1259+ var _this = this;
 1260+ // at least one description -- never mind, we are disallowing removal of first description
 1261+ // all the descriptions -- check min & max length
 1262+
 1263+ // the title
 1264+ var titleInputValid = $j( _this.titleInput ).data( 'valid' );
 1265+ if ( typeof titleInputValid == 'undefined' ) {
 1266+ alert( "please wait, still checking the title for uniqueness..." );
 1267+ return false;
 1268+ }
 1269+
 1270+ // all other fields validated with validator js
 1271+ var formValid = _this.$form.valid();
 1272+ return titleInputValid && formValid;
 1273+
 1274+ // categories are assumed valid
 1275+
 1276+ // the license, if any
 1277+
 1278+ // pop open the 'more-options' if the date is bad
 1279+ // the date
 1280+
 1281+ // location?
 1282+ },
 1283+
 1284+
 1285+
 1286+ /**
 1287+ * toggles whether we use the 'macro' deed or our own
 1288+ */
 1289+ useCustomDeedChooser: function() {
 1290+ var _this = this;
 1291+ _this.copyrightInfoFieldset.show();
 1292+ _this.upload.wizardDeedChooser = _this.upload.deedChooser;
 1293+ _this.upload.deedChooser = new mw.UploadWizardDeedChooser(
 1294+ _this.deedDiv,
 1295+ [ new mw.UploadWizardDeedOwnWork(),
 1296+ new mw.UploadWizardDeedThirdParty() ]
 1297+ );
 1298+ },
 1299+
 1300+ /**
 1301+ * Sets the filename from the title plus this upload's extension.
 1302+ */
 1303+ setFilenameFromTitle: function() {
 1304+ var _this = this;
 1305+
 1306+ _this.filename = wgFormattedNamespaces[wgNamespaceIds['file']] + ':' + _this.getFilenameFromTitle();
 1307+ $j( '#mwe-upwiz-details-filename' ).text( _this.filename );
 1308+
 1309+ },
 1310+
 1311+ /**
 1312+ * Gets a filename from the human readable title, using upload's extension.
 1313+ * @return Filename
 1314+ */
 1315+ getFilenameFromTitle: function() {
 1316+ var _this = this;
 1317+ var name = $j( _this.titleInput ).val();
 1318+ return mw.UploadWizardUtil.pathToTitle( name ) + '.' + _this.upload.extension;
 1319+ },
 1320+
 1321+
 1322+ /**
 1323+ * show file destination field as "busy" while checking
 1324+ * @param busy boolean true = show busy-ness, false = remove
 1325+ */
 1326+ toggleDestinationBusy: function ( busy ) {
 1327+ var _this = this;
 1328+ if (busy) {
 1329+ _this.titleInput.addClass( "busy" );
 1330+ $j( _this.titleInput ).data( 'valid', undefined );
 1331+ } else {
 1332+ _this.titleInput.removeClass( "busy" );
 1333+ }
 1334+ },
 1335+
 1336+ /**
 1337+ * Process the result of a destination filename check.
 1338+ * See mw.DestinationChecker.js for documentation of result format
 1339+ * XXX would be simpler if we created all these divs in the DOM and had a more jquery-friendly way of selecting
 1340+ * attrs. Instead we create & destroy whole interface each time. Won't someone think of the DOM elements?
 1341+ * @param result
 1342+ */
 1343+ processDestinationCheck: function( result ) {
 1344+ var _this = this;
 1345+
 1346+ if ( result.isUnique ) {
 1347+ $j( _this.titleInput ).data( 'valid', true );
 1348+ _this.$form.find( 'label[for=' + _this.titleId + ']' ).hide().empty();
 1349+ _this.ignoreWarningsInput = undefined;
 1350+ return;
 1351+ }
 1352+
 1353+ $j( _this.titleInput ).data( 'valid', false );
 1354+
 1355+ // result is NOT unique
 1356+ var title = mw.UploadWizardUtil.fileTitleToHumanTitle( result.title );
 1357+ /* var img = result.img;
 1358+ var href = result.href; */
 1359+
 1360+ _this.$form.find( 'label[for=' + _this.titleId + ']' )
 1361+ .html( gM( 'mwe-upwiz-fileexists-replace', title ) )
 1362+ .show();
 1363+
 1364+ /* temporarily commenting out the full thumbnail etc. thing. For now, we just want the user to change
 1365+ to a different name
 1366+ _this.ignoreWarningsInput = $j("<input />").attr( { type: 'checkbox', name: 'ignorewarnings' } );
 1367+ var $fileAlreadyExists = $j('<div />')
 1368+ .append(
 1369+ gM( 'mwe-upwiz-fileexists',
 1370+ $j('<a />')
 1371+ .attr( { target: '_new', href: href } )
 1372+ .text( title )
 1373+ ),
 1374+ $j('<br />'),
 1375+ _this.ignoreWarningsInput,
 1376+ gM('mwe-upwiz-overwrite')
 1377+ );
 1378+
 1379+ var $imageLink = $j('<a />')
 1380+ .addClass( 'image' )
 1381+ .attr( { target: '_new', href: href } )
 1382+ .append(
 1383+ $j( '<img />')
 1384+ .addClass( 'thumbimage' )
 1385+ .attr( {
 1386+ 'width' : img.thumbwidth,
 1387+ 'height' : img.thumbheight,
 1388+ 'border' : 0,
 1389+ 'src' : img.thumburl,
 1390+ 'alt' : title
 1391+ } )
 1392+ );
 1393+
 1394+ var $imageCaption = $j( '<div />' )
 1395+ .addClass( 'thumbcaption' )
 1396+ .append(
 1397+ $j('<div />')
 1398+ .addClass( "magnify" )
 1399+ .append(
 1400+ $j('<a />' )
 1401+ .addClass( 'internal' )
 1402+ .attr( {
 1403+ 'title' : gM('mwe-upwiz-thumbnail-more'),
 1404+ 'href' : href
 1405+ } ),
 1406+
 1407+ $j( '<img />' )
 1408+ .attr( {
 1409+ 'border' : 0,
 1410+ 'width' : 15,
 1411+ 'height' : 11,
 1412+ 'src' : mw.UploadWizard.config[ 'images_path' ] + 'magnify-clip.png'
 1413+ } ),
 1414+
 1415+ $j('<span />')
 1416+ .html( gM( 'mwe-fileexists-thumb' ) )
 1417+ )
 1418+ );
 1419+
 1420+ $j( _this.titleErrorDiv ).html(
 1421+ $j('<span />') // dummy argument since .html() only takes one arg
 1422+ .append(
 1423+ $fileAlreadyExists,
 1424+ $j( '<div />' )
 1425+ .addClass( 'thumb tright' )
 1426+ .append(
 1427+ $j( '<div />' )
 1428+ .addClass( 'thumbinner' )
 1429+ .css({
 1430+ 'width' : ( parseInt( img.thumbwidth ) + 2 ) + 'px;'
 1431+ })
 1432+ .append(
 1433+ $imageLink,
 1434+ $imageCaption
 1435+ )
 1436+ )
 1437+ )
 1438+ ).show();
 1439+ */
 1440+
 1441+
 1442+ },
 1443+
 1444+ /**
 1445+ * Do anything related to a change in the number of descriptions
 1446+ */
 1447+ recountDescriptions: function() {
 1448+ var _this = this;
 1449+ // if there is some maximum number of descriptions, deal with that here
 1450+ $j( _this.descriptionAdder ).html( gM( 'mwe-upwiz-desc-add-' + ( _this.descriptions.length === 0 ? '0' : 'n' ) ) );
 1451+ },
 1452+
 1453+
 1454+ /**
 1455+ * Add a new description
 1456+ */
 1457+ addDescription: function( required, languageCode ) {
 1458+ var _this = this;
 1459+ if ( typeof required === 'undefined' ) {
 1460+ required = false;
 1461+ }
 1462+
 1463+ if ( typeof languageCode === 'undefined' ) {
 1464+ languageCode = mw.LanguageUpWiz.UNKNOWN;
 1465+ }
 1466+
 1467+ var description = new mw.UploadWizardDescription( languageCode, required );
 1468+
 1469+ if ( ! required ) {
 1470+ $j( description.div ).append(
 1471+ $j.fn.removeCtrl( 'mwe-upwiz-remove-description', function() { _this.removeDescription( description ); } )
 1472+ );
 1473+ }
 1474+
 1475+ $j( _this.descriptionsDiv ).append( description.div );
 1476+
 1477+ // must defer adding rules until it's in a form
 1478+ // sigh, this would be simpler if we refactored to be more jquery style, passing DOM element downward
 1479+ description.addValidationRules( required );
 1480+
 1481+ _this.descriptions.push( description );
 1482+ _this.recountDescriptions();
 1483+ },
 1484+
 1485+ /**
 1486+ * Remove a description
 1487+ * @param description
 1488+ */
 1489+ removeDescription: function( description ) {
 1490+ var _this = this;
 1491+ $j( description.div ).remove();
 1492+ mw.UploadWizardUtil.removeItem( _this.descriptions, description );
 1493+ _this.recountDescriptions();
 1494+ },
 1495+
 1496+ /**
 1497+ * Display an error with details
 1498+ * XXX this is a lot like upload ui's error -- should merge
 1499+ */
 1500+ error: function() {
 1501+ var _this = this;
 1502+ var args = Array.prototype.slice.call( arguments ); // copies arguments into a real array
 1503+ var msg = 'mwe-upwiz-upload-error-' + args[0];
 1504+ $j( _this.errorDiv ).append( $j( '<p class="mwe-upwiz-upload-error">' + gM( msg, args.slice( 1 ) ) + '</p>' ) );
 1505+ // apply a error style to entire did
 1506+ $j( _this.div ).addClass( 'mwe-upwiz-upload-error' );
 1507+ $j( _this.dataDiv ).hide();
 1508+ $j( _this.errorDiv ).show();
 1509+ },
 1510+
 1511+ /**
 1512+ * Given the API result pull some info into the form ( for instance, extracted from EXIF, desired filename )
 1513+ * @param result Upload API result object
 1514+ */
 1515+ populate: function() {
 1516+ var _this = this;
 1517+ mw.log( "populating details from upload" );
 1518+ _this.upload.setThumbnail( _this.thumbnailDiv );
 1519+ _this.prefillDate();
 1520+ _this.prefillSource();
 1521+ _this.prefillAuthor();
 1522+ _this.prefillTitle();
 1523+ _this.prefillFilename();
 1524+ _this.prefillLocation();
 1525+ },
 1526+
 1527+ /**
 1528+ * Check if we got an EXIF date back; otherwise use today's date; and enter it into the details
 1529+ * XXX We ought to be using date + time here...
 1530+ * EXIF examples tend to be in ISO 8601, but the separators are sometimes things like colons, and they have lots of trailing info
 1531+ * (which we should actually be using, such as time and timezone)
 1532+ */
 1533+ prefillDate: function() {
 1534+ // XXX surely we have this function somewhere already
 1535+ function pad( n ) {
 1536+ return n < 10 ? "0" + n : n;
 1537+ }
 1538+
 1539+ var _this = this;
 1540+ var yyyyMmDdRegex = /^(\d\d\d\d)[:\/-](\d\d)[:\/-](\d\d)\D.*/;
 1541+ var dateObj;
 1542+ var metadata = _this.upload.imageinfo.metadata;
 1543+ $j.each([metadata.datetimeoriginal, metadata.datetimedigitized, metadata.datetime, metadata['date']],
 1544+ function( i, imageinfoDate ) {
 1545+ if ( ! mw.isEmpty( imageinfoDate ) ) {
 1546+ var matches = imageinfoDate.trim().match( yyyyMmDdRegex );
 1547+ if ( ! mw.isEmpty( matches ) ) {
 1548+ dateObj = new Date( parseInt( matches[1], 10 ),
 1549+ parseInt( matches[2], 10 ) - 1,
 1550+ parseInt( matches[3], 10 ) );
 1551+ return false; // break from $j.each
 1552+ }
 1553+ }
 1554+ }
 1555+ );
 1556+
 1557+ // if we don't have EXIF or other metadata, let's use "now"
 1558+ // XXX if we have FileAPI, it might be clever to look at file attrs, saved
 1559+ // in the upload object for use here later, perhaps
 1560+ if (typeof dateObj === 'undefined') {
 1561+ dateObj = new Date();
 1562+ }
 1563+ dateStr = dateObj.getUTCFullYear() + '-' + pad( dateObj.getUTCMonth() ) + '-' + pad( dateObj.getUTCDate() );
 1564+
 1565+ // ok by now we should definitely have a dateObj and a date string
 1566+ $j( _this.dateInput ).val( dateStr );
 1567+ $j( _this.dateDisplayInput ).datepicker( "setDate", dateObj );
 1568+ },
 1569+
 1570+ /**
 1571+ * Set the title of the thing we just uploaded, visibly
 1572+ * Note: the interface's notion of "filename" versus "title" is the opposite of MediaWiki
 1573+ */
 1574+ prefillTitle: function() {
 1575+ var _this = this;
 1576+ var titleExt = mw.UploadWizardUtil.titleToPath( _this.upload.originalFilename );
 1577+ var title = titleExt.replace( /\.\w+$/, '' );
 1578+ $j( _this.titleInput ).val( title );
 1579+ },
 1580+
 1581+ /**
 1582+ * Set the title of the thing we just uploaded, visibly
 1583+ * Note: the interface's notion of "filename" versus "title" is the opposite of MediaWiki
 1584+ */
 1585+ prefillFilename: function() {
 1586+ var _this = this;
 1587+ _this.setFilenameFromTitle();
 1588+ },
 1589+
 1590+ /**
 1591+ * Prefill location inputs (and/or scroll to position on map) from image info and metadata
 1592+ *
 1593+ * At least for my test images, the EXIF parser on MediaWiki is not giving back any data for
 1594+ * GPSLatitude, GPSLongitude, or GPSAltitudeRef. It is giving the lat/long Refs, the Altitude, and the MapDatum
 1595+ * So, this is broken until we fix MediaWiki's parser, OR, parse it ourselves somehow
 1596+ *
 1597+ * in Image namespace
 1598+ * GPSTag Long ??
 1599+ *
 1600+ * in GPSInfo namespace
 1601+ * GPSVersionID byte* 2000 = 2.0.0.0
 1602+ * GPSLatitude rational
 1603+ * GPSLatitudeRef ascii (N | S) or North | South
 1604+ * GPSLongitude rational
 1605+ * GPSLongitudeRef ascii (E | W) or East | West
 1606+ * GPSAltitude rational
 1607+ * GPSAltitudeRef byte (0 | 1) above or below sea level
 1608+ * GPSImgDirection rational
 1609+ * GPSImgDirectionRef ascii (M | T) magnetic or true north
 1610+ * GPSMapDatum ascii "WGS-84" is the standard
 1611+ *
 1612+ * A 'rational' is a string like this:
 1613+ * "53/1 0/1 201867/4096" --> 53 deg 0 min 49.284 seconds
 1614+ * "2/1 11/1 64639/4096" --> 2 deg 11 min 15.781 seconds
 1615+ * "122/1" -- 122 m (altitude)
 1616+ */
 1617+ prefillLocation: function() {
 1618+ var _this = this;
 1619+ var metadata = _this.upload.imageinfo.metadata;
 1620+ if (metadata === undefined) {
 1621+ return;
 1622+ }
 1623+
 1624+
 1625+ },
 1626+
 1627+ /**
 1628+ * Given a decimal latitude and longitude, return filled out {{Location}} template
 1629+ * @param latitude decimal latitude ( -90.0 >= n >= 90.0 ; south = negative )
 1630+ * @param longitude decimal longitude ( -180.0 >= n >= 180.0 ; west = negative )
 1631+ * @param scale (optional) how rough the geocoding is.
 1632+ * @param heading (optional) what direction the camera is pointing in. (decimal 0.0-360.0, 0 = north, 90 = E)
 1633+ * @return string with WikiText which will geotag this record
 1634+ */
 1635+ coordsToWikiText: function(latitude, longitude, scale, heading) {
 1636+ //Wikipedia
 1637+ //http://en.wikipedia.org/wiki/Wikipedia:WikiProject_Geographical_coordinates#Parameters
 1638+ // http://en.wikipedia.org/wiki/Template:Coord
 1639+ //{{coord|61.1631|-149.9721|type:landmark_globe:earth_region:US-AK_scale:150000_source:gnis|name=Kulis Air National Guard Base}}
 1640+
 1641+ //Wikimedia Commons
 1642+ //{{Coor dms|41|19|20.4|N|19|38|36.7|E}}
 1643+ //{{Location}}
 1644+
 1645+ },
 1646+
 1647+ /**
 1648+ * If there is a way to figure out source from image info, do so here
 1649+ * XXX user pref?
 1650+ */
 1651+ prefillSource: function() {
 1652+ // we have no way to do this AFAICT
 1653+ },
 1654+
 1655+ /**
 1656+ * Prefill author (such as can be determined) from image info and metadata
 1657+ * XXX user pref?
 1658+ */
 1659+ prefillAuthor: function() {
 1660+ var _this = this;
 1661+ if (_this.upload.imageinfo.metadata.author !== undefined) {
 1662+ $j( _this.authorInput ).val( _this.upload.imageinfo.metadata.author );
 1663+ }
 1664+
 1665+ },
 1666+
 1667+ /**
 1668+ * Prefill license (such as can be determined) from image info and metadata
 1669+ * XXX user pref?
 1670+ */
 1671+ prefillLicense: function() {
 1672+ var _this = this;
 1673+ var copyright = _this.upload.imageinfo.metadata.copyright;
 1674+ if (copyright !== undefined) {
 1675+ if (copyright.match(/\bcc-by-sa\b/i)) {
 1676+ alert("unimplemented cc-by-sa in prefillLicense");
 1677+ // XXX set license to be that CC-BY-SA
 1678+ } else if (copyright.match(/\bcc-by\b/i)) {
 1679+ alert("unimplemented cc-by in prefillLicense");
 1680+ // XXX set license to be that
 1681+ } else if (copyright.match(/\bcc-zero\b/i)) {
 1682+ alert("unimplemented cc-zero in prefillLicense");
 1683+ // XXX set license to be that
 1684+ // XXX any other licenses we could guess from copyright statement
 1685+ } else {
 1686+ $j( _this.licenseInput ).val( copyright );
 1687+ }
 1688+ }
 1689+ // if we still haven't set a copyright use the user's preferences
 1690+ },
 1691+
 1692+
 1693+ /**
 1694+ *
 1695+ showErrors: function() {
 1696+ var _this = this;
 1697+ $j.each( _this.errors, function() {
 1698+
 1699+ } );
 1700+ },
 1701+ */
 1702+
 1703+ /**
 1704+ * Convert entire details for this file into wikiText, which will then be posted to the file
 1705+ * XXX there is a WikiText sanitizer in use on UploadForm -- use that here, or port it
 1706+ * @return wikitext representing all details
 1707+ */
 1708+ getWikiText: function() {
 1709+ var _this = this;
 1710+
 1711+ // if invalid, should produce side effects in the form
 1712+ // instructing user to fix.
 1713+ if ( ! _this.valid() ) {
 1714+ return null;
 1715+ }
 1716+
 1717+ wikiText = '';
 1718+
 1719+
 1720+ // http://commons.wikimedia.org / wiki / Template:Information
 1721+
 1722+ // can we be more slick and do this with maps, applys, joins?
 1723+ var information = {
 1724+ 'description' : '', // {{lang|description in lang}}* required
 1725+ 'date' : '', // YYYY, YYYY-MM, or YYYY-MM-DD required - use jquery but allow editing, then double check for sane date.
 1726+ 'source' : '', // {{own}} or wikitext optional
 1727+ 'author' : '', // any wikitext, but particularly {{Creator:Name Surname}} required
 1728+ 'permission' : '', // leave blank unless OTRS pending; by default will be "see below" optional
 1729+ 'other_versions' : '', // pipe separated list, other versions optional
 1730+ 'other_fields' : '' // ??? additional table fields
 1731+ };
 1732+
 1733+ // sanity check the descriptions -- do not have two in the same lang
 1734+ // all should be a known lang
 1735+ if ( _this.descriptions.length === 0 ) {
 1736+ alert("something has gone horribly wrong, unimplemented error check for zero descriptions");
 1737+ // XXX ruh roh
 1738+ // we should not even allow them to press the button ( ? ) but then what about the queue...
 1739+ }
 1740+ $j.each( _this.descriptions, function( i, desc ) {
 1741+ information['description'] += desc.getWikiText();
 1742+ } );
 1743+
 1744+ // XXX add a sanity check here for good date
 1745+ information['date'] = $j( _this.dateInput ).val().trim();
 1746+
 1747+ var deed = _this.upload.deedChooser.deed;
 1748+
 1749+ information['source'] = deed.getSourceWikiText();
 1750+
 1751+ information['author'] = deed.getAuthorWikiText();
 1752+
 1753+ var info = '';
 1754+ for ( var key in information ) {
 1755+ info += '|' + key + '=' + information[key] + "\n";
 1756+ }
 1757+
 1758+ wikiText += "=={{int:filedesc}}==\n";
 1759+
 1760+ wikiText += '{{Information\n' + info + '}}\n';
 1761+
 1762+ // add a location template if possible
 1763+
 1764+ // add an "anything else" template if needed
 1765+ var otherInfoWikiText = $j( _this.otherInformationInput ).val().trim();
 1766+ if ( ! mw.isEmpty( otherInfoWikiText ) ) {
 1767+ wikiText += "=={{int:otherinfo}}==\n";
 1768+ wikiText += otherInfoWikiText;
 1769+ }
 1770+
 1771+ wikiText += "=={{int:license-header}}==\n";
 1772+
 1773+ // in the other implementations, category text follows immediately after license text. This helps
 1774+ // group categories together, maybe?
 1775+ wikiText += deed.getLicenseWikiText() + _this.div.find( '.categoryInput' ).get(0).getWikiText();
 1776+
 1777+
 1778+ return wikiText;
 1779+ },
 1780+
 1781+ /**
 1782+ * Check if we are ready to post wikitext
 1783+ deedValid: function() {
 1784+ var _this = this;
 1785+ return _this.upload.deedChooser.deed.valid();
 1786+
 1787+ // somehow, all the various issues discovered with this upload should be present in a single place
 1788+ // where we can then check on
 1789+ // perhaps as simple as _this.issues or _this.agenda
 1790+ },
 1791+ */
 1792+
 1793+ /**
 1794+ * Post wikitext as edited here, to the file
 1795+ * XXX This should be split up -- one part should get wikitext from the interface here, and the ajax call
 1796+ * should be be part of upload
 1797+ */
 1798+ submit: function( endCallback ) {
 1799+ var _this = this;
 1800+
 1801+
 1802+ // are we okay to submit?
 1803+ // all necessary fields are ready
 1804+ // check descriptions
 1805+ // the filename is in a sane state
 1806+ var desiredFilename = _this.filename;
 1807+ shouldRename = ( desiredFilename != _this.upload.title );
 1808+
 1809+ // XXX check state of details for okayness ( license selected, at least one desc, sane filename )
 1810+ var wikiText = _this.getWikiText();
 1811+ mw.log( wikiText );
 1812+
 1813+ var params = {
 1814+ action: 'edit',
 1815+ // XXX this is problematic, if the upload wizard is idle for a long time the token expires.
 1816+ // should obtain token just before uploading
 1817+ token: mw.UploadWizard.config[ 'token' ],
 1818+ title: _this.upload.title,
 1819+ // section: 0, ?? causing issues?
 1820+ text: wikiText,
 1821+ summary: "User edited page with " + mw.UploadWizard.userAgent,
 1822+ // notminor: 1,
 1823+ // basetimestamp: _this.upload.imageinfo.timestamp, ( conflicts? )
 1824+ nocreate: 1
 1825+ };
 1826+
 1827+ var finalCallback = function() {
 1828+ endCallback();
 1829+ _this.completeDetailsSubmission();
 1830+ };
 1831+
 1832+ mw.log( "editing!" );
 1833+ mw.log( params );
 1834+ var callback = function( result ) {
 1835+ mw.log( result );
 1836+ mw.log( "successful edit" );
 1837+ if ( shouldRename ) {
 1838+ _this.rename( desiredFilename, finalCallback );
 1839+ } else {
 1840+ finalCallback();
 1841+ }
 1842+ };
 1843+
 1844+ _this.upload.state = 'submitting-details';
 1845+ mw.getJSON( params, callback );
 1846+ },
 1847+
 1848+ /**
 1849+ * Rename the file
 1850+ *
 1851+ * THIS MAY NOT WORK ON ALL WIKIS. for instance, on Commons, it may be that only admins can move pages. This is another example of how
 1852+ * we need an "incomplete" upload status
 1853+ * we are presuming this File page is brand new, so let's not bother with the whole redirection deal. ('noredirect')
 1854+ *
 1855+ * use _this.ignoreWarningsInput (if it exists) to check if we can blithely move the file or if we have a problem if there
 1856+ * is a file by that name already there
 1857+ *
 1858+ * @param filename to rename this file to
 1859+ */
 1860+ rename: function( title, endCallback ) {
 1861+ var _this = this;
 1862+ mw.log("renaming!");
 1863+ params = {
 1864+ action: 'move',
 1865+ from: _this.upload.title,
 1866+ to: title,
 1867+ reason: "User edited page with " + mw.UploadWizard.userAgent,
 1868+ movetalk: '',
 1869+ noredirect: '', // presume it's too new
 1870+ token: mw.UploadWizard.config[ 'token' ]
 1871+ };
 1872+ mw.log(params);
 1873+ // despite the name, getJSON magically changes this into a POST request (it has a list of methods and what they require).
 1874+ mw.getJSON( params, function( data ) {
 1875+ // handle errors later
 1876+ // possible error data: { code = 'missingtitle' } -- orig filename not there
 1877+ // and many more
 1878+
 1879+ // which should match our request.
 1880+ // we should update the current upload filename
 1881+ // then call the uploadwizard with our progress
 1882+
 1883+ // success is
 1884+ // move = from : ..., reason : ..., redirectcreated : ..., to : ....
 1885+ if (data !== undefined && data.move !== undefined && data.move.to !== undefined) {
 1886+ _this.upload.title = data.move.to;
 1887+ _this.refreshImageInfo( _this.upload, _this.upload.title, endCallback );
 1888+ }
 1889+ } );
 1890+ },
 1891+
 1892+ /**
 1893+ * Get new image info, for instance, after we renamed an image
 1894+ *
 1895+ * @param upload an UploadWizardUpload object
 1896+ * @param title title to look up remotely
 1897+ * @param endCallback execute upon completion
 1898+ */
 1899+ refreshImageInfo: function( upload, title, endCallback ) {
 1900+ var params = {
 1901+ 'titles': title,
 1902+ 'prop': 'imageinfo',
 1903+ 'iiprop': 'timestamp|url|user|size|sha1|mime|metadata'
 1904+ };
 1905+ // XXX timeout callback?
 1906+ mw.getJSON( params, function( data ) {
 1907+ if ( data && data.query && data.query.pages ) {
 1908+ if ( ! data.query.pages[-1] ) {
 1909+ for ( var page_id in data.query.pages ) {
 1910+ var page = data.query.pages[ page_id ];
 1911+ if ( ! page.imageinfo ) {
 1912+ alert("unimplemented error check, missing imageinfo");
 1913+ // XXX not found? error
 1914+ } else {
 1915+ upload.extractImageInfo( page.imageinfo[0] );
 1916+ }
 1917+ }
 1918+ }
 1919+ }
 1920+ endCallback();
 1921+ } );
 1922+ },
 1923+
 1924+ completeDetailsSubmission: function() {
 1925+ var _this = this;
 1926+ _this.upload.state = 'complete';
 1927+ // de-spinnerize
 1928+ _this.upload.detailsProgress = 1.0;
 1929+ },
 1930+
 1931+ dateInputCount: 0
 1932+
 1933+
 1934+};
 1935+
 1936+
 1937+/**
 1938+ * Object that reperesents the entire multi-step Upload Wizard
 1939+ */
 1940+mw.UploadWizard = function( config ) {
 1941+
 1942+ this.uploads = [];
 1943+
 1944+ // making a sort of global for now, should be done by passing in config or fragments of config when needed
 1945+ // elsewhere
 1946+ mw.UploadWizard.config = config;
 1947+
 1948+ // XXX need a robust way of defining default config
 1949+ this.maxUploads = mw.UploadWizard.config[ 'maxUploads' ] || 10;
 1950+ this.maxSimultaneousConnections = mw.UploadWizard.config[ 'maxSimultaneousConnections' ] || 2;
 1951+
 1952+};
 1953+
 1954+
 1955+mw.UploadWizard.userAgent = "UploadWizard (alpha)";
 1956+
 1957+
 1958+mw.UploadWizard.prototype = {
 1959+ stepNames: [ 'file', 'deeds', 'details', 'thanks' ],
 1960+ currentStepName: undefined,
 1961+
 1962+ /*
 1963+ // list possible upload handlers in order of preference
 1964+ // these should all be in the mw.* namespace
 1965+ // hardcoded for now. maybe some registry system might work later, like, all
 1966+ // things which subclass off of UploadHandler
 1967+ uploadHandlers: [
 1968+ 'FirefoggUploadHandler',
 1969+ 'XhrUploadHandler',
 1970+ 'ApiIframeUploadHandler',
 1971+ 'SimpleUploadHandler',
 1972+ 'NullUploadHandler'
 1973+ ],
 1974+
 1975+ * We can use various UploadHandlers based on the browser's capabilities. Let's pick one.
 1976+ * For example, the ApiUploadHandler should work just about everywhere, but XhrUploadHandler
 1977+ * allows for more fine-grained upload progress
 1978+ * @return valid JS upload handler class constructor function
 1979+ getUploadHandlerClass: function() {
 1980+ // return mw.MockUploadHandler;
 1981+ return mw.ApiUploadHandler;
 1982+ var _this = this;
 1983+ for ( var i = 0; i < uploadHandlers.length; i++ ) {
 1984+ var klass = mw[uploadHandlers[i]];
 1985+ if ( klass != undefined && klass.canRun( this.config )) {
 1986+ return klass;
 1987+ }
 1988+ }
 1989+ // this should never happen; NullUploadHandler should always work
 1990+ return null;
 1991+ },
 1992+ */
 1993+
 1994+ /**
 1995+ * Reset the entire interface so we can upload more stuff
 1996+ * Depending on whether we split uploading / detailing, it may actually always be as simple as loading a URL
 1997+ */
 1998+ reset: function() {
 1999+ window.location.reload();
 2000+ },
 2001+
 2002+
 2003+ /**
 2004+ * create the basic interface to make an upload in this div
 2005+ * @param div The div in the DOM to put all of this into.
 2006+ */
 2007+ createInterface: function( selector ) {
 2008+ var _this = this;
 2009+ var div = $j( selector ).get(0);
 2010+ div.innerHTML =
 2011+ // the arrow steps
 2012+ '<ul id="mwe-upwiz-steps">'
 2013+ + '<li id="mwe-upwiz-step-file"><div>' + gM('mwe-upwiz-step-file') + '</div></li>'
 2014+ + '<li id="mwe-upwiz-step-deeds"><div>' + gM('mwe-upwiz-step-deeds') + '</div></li>'
 2015+ + '<li id="mwe-upwiz-step-details"><div>' + gM('mwe-upwiz-step-details') + '</div></li>'
 2016+ + '<li id="mwe-upwiz-step-thanks"><div>' + gM('mwe-upwiz-step-thanks') + '</div></li>'
 2017+ + '</ul>'
 2018+
 2019+ // the individual steps, all at once
 2020+ + '<div id="mwe-upwiz-content">'
 2021+
 2022+ + '<div class="mwe-upwiz-stepdiv ui-helper-clearfix" id="mwe-upwiz-stepdiv-file">'
 2023+ + '<div id="mwe-upwiz-intro">' + gM('mwe-upwiz-intro') + '</div>'
 2024+ + '<div id="mwe-upwiz-files">'
 2025+ + '<div id="mwe-upwiz-upload-ctrls" class="mwe-upwiz-file">'
 2026+ + '<div id="mwe-upwiz-add-file-container" class="mwe-upwiz-add-files-0">'
 2027+ + '<a id="mwe-upwiz-add-file">' + gM("mwe-upwiz-add-file-0") + '</a>'
 2028+ + '</div>'
 2029+ + '</div>'
 2030+ + '<div id="mwe-upwiz-progress" class="ui-helper-clearfix"></div>'
 2031+ + '</div>'
 2032+ + '<div class="mwe-upwiz-buttons" style="display: none"/>'
 2033+ + '<button class="mwe-upwiz-button-next" />'
 2034+ + '</div>'
 2035+ + '</div>'
 2036+
 2037+ + '<div class="mwe-upwiz-stepdiv" id="mwe-upwiz-stepdiv-deeds">'
 2038+ + '<div id="mwe-upwiz-deeds-intro"></div>'
 2039+ + '<div id="mwe-upwiz-deeds-thumbnails" class="ui-helper-clearfix"></div>'
 2040+ + '<div id="mwe-upwiz-deeds" class="ui-helper-clearfix"></div>'
 2041+ + '<div id="mwe-upwiz-deeds-custom" class="ui-helper-clearfix"></div>'
 2042+ + '<div class="mwe-upwiz-buttons"/>'
 2043+ + '<button class="mwe-upwiz-button-next" />'
 2044+ + '</div>'
 2045+ + '</div>'
 2046+
 2047+ + '<div class="mwe-upwiz-stepdiv" id="mwe-upwiz-stepdiv-details">'
 2048+ + '<div id="mwe-upwiz-macro">'
 2049+ + '<div id="mwe-upwiz-macro-progress" class="ui-helper-clearfix"></div>'
 2050+ + '<div id="mwe-upwiz-macro-choice">'
 2051+ + '<div>' + gM( 'mwe-upwiz-details-intro' ) + '</div>'
 2052+ + '</div>'
 2053+ + '<div id="mwe-upwiz-macro-files"></div>'
 2054+ + '</div>'
 2055+ + '<div class="mwe-upwiz-buttons"/>'
 2056+ + '<button class="mwe-upwiz-button-next" />'
 2057+ + '</div>'
 2058+ + '</div>'
 2059+
 2060+ + '<div class="mwe-upwiz-stepdiv" id="mwe-upwiz-stepdiv-thanks">'
 2061+ + '<div id="mwe-upwiz-thanks"></div>'
 2062+ + '<div class="mwe-upwiz-buttons"/>'
 2063+ + '<button class="mwe-upwiz-button-begin"></button>'
 2064+ + '<br/><button class="mwe-upwiz-button-home"></button>'
 2065+ + '</div>'
 2066+ + '</div>'
 2067+
 2068+ + '</div>'
 2069+
 2070+ + '<div class="mwe-upwiz-clearing"></div>';
 2071+
 2072+ $j( '#mwe-upwiz-steps' )
 2073+ .addClass( 'ui-helper-clearfix ui-state-default ui-widget ui-helper-reset ui-helper-clearfix' )
 2074+ .arrowSteps();
 2075+
 2076+ $j( '.mwe-upwiz-button-home' )
 2077+ .append( gM( 'mwe-upwiz-home' ) )
 2078+ .click( function() { window.location.href = '/'; } );
 2079+
 2080+ $j( '.mwe-upwiz-button-begin' )
 2081+ .append( gM( 'mwe-upwiz-upload-another' ) )
 2082+ .click( function() { _this.reset(); } );
 2083+
 2084+
 2085+
 2086+ // handler for next button
 2087+ $j( '#mwe-upwiz-stepdiv-file .mwe-upwiz-button-next')
 2088+ .append( gM( 'mwe-upwiz-next-file' ) )
 2089+ .click( function() {
 2090+ // check if there is an upload at all
 2091+ if ( _this.uploads.length === 0 ) {
 2092+ alert( gM( 'mwe-upwiz-file-need-file' ) );
 2093+ return;
 2094+ }
 2095+
 2096+ _this.removeEmptyUploads();
 2097+ _this.startUploads( function() {
 2098+
 2099+ // okay all uploads are done, we're ready to go to the next step
 2100+
 2101+ // do some last minute prep before advancing to the DEEDS page
 2102+
 2103+ // these deeds are standard
 2104+ var deeds = [
 2105+ new mw.UploadWizardDeedOwnWork( _this.uploads.length ),
 2106+ new mw.UploadWizardDeedThirdParty( _this.uploads.length )
 2107+ ];
 2108+
 2109+ // if we have multiple uploads, also give them the option to set
 2110+ // licenses individually
 2111+ if ( _this.uploads.length > 1 ) {
 2112+ var customDeed = $j.extend( new mw.UploadWizardDeed(), {
 2113+ valid: function() { return true; },
 2114+ name: 'custom'
 2115+ } );
 2116+ deeds.push( customDeed );
 2117+ }
 2118+
 2119+ _this.deedChooser = new mw.UploadWizardDeedChooser(
 2120+ '#mwe-upwiz-deeds',
 2121+ deeds,
 2122+ _this.uploads.length );
 2123+
 2124+ $j( '<div>' ).html( gM( 'mwe-upwiz-deeds-macro-prompt', _this.uploads.length ) )
 2125+ .insertBefore ( _this.deedChooser.$selector.find( '.mwe-upwiz-deed-ownwork' ) );
 2126+
 2127+ if ( _this.uploads.length > 1 ) {
 2128+ $j( '<div style="margin-top: 1em">' ).html( gM( 'mwe-upwiz-deeds-custom-prompt' ) )
 2129+ .insertBefore( _this.deedChooser.$selector.find( '.mwe-upwiz-deed-custom' ) );
 2130+ }
 2131+
 2132+
 2133+ _this.moveToStep( 'deeds' );
 2134+
 2135+ } );
 2136+ } );
 2137+
 2138+
 2139+ // DEEDS div
 2140+
 2141+ $j( '#mwe-upwiz-deeds-intro' ).html( gM( 'mwe-upwiz-deeds-intro' ) );
 2142+
 2143+ $j( '#mwe-upwiz-stepdiv-deeds .mwe-upwiz-button-next')
 2144+ .append( gM( 'mwe-upwiz-next-deeds' ) )
 2145+ .click( function() {
 2146+ // validate has the side effect of notifying the user of problems, or removing existing notifications.
 2147+ // if returns false, you can assume there are notifications in the interface.
 2148+ if ( _this.deedChooser.valid() ) {
 2149+
 2150+ var lastUploadIndex = _this.uploads.length - 1;
 2151+
 2152+ $j.each( _this.uploads, function( i, upload ) {
 2153+
 2154+ if ( _this.deedChooser.deed.name == 'custom' ) {
 2155+ upload.details.useCustomDeedChooser();
 2156+ } else {
 2157+ upload.deedChooser = _this.deedChooser;
 2158+ }
 2159+
 2160+ /* put a border below every details div except the last */
 2161+ if ( i < lastUploadIndex ) {
 2162+ upload.details.div.css( 'border-bottom', '1px solid #e0e0e0' );
 2163+ }
 2164+
 2165+ upload.details.titleInput.checkUnique();
 2166+ } );
 2167+
 2168+ _this.moveToStep( 'details' );
 2169+ }
 2170+ } );
 2171+
 2172+
 2173+ // DETAILS div
 2174+
 2175+ $j( '#mwe-upwiz-stepdiv-details .mwe-upwiz-button-next' )
 2176+ .append( gM( 'mwe-upwiz-next-details' ) )
 2177+ .click( function() {
 2178+ if ( _this.detailsValid() ) {
 2179+ _this.detailsSubmit( function() {
 2180+ _this.prefillThanksPage();
 2181+ _this.moveToStep( 'thanks' );
 2182+ } );
 2183+ }
 2184+ } );
 2185+
 2186+
 2187+
 2188+ // WIZARD
 2189+
 2190+ // add one upload field to start (this is the big one that asks you to upload something)
 2191+ var upload = _this.newUpload( '#mwe-upwiz-add-file' );
 2192+
 2193+ // "select" the first step - highlight, make it visible, hide all others
 2194+ _this.moveToStep( 'file', function() {
 2195+ // XXX moving the file input doesn't seem to work at this point; we get its old position before
 2196+ // CSS is applied. Hence, using a timeout.
 2197+ // XXX using a timeout is lame, are there other options?
 2198+ // XXX Trevor suggests that using addClass() may queue stuff unnecessarily; use 'concrete' HTML
 2199+ setTimeout( function() {
 2200+ upload.ui.moveFileInputToCover( '#mwe-upwiz-add-file' );
 2201+ }, 300 );
 2202+ } );
 2203+
 2204+ },
 2205+
 2206+ /**
 2207+ * Advance one "step" in the wizard interface.
 2208+ * It is assumed that the previous step to the current one was selected.
 2209+ * We do not hide the tabs because this messes up certain calculations we'd like to make about dimensions, while elements are not
 2210+ * on screen. So instead we make the tabs zero height and, in CSS, they are already overflow hidden
 2211+ * @param selectedStepName
 2212+ * @param callback to do after layout is ready?
 2213+ */
 2214+ moveToStep: function( selectedStepName, callback ) {
 2215+ var _this = this;
 2216+ $j.each( _this.stepNames, function(i, stepName) {
 2217+
 2218+ // the step indicator
 2219+ var step = $j( '#mwe-upwiz-step-' + stepName );
 2220+
 2221+ // the step's contents
 2222+ var stepDiv = $j( '#mwe-upwiz-stepdiv-' + stepName );
 2223+
 2224+ if ( _this.currentStepName == stepName ) {
 2225+ stepDiv.hide();
 2226+ // we hide the old stepDivs because we are afraid of some z-index elements that may interfere with later tabs
 2227+ // this will break if we ever allow people to page back and forth.
 2228+ } else {
 2229+ if ( selectedStepName == stepName ) {
 2230+ stepDiv.maskSafeShow();
 2231+ } else {
 2232+ stepDiv.maskSafeHide( 1000 );
 2233+ }
 2234+ }
 2235+
 2236+ } );
 2237+
 2238+ $j( '#mwe-upwiz-steps' ).arrowStepsHighlight( '#mwe-upwiz-step-' + selectedStepName );
 2239+
 2240+ _this.currentStepName = selectedStepName;
 2241+
 2242+ $j.each( _this.uploads, function(i, upload) {
 2243+ upload.state = selectedStepName;
 2244+ } );
 2245+
 2246+ if ( callback ) {
 2247+ callback();
 2248+ }
 2249+ },
 2250+
 2251+ /**
 2252+ * add an Upload
 2253+ * we create the upload interface, a handler to transport it to the server,
 2254+ * and UI for the upload itself and the "details" at the second step of the wizard.
 2255+ * we don't yet add it to the list of uploads; that only happens when it gets a real file.
 2256+ * @return the new upload
 2257+ */
 2258+ newUpload: function() {
 2259+ var _this = this;
 2260+ if ( _this.uploads.length == _this.maxUploads ) {
 2261+ return false;
 2262+ }
 2263+
 2264+ var upload = new mw.UploadWizardUpload( _this, '#mwe-upwiz-files' );
 2265+ _this.uploadToAdd = upload;
 2266+
 2267+ upload.ui.moveFileInputToCover( '#mwe-upwiz-add-file' );
 2268+ // we bind to the ui div since unbind doesn't work for non-DOM objects
 2269+
 2270+ $j( upload.ui.div ).bind( 'filenameAccepted', function(e) { _this.updateFileCounts(); e.stopPropagation(); } );
 2271+ $j( upload.ui.div ).bind( 'removeUploadEvent', function(e) { _this.removeUpload( upload ); e.stopPropagation(); } );
 2272+ $j( upload.ui.div ).bind( 'filled', function(e) {
 2273+ mw.log( "filled! received!" );
 2274+ _this.newUpload();
 2275+ mw.log( "filled! new upload!" );
 2276+ _this.setUploadFilled(upload);
 2277+ mw.log( "filled! set upload filled!" );
 2278+ e.stopPropagation();
 2279+ mw.log( "filled! stop propagation!" );
 2280+ } );
 2281+ // XXX bind to some error state
 2282+
 2283+
 2284+ return upload;
 2285+ },
 2286+
 2287+ /**
 2288+ * When an upload is filled with a real file, accept it in the wizard's list of uploads
 2289+ * and set up some other interfaces
 2290+ * @param UploadWizardUpload
 2291+ */
 2292+ setUploadFilled: function( upload ) {
 2293+ var _this = this;
 2294+
 2295+ // XXX check if it has a file?
 2296+ _this.uploads.push( upload );
 2297+
 2298+ /* useful for making ids unique and so on */
 2299+ _this.uploadsSeen++;
 2300+ upload.index = _this.uploadsSeen;
 2301+
 2302+ _this.updateFileCounts();
 2303+
 2304+ upload.deedPreview = new mw.UploadWizardDeedPreview( upload );
 2305+
 2306+ // XXX do we really need to do this now? some things will even change after step 2.
 2307+ // legacy.
 2308+ // set up details
 2309+ upload.details = new mw.UploadWizardDetails( upload, $j( '#mwe-upwiz-macro-files' ) );
 2310+ },
 2311+
 2312+ /* increments with every upload */
 2313+ uploadsSeen: 0,
 2314+
 2315+ /**
 2316+ * Remove an upload from our array of uploads, and the HTML UI
 2317+ * We can remove the HTML UI directly, as jquery will just get the parent.
 2318+ * We need to grep through the array of uploads, since we don't know the current index.
 2319+ * We need to update file counts for obvious reasons.
 2320+ *
 2321+ * @param upload
 2322+ */
 2323+ removeUpload: function( upload ) {
 2324+ var _this = this;
 2325+ // remove the div that passed along the trigger
 2326+ var $div = $j( upload.ui.div );
 2327+ $div.unbind(); // everything
 2328+ // sexily fade away
 2329+ $div.fadeOut('fast', function() {
 2330+ $div.remove();
 2331+ // and do what we in the wizard need to do after an upload is removed
 2332+ mw.UploadWizardUtil.removeItem( _this.uploads, upload );
 2333+ _this.updateFileCounts();
 2334+ });
 2335+ },
 2336+
 2337+ /**
 2338+ * This is useful to clean out unused upload file inputs if the user hits GO.
 2339+ * We are using a second array to iterate, because we will be splicing the main one, _this.uploads
 2340+ */
 2341+ removeEmptyUploads: function() {
 2342+ var _this = this;
 2343+ var toRemove = [];
 2344+
 2345+ for ( var i = 0; i < _this.uploads.length; i++ ) {
 2346+ if ( mw.isEmpty( _this.uploads[i].ui.fileInputCtrl.value ) ) {
 2347+ toRemove.push( _this.uploads[i] );
 2348+ }
 2349+ }
 2350+
 2351+ for ( var j = 0; j < toRemove.length; j++ ) {
 2352+ toRemove[j].remove();
 2353+ }
 2354+ },
 2355+
 2356+ /**
 2357+ * Manage transitioning all of our uploads from one state to another -- like from "new" to "uploaded".
 2358+ *
 2359+ * @param beginState what state the upload should be in before starting.
 2360+ * @param progressState the state to set the upload to while it's doing whatever
 2361+ * @param endState the state to set the upload to after it's done whatever
 2362+ * @param starter function, taking single argument (upload) which starts the process we're interested in
 2363+ * @param endCallback function to call when all uploads are in the end state.
 2364+ */
 2365+ makeTransitioner: function( beginState, progressState, endState, starter, endCallback ) {
 2366+
 2367+ var _this = this;
 2368+
 2369+ var transitioner = function() {
 2370+ var uploadsToStart = _this.maxSimultaneousConnections;
 2371+ var endStateCount = 0;
 2372+ $j.each( _this.uploads, function(i, upload) {
 2373+ if ( upload.state == endState ) {
 2374+ endStateCount++;
 2375+ } else if ( upload.state == progressState ) {
 2376+ uploadsToStart--;
 2377+ } else if ( ( upload.state == beginState ) && ( uploadsToStart > 0 ) ) {
 2378+ starter( upload );
 2379+ uploadsToStart--;
 2380+ }
 2381+ } );
 2382+
 2383+ // build in a little delay even for the end state, so user can see progress bar in a complete state.
 2384+ var nextAction = ( endStateCount == _this.uploads.length ) ? endCallback : transitioner;
 2385+
 2386+ setTimeout( nextAction, _this.transitionerDelay );
 2387+ };
 2388+
 2389+ transitioner();
 2390+ },
 2391+
 2392+ transitionerDelay: 200, // milliseconds
 2393+
 2394+
 2395+ /**
 2396+ * Kick off the upload processes.
 2397+ * Does some precalculations, changes the interface to be less mutable, moves the uploads to a queue,
 2398+ * and kicks off a thread which will take from the queue.
 2399+ * @param endCallback - to execute when uploads are completed
 2400+ */
 2401+ startUploads: function( endCallback ) {
 2402+ var _this = this;
 2403+ // remove the upload button, and the add file button
 2404+ $j( '#mwe-upwiz-upload-ctrls' ).hide();
 2405+ $j( '#mwe-upwiz-add-file' ).hide();
 2406+
 2407+ var allowCloseWindow = $j().preventCloseWindow( {
 2408+ message: gM( 'mwe-prevent-close')
 2409+ } );
 2410+
 2411+
 2412+ var progressBar = new mw.GroupProgressBar( '#mwe-upwiz-progress',
 2413+ gM( 'mwe-upwiz-uploading' ),
 2414+ _this.uploads,
 2415+ 'transported',
 2416+ 'transportProgress',
 2417+ 'transportWeight' );
 2418+ progressBar.start();
 2419+
 2420+
 2421+ // remove ability to change files
 2422+ // ideally also hide the "button"... but then we require styleable file input CSS trickery
 2423+ // although, we COULD do this just for files already in progress...
 2424+
 2425+ // it might be interesting to just make this creational -- attach it to the dom element representing
 2426+ // the progress bar and elapsed time
 2427+ _this.makeTransitioner(
 2428+ 'new',
 2429+ 'transporting',
 2430+ 'transported',
 2431+ function( upload ) {
 2432+ upload.start();
 2433+ },
 2434+ function() {
 2435+ allowCloseWindow();
 2436+ $j().notify( gM( 'mwe-upwiz-files-complete' ) );
 2437+ endCallback();
 2438+ }
 2439+ );
 2440+ },
 2441+
 2442+
 2443+
 2444+ /**
 2445+ * Occurs whenever we need to update the interface based on how many files there are
 2446+ * Thhere is an uncounted upload, waiting to be used, which has a fileInput which covers the
 2447+ * "add an upload" button. This is absolutely positioned, so it needs to be moved if another upload was removed.
 2448+ * The uncounted upload is also styled differently between the zero and n files cases
 2449+ */
 2450+ updateFileCounts: function() {
 2451+ var _this = this;
 2452+
 2453+ if ( _this.uploads.length ) {
 2454+ $j( '#mwe-upwiz-upload-ctrl' ).removeAttr( 'disabled' );
 2455+ $j( '#mwe-upwiz-stepdiv-file .mwe-upwiz-buttons' ).show();
 2456+ $j( '#mwe-upwiz-add-file' ).html( gM( 'mwe-upwiz-add-file-n' ) );
 2457+ $j( '#mwe-upwiz-add-file-container' ).removeClass('mwe-upwiz-add-files-0');
 2458+ $j( '#mwe-upwiz-add-file-container' ).addClass('mwe-upwiz-add-files-n');
 2459+ $j( '#mwe-upwiz-files .mwe-upwiz-file.filled:odd' ).addClass( 'odd' );
 2460+ $j( '#mwe-upwiz-files .mwe-upwiz-file:filled:even' ).removeClass( 'odd' );
 2461+ } else {
 2462+ $j( '#mwe-upwiz-upload-ctrl' ).attr( 'disabled', 'disabled' );
 2463+ $j( '#mwe-upwiz-stepdiv-file .mwe-upwiz-buttons' ).hide();
 2464+ $j( '#mwe-upwiz-add-file' ).html( gM( 'mwe-upwiz-add-file-0' ) );
 2465+ $j( '#mwe-upwiz-add-file-container' ).addClass('mwe-upwiz-add-files-0');
 2466+ $j( '#mwe-upwiz-add-file-container' ).removeClass('mwe-upwiz-add-files-n');
 2467+ }
 2468+
 2469+ if ( _this.uploads.length < _this.maxUploads ) {
 2470+ $j( '#mwe-upwiz-add-file' ).removeAttr( 'disabled' );
 2471+ $j( _this.uploadToAdd.ui.div ).show();
 2472+ _this.uploadToAdd.ui.moveFileInputToCover( '#mwe-upwiz-add-file' );
 2473+ } else {
 2474+ $j( '#mwe-upwiz-add-file' ).attr( 'disabled', true );
 2475+ $j( _this.uploadToAdd.ui.div ).hide();
 2476+ }
 2477+
 2478+
 2479+ },
 2480+
 2481+
 2482+ /**
 2483+ * are all the details valid?
 2484+ * @return boolean
 2485+ */
 2486+ detailsValid: function() {
 2487+ var _this = this;
 2488+ var valid = true;
 2489+ $j.each( _this.uploads, function(i, upload) {
 2490+ valid &= upload.details.valid();
 2491+ } );
 2492+ return valid;
 2493+ },
 2494+
 2495+ /**
 2496+ * Submit all edited details and other metadata
 2497+ * Works just like startUploads -- parallel simultaneous submits with progress bar.
 2498+ */
 2499+ detailsSubmit: function( endCallback ) {
 2500+ var _this = this;
 2501+ // some details blocks cannot be submitted (for instance, identical file hash)
 2502+ _this.removeBlockedDetails();
 2503+
 2504+ // XXX validate all
 2505+
 2506+ // remove ability to edit details
 2507+ $j.each( _this.uploads, function( i, upload ) {
 2508+ upload.details.div.mask();
 2509+ upload.details.div.data( 'mask' ).loadingSpinner();
 2510+ } );
 2511+
 2512+ // add the upload progress bar, with ETA
 2513+ // add in the upload count
 2514+ _this.makeTransitioner(
 2515+ 'details',
 2516+ 'submitting-details',
 2517+ 'complete',
 2518+ function( upload ) {
 2519+ upload.details.submit( function() {
 2520+ upload.details.div.data( 'mask' ).html();
 2521+ } );
 2522+ },
 2523+ endCallback
 2524+ );
 2525+ },
 2526+
 2527+ /**
 2528+ * Removes(?) details that we can't edit for whatever reason -- might just advance them to a different state?
 2529+ */
 2530+ removeBlockedDetails: function() {
 2531+ // TODO
 2532+ },
 2533+
 2534+
 2535+ prefillThanksPage: function() {
 2536+ var _this = this;
 2537+
 2538+ $j( '#mwe-upwiz-thanks' )
 2539+ .append( $j( '<h3 style="text-align: center;">' ).append( gM( 'mwe-upwiz-thanks-intro' ) ),
 2540+ $j( '<p style="margin-bottom: 2em; text-align: center;">' )
 2541+ .append( gM( 'mwe-upwiz-thanks-explain', _this.uploads.length ) ) );
 2542+
 2543+ $j.each( _this.uploads, function(i, upload) {
 2544+ var thanksDiv = $j( '<div class="mwe-upwiz-thanks ui-helper-clearfix" />' );
 2545+ _this.thanksDiv = thanksDiv;
 2546+
 2547+ var thumbnailDiv = $j( '<div class="mwe-upwiz-thumbnail mwe-upwiz-thumbnail-side"></div>' );
 2548+ upload.setThumbnail( thumbnailDiv );
 2549+ thumbnailDiv.append( $j('<p/>').append(
 2550+ $j( '<a />' )
 2551+ .attr( { target: '_new',
 2552+ href: upload.imageinfo.descriptionurl } )
 2553+ .text( upload.title )
 2554+ ) );
 2555+
 2556+ thanksDiv.append( thumbnailDiv );
 2557+
 2558+ var thumbWikiText = "[[" + upload.title + "|thumb|Add caption here]]";
 2559+
 2560+ thanksDiv.append(
 2561+ $j( '<div class="mwe-upwiz-data"></div>' )
 2562+ .append(
 2563+ $j('<p/>').append(
 2564+ gM( 'mwe-upwiz-thanks-wikitext' ),
 2565+ $j( '<br />' ),
 2566+ $j( '<textarea class="mwe-long-textarea" rows="2"/>' )
 2567+ .growTextArea()
 2568+ .readonly()
 2569+ .append( thumbWikiText )
 2570+ .trigger('resizeEvent')
 2571+ ),
 2572+ $j('<p/>').append(
 2573+ gM( 'mwe-upwiz-thanks-url' ),
 2574+ $j( '<br />' ),
 2575+ $j( '<textarea class="mwe-long-textarea" rows="2"/>' )
 2576+ .growTextArea()
 2577+ .readonly()
 2578+ .append( upload.imageinfo.descriptionurl )
 2579+ .trigger('resizeEvent')
 2580+ )
 2581+ )
 2582+ );
 2583+
 2584+ $j( '#mwe-upwiz-thanks' ).append( thanksDiv );
 2585+ } );
 2586+ },
 2587+
 2588+ /**
 2589+ *
 2590+ */
 2591+ pause: function() {
 2592+
 2593+ },
 2594+
 2595+ /**
 2596+ *
 2597+ */
 2598+ stop: function() {
 2599+
 2600+ }
 2601+};
 2602+
 2603+
 2604+mw.UploadWizardDeedPreview = function(upload) {
 2605+ this.upload = upload;
 2606+};
 2607+
 2608+mw.UploadWizardDeedPreview.prototype = {
 2609+ setup: function() {
 2610+ var _this = this;
 2611+ // add a preview on the deeds page
 2612+ var thumbnailDiv = $j( '<div class="mwe-upwiz-thumbnail-small"></div>' );
 2613+ $j( '#mwe-upwiz-deeds-thumbnails' ).append( thumbnailDiv );
 2614+ _this.upload.setThumbnail( thumbnailDiv, mw.UploadWizard.config[ 'smallThumbnailWidth' ] );
 2615+ }
 2616+};
 2617+
 2618+mw.UploadWizardNullDeed = $j.extend( new mw.UploadWizardDeed(), {
 2619+ valid: function() {
 2620+ return false;
 2621+ }
 2622+} );
 2623+
 2624+
 2625+/**
 2626+ * Set up the form and deed object for the deed option that says these uploads are all the user's own work.
 2627+ * XXX these deeds are starting to turn into jquery fns
 2628+ */
 2629+mw.UploadWizardDeedOwnWork = function( uploadCount ) {
 2630+ uploadCount = uploadCount ? uploadCount : 1;
 2631+
 2632+ var _this = new mw.UploadWizardDeed();
 2633+
 2634+ _this.authorInput = $j( '<input />')
 2635+ .attr( { name: "author", type: "text" } )
 2636+ .addClass( 'mwe-upwiz-sign' );
 2637+
 2638+ var licenseInputDiv = $j( '<div class="mwe-upwiz-deed-license"></div>' );
 2639+ _this.licenseInput = new mw.UploadWizardLicenseInput( licenseInputDiv );
 2640+ _this.licenseInput.setDefaultValues();
 2641+
 2642+ return $j.extend( _this, {
 2643+
 2644+ name: 'ownwork',
 2645+
 2646+ /**
 2647+ * Is this correctly set, with side effects of causing errors to show in interface.
 2648+ * @return boolean true if valid, false if not
 2649+ */
 2650+ valid: function() {
 2651+ // n.b. valid() has side effects and both should be called every time the function is called.
 2652+ // do not short-circuit.
 2653+ var formValid = _this.$form.valid();
 2654+ var licenseInputValid = _this.licenseInput.valid();
 2655+ return formValid && licenseInputValid;
 2656+ },
 2657+
 2658+ getSourceWikiText: function() {
 2659+ return '{{own}}';
 2660+ },
 2661+
 2662+ // XXX do we need to escape authorInput, or is wikitext a feature here?
 2663+ // what about scripts?
 2664+ getAuthorWikiText: function() {
 2665+ return "[[User:" + mw.UploadWizard.config[ 'userName' ] + '|' + $j( _this.authorInput ).val() + ']]';
 2666+ },
 2667+
 2668+
 2669+ getLicenseWikiText: function() {
 2670+ var wikiText = '{{self';
 2671+ $j.each( _this.licenseInput.getTemplates(), function( i, template ) {
 2672+ wikiText += '|' + template;
 2673+ } );
 2674+ wikiText += '}}';
 2675+ return wikiText;
 2676+ },
 2677+
 2678+ setFormFields: function( $selector ) {
 2679+ _this.$selector = $selector;
 2680+
 2681+ _this.$form = $j( '<form/>' );
 2682+
 2683+ var $standardDiv = $j( '<div />' ).append(
 2684+ $j( '<label for="author2" generated="true" class="mwe-validator-error" style="display:block;"/>' ),
 2685+ $j( '<p>' )
 2686+ .html( gM( 'mwe-upwiz-source-ownwork-assert',
 2687+ uploadCount,
 2688+ '<span class="mwe-standard-author-input"></span>' )
 2689+ ),
 2690+ $j( '<p class="mwe-small-print" />' ).append( gM( 'mwe-upwiz-source-ownwork-assert-note' ) )
 2691+ );
 2692+ $standardDiv.find( '.mwe-standard-author-input' ).append( $j( '<input name="author2" type="text" class="mwe-upwiz-sign" />' ) );
 2693+
 2694+ var $customDiv = $j('<div/>').append(
 2695+ $j( '<label for="author" generated="true" class="mwe-validator-error" style="display:block;"/>' ),
 2696+ $j( '<p>' )
 2697+ .html( gM( 'mwe-upwiz-source-ownwork-assert-custom',
 2698+ uploadCount,
 2699+ '<span class="mwe-custom-author-input"></span>' ) ),
 2700+ licenseInputDiv
 2701+ );
 2702+ // have to add the author input this way -- gM() will flatten it to a string and we'll lose it as a dom object
 2703+ $customDiv.find( '.mwe-custom-author-input' ).append( _this.authorInput );
 2704+
 2705+
 2706+ var $crossfader = $j( '<div>' ).append( $standardDiv, $customDiv );
 2707+ var $toggler = $j( '<p class="mwe-more-options" style="text-align: right" />' )
 2708+ .append( $j( '<a />' )
 2709+ .append( gM( 'mwe-upwiz-license-show-all' ) )
 2710+ .click( function() {
 2711+ _this.formValidator.resetForm();
 2712+ if ( $crossfader.data( 'crossfadeDisplay' ) === $customDiv ) {
 2713+ _this.licenseInput.setDefaultValues();
 2714+ $crossfader.morphCrossfade( $standardDiv );
 2715+ $j( this ).html( gM( 'mwe-upwiz-license-show-all' ) );
 2716+ } else {
 2717+ $crossfader.morphCrossfade( $customDiv );
 2718+ $j( this ).html( gM( 'mwe-upwiz-license-show-recommended' ) );
 2719+ }
 2720+ } ) );
 2721+
 2722+ var $formFields = $j( '<div class="mwe-upwiz-deed-form-internal" />' )
 2723+ .append( $crossfader, $toggler );
 2724+
 2725+
 2726+ // synchronize both username signatures
 2727+ // set initial value to configured username
 2728+ // if one changes all the others change (keyup event)
 2729+ //
 2730+ // also set tooltips ( the title, tipsy() )
 2731+ $formFields.find( '.mwe-upwiz-sign' )
 2732+ .attr( {
 2733+ title: gM( 'mwe-upwiz-tooltip-sign' ),
 2734+ value: mw.UploadWizard.config[ 'userName' ]
 2735+ } )
 2736+ .tipsyPlus()
 2737+ .keyup( function() {
 2738+ var thisInput = this;
 2739+ var thisVal = $j( thisInput ).val();
 2740+ $j.each( $formFields.find( '.mwe-upwiz-sign' ), function( i, input ) {
 2741+ if (thisInput !== input) {
 2742+ $j( input ).val( thisVal );
 2743+ }
 2744+ } );
 2745+ } );
 2746+
 2747+ _this.$form.append( $formFields );
 2748+ $selector.append( _this.$form );
 2749+
 2750+ // done after added to the DOM, so there are true heights
 2751+ $crossfader.morphCrossfader();
 2752+
 2753+
 2754+ // and finally, make it validatable
 2755+ _this.formValidator = _this.$form.validate( {
 2756+ rules: {
 2757+ author2: {
 2758+ required: function( element ) {
 2759+ return $crossfader.data( 'crossfadeDisplay' ).get(0) === $standardDiv.get(0);
 2760+ },
 2761+ minlength: mw.UploadWizard.config[ 'minAuthorLength' ],
 2762+ maxlength: mw.UploadWizard.config[ 'maxAuthorLength' ]
 2763+ },
 2764+ author: {
 2765+ required: function( element ) {
 2766+ return $crossfader.data( 'crossfadeDisplay' ).get(0) === $customDiv.get(0);
 2767+ },
 2768+ minlength: mw.UploadWizard.config[ 'minAuthorLength' ],
 2769+ maxlength: mw.UploadWizard.config[ 'maxAuthorLength' ]
 2770+ }
 2771+ },
 2772+ messages: {
 2773+ author2: {
 2774+ required: gM( 'mwe-upwiz-error-signature-blank' ),
 2775+ minlength: gM( 'mwe-upwiz-error-signature-too-short', mw.UploadWizard.config[ 'minAuthorLength' ] ),
 2776+ maxlength: gM( 'mwe-upwiz-error-signature-too-long', mw.UploadWizard.config[ 'maxAuthorLength' ] )
 2777+ },
 2778+ author: {
 2779+ required: gM( 'mwe-upwiz-error-signature-blank' ),
 2780+ minlength: gM( 'mwe-upwiz-error-signature-too-short', mw.UploadWizard.config[ 'minAuthorLength' ] ),
 2781+ maxlength: gM( 'mwe-upwiz-error-signature-too-long', mw.UploadWizard.config[ 'maxAuthorLength' ] )
 2782+ }
 2783+ }
 2784+ } );
 2785+ }
 2786+
 2787+
 2788+ } );
 2789+
 2790+};
 2791+
 2792+// XXX these deeds are starting to turn into jquery fns
 2793+mw.UploadWizardDeedThirdParty = function( uploadCount ) {
 2794+ var _this = new mw.UploadWizardDeed();
 2795+
 2796+ _this.uploadCount = uploadCount ? uploadCount : 1;
 2797+ _this.sourceInput = $j('<textarea class="mwe-source mwe-long-textarea" name="source" rows="1" cols="40"></textarea>' )
 2798+ .growTextArea()
 2799+ .attr( 'title', gM( 'mwe-upwiz-tooltip-source' ) )
 2800+ .tipsyPlus();
 2801+ _this.authorInput = $j('<textarea class="mwe-author mwe-long-textarea" name="author" rows="1" cols="40"></textarea>' )
 2802+ .growTextArea()
 2803+ .attr( 'title', gM( 'mwe-upwiz-tooltip-author' ) )
 2804+ .tipsyPlus();
 2805+ licenseInputDiv = $j( '<div class="mwe-upwiz-deed-license"></div>' );
 2806+ _this.licenseInput = new mw.UploadWizardLicenseInput( licenseInputDiv );
 2807+
 2808+
 2809+ return $j.extend( _this, mw.UploadWizardDeed.prototype, {
 2810+ name: 'thirdparty',
 2811+
 2812+ setFormFields: function( $selector ) {
 2813+ var _this = this;
 2814+ _this.$form = $j( '<form/>' );
 2815+
 2816+ var $formFields = $j( '<div class="mwe-upwiz-deed-form-internal"/>' );
 2817+
 2818+ if ( uploadCount > 1 ) {
 2819+ $formFields.append( $j( '<div />' ).append( gM( 'mwe-upwiz-source-thirdparty-custom-multiple-intro' ) ) );
 2820+ }
 2821+
 2822+ $formFields.append (
 2823+ $j( '<div class="mwe-upwiz-source-thirdparty-custom-multiple-intro" />' ),
 2824+ $j( '<label for="source" generated="true" class="mwe-validator-error" style="display:block;"/>' ),
 2825+ $j( '<div class="mwe-upwiz-thirdparty-fields" />' )
 2826+ .append( $j( '<label for="source"/>' ).text( gM( 'mwe-upwiz-source' ) ),
 2827+ _this.sourceInput ),
 2828+ $j( '<label for="author" generated="true" class="mwe-validator-error" style="display:block;"/>' ),
 2829+ $j( '<div class="mwe-upwiz-thirdparty-fields" />' )
 2830+ .append( $j( '<label for="author"/>' ).text( gM( 'mwe-upwiz-author' ) ),
 2831+ _this.authorInput ),
 2832+ $j( '<div class="mwe-upwiz-thirdparty-license" />' )
 2833+ .append( gM( 'mwe-upwiz-source-thirdparty-license', uploadCount ) ),
 2834+ licenseInputDiv
 2835+ );
 2836+
 2837+ _this.$form.validate( {
 2838+ rules: {
 2839+ source: { required: true,
 2840+ minlength: mw.UploadWizard.config[ 'minSourceLength' ],
 2841+ maxlength: mw.UploadWizard.config[ 'maxSourceLength' ] },
 2842+ author: { required: true,
 2843+ minlength: mw.UploadWizard.config[ 'minAuthorLength' ],
 2844+ maxlength: mw.UploadWizard.config[ 'maxAuthorLength' ] }
 2845+ },
 2846+ messages: {
 2847+ source: {
 2848+ required: gM( 'mwe-upwiz-error-blank' ),
 2849+ minlength: gM( 'mwe-upwiz-error-too-short', mw.UploadWizard.config[ 'minSourceLength' ] ),
 2850+ maxlength: gM( 'mwe-upwiz-error-too-long', mw.UploadWizard.config[ 'maxSourceLength' ] )
 2851+ },
 2852+ author: {
 2853+ required: gM( 'mwe-upwiz-error-blank' ),
 2854+ minlength: gM( 'mwe-upwiz-error-too-short', mw.UploadWizard.config[ 'minAuthorLength' ] ),
 2855+ maxlength: gM( 'mwe-upwiz-error-too-long', mw.UploadWizard.config[ 'maxAuthorLength' ] )
 2856+ }
 2857+ }
 2858+ } );
 2859+
 2860+ _this.$form.append( $formFields );
 2861+
 2862+ $selector.append( _this.$form );
 2863+ },
 2864+
 2865+ /**
 2866+ * Is this correctly set, with side effects of causing errors to show in interface.
 2867+ * this is exactly the same as the ownwork valid() function... hopefully we can reduce these to nothing if we make
 2868+ * all validators work the same.
 2869+ * @return boolean true if valid, false if not
 2870+ */
 2871+ valid: function() {
 2872+ // n.b. valid() has side effects and both should be called every time the function is called.
 2873+ // do not short-circuit.
 2874+ var formValid = _this.$form.valid();
 2875+ var licenseInputValid = _this.licenseInput.valid();
 2876+ return formValid && licenseInputValid;
 2877+ }
 2878+ } );
 2879+};
 2880+
 2881+
 2882+
 2883+
 2884+/**
 2885+ * @param selector where to put this deed chooser
 2886+ * @param isPlural whether this chooser applies to multiple files (changes messaging mostly)
 2887+ */
 2888+mw.UploadWizardDeedChooser = function( selector, deeds, uploadCount ) {
 2889+ var _this = this;
 2890+ _this.$selector = $j( selector );
 2891+ _this.uploadCount = uploadCount ? uploadCount : 1;
 2892+
 2893+
 2894+ _this.$errorEl = $j( '<div class="mwe-error"></div>' );
 2895+ _this.$selector.append( _this.$errorEl );
 2896+
 2897+ // name for radio button set
 2898+ mw.UploadWizardDeedChooser.prototype.widgetCount++;
 2899+ _this.name = 'deedChooser' + mw.UploadWizardDeedChooser.prototype.widgetCount.toString();
 2900+
 2901+ $j.each( deeds, function (i, deed) {
 2902+ var id = _this.name + '-' + deed.name;
 2903+
 2904+ var $deedInterface = $j(
 2905+ '<div class="mwe-upwiz-deed mwe-upwiz-deed-' + deed.name + '">'
 2906+ + '<div class="mwe-upwiz-deed-option-title">'
 2907+ + '<span class="mwe-upwiz-deed-header">'
 2908+ + '<input id="' + id +'" name="' + _this.name + '" type="radio" value="' + deed.name + '">'
 2909+ + '<label for="' + id + '" class="mwe-upwiz-deed-name">'
 2910+ + gM( 'mwe-upwiz-source-' + deed.name, _this.uploadCount )
 2911+ + '</label>'
 2912+ + '</input>'
 2913+ + '</span>'
 2914+ // + ' <a class="mwe-upwiz-macro-deeds-return">' + gM( 'mwe-upwiz-change' ) + '</a>'
 2915+ + '</div>'
 2916+ + '<div class="mwe-upwiz-deed-form">'
 2917+ + '</div>'
 2918+ );
 2919+
 2920+ var $deedSelector = _this.$selector.append( $deedInterface );
 2921+
 2922+ deed.setFormFields( $deedInterface.find( '.mwe-upwiz-deed-form' ) );
 2923+
 2924+ $deedInterface.find( 'span.mwe-upwiz-deed-header input' ).click( function() {
 2925+ if ( $j( this ).is(':checked' ) ) {
 2926+ _this.choose( deed );
 2927+ _this.showDeed( $deedInterface );
 2928+ }
 2929+ } );
 2930+
 2931+ } );
 2932+
 2933+ /*
 2934+ $j( '.mwe-upwiz-macro-deeds-return' ).click( function() {
 2935+ _this.choose( mw.UploadWizardNullDeed );
 2936+ _this.showDeedChoice();
 2937+ } );
 2938+ */
 2939+
 2940+ _this.choose( mw.UploadWizardNullDeed );
 2941+ _this.showDeedChoice();
 2942+
 2943+
 2944+};
 2945+
 2946+
 2947+mw.UploadWizardDeedChooser.prototype = {
 2948+
 2949+ /**
 2950+ * How many deed choosers there are (important for creating unique ids, element names)
 2951+ */
 2952+ widgetCount: 0,
 2953+
 2954+ /**
 2955+ * Check if this form is filled out correctly, with side effects of showing error messages if invalid
 2956+ * @return boolean; true if valid, false if not
 2957+ */
 2958+ valid: function() {
 2959+ var _this = this;
 2960+ // we assume there is always a deed available, even if it's just the null deed.
 2961+ var valid = _this.deed.valid();
 2962+ // the only time we need to set an error message is if the null deed is selected.
 2963+ // otherwise, we can assume that the widgets have already added error messages.
 2964+ if (valid) {
 2965+ _this.hideError();
 2966+ } else {
 2967+ if ( _this.deed === mw.UploadWizardNullDeed ) {
 2968+ _this.showError( gM( 'mwe-upwiz-deeds-need-deed', _this.uploadCount ) );
 2969+ $j( _this ).bind( 'chooseDeed', function() {
 2970+ _this.hideError();
 2971+ } );
 2972+ }
 2973+ }
 2974+ return valid;
 2975+ },
 2976+
 2977+ showError: function( error ) {
 2978+ this.$errorEl.html( error );
 2979+ this.$errorEl.fadeIn();
 2980+ },
 2981+
 2982+ hideError: function() {
 2983+ this.$errorEl.fadeOut();
 2984+ this.$errorEl.empty();
 2985+ },
 2986+
 2987+ /**
 2988+ * How many uploads this deed controls
 2989+ */
 2990+ uploadCount: 0,
 2991+
 2992+
 2993+ // XXX it's impossible to choose the null deed if we stick with radio buttons, so that may be useless later
 2994+ choose: function( deed ) {
 2995+ var _this = this;
 2996+ _this.deed = deed;
 2997+ if ( deed === mw.UploadWizardNullDeed ) {
 2998+ $j( _this ).trigger( 'chooseNullDeed' );
 2999+ //_this.trigger( 'isNotReady' );
 3000+ _this.$selector
 3001+ .find( 'input.mwe-accept-deed' )
 3002+ .attr( 'checked', false );
 3003+ } else {
 3004+ $j( _this ).trigger( 'chooseDeed' );
 3005+ }
 3006+ },
 3007+
 3008+ /**
 3009+ * Go back to original source choice.
 3010+ */
 3011+ showDeedChoice: function() {
 3012+ var $allDeeds = this.$selector.find( '.mwe-upwiz-deed' );
 3013+ this.deselectDeed( $allDeeds );
 3014+ // $allDeeds.fadeTo( 'fast', 1.0 ); //maskSafeShow();
 3015+ },
 3016+
 3017+ /**
 3018+ * From the deed choices, make a choice fade to the background a bit, hide the extended form
 3019+ */
 3020+ deselectDeed: function( $deedSelector ) {
 3021+ $deedSelector.removeClass( 'selected' );
 3022+ // $deedSelector.find( 'a.mwe-upwiz-macro-deeds-return' ).hide();
 3023+ $deedSelector.find( '.mwe-upwiz-deed-form' ).slideUp( 500 ); //.maskSafeHide();
 3024+ },
 3025+
 3026+ /**
 3027+ * From the deed choice page, show a particular deed
 3028+ */
 3029+ showDeed: function( $deedSelector ) {
 3030+ var $otherDeeds = $deedSelector.siblings().filter( '.mwe-upwiz-deed' );
 3031+ this.deselectDeed( $otherDeeds );
 3032+ // $siblings.fadeTo( 'fast', 0.5 ) // maskSafeHide();
 3033+
 3034+ $deedSelector
 3035+ .addClass('selected')
 3036+ .fadeTo( 'fast', 1.0 )
 3037+ .find( '.mwe-upwiz-deed-form' ).slideDown( 500 ); // maskSafeShow();
 3038+ // $deedSelector.find( 'a.mwe-upwiz-macro-deeds-return' ).show();
 3039+ }
 3040+
 3041+};
 3042+
 3043+
 3044+
 3045+/**
 3046+ * Miscellaneous utilities
 3047+ */
 3048+mw.UploadWizardUtil = {
 3049+
 3050+ /**
 3051+ * Simple 'more options' toggle that opens more of a form.
 3052+ *
 3053+ * @param toggleDiv the div which has the control to open and shut custom options
 3054+ * @param moreDiv the div containing the custom options
 3055+ */
 3056+ makeToggler: function ( toggleDiv, moreDiv ) {
 3057+ var $toggleLink = $j( '<a>' )
 3058+ .addClass( 'mwe-upwiz-toggler mwe-upwiz-more-options' )
 3059+ .append( gM( 'mwe-upwiz-more-options' ) );
 3060+ $j( toggleDiv ).append( $toggleLink );
 3061+
 3062+
 3063+ var toggle = function( open ) {
 3064+ if ( typeof open === 'undefined' ) {
 3065+ open = ! ( $j( this ).data( 'open' ) ) ;
 3066+ }
 3067+ $j( this ).data( 'open', open );
 3068+ if ( open ) {
 3069+ moreDiv.maskSafeShow();
 3070+ /* when open, show control to close */
 3071+ $toggleLink.html( gM( 'mwe-upwiz-fewer-options' ) );
 3072+ $toggleLink.addClass( "mwe-upwiz-toggler-open" );
 3073+ } else {
 3074+ moreDiv.maskSafeHide();
 3075+ /* when closed, show control to open */
 3076+ $toggleLink.html( gM( 'mwe-upwiz-more-options' ) );
 3077+ $toggleLink.removeClass( "mwe-upwiz-toggler-open" );
 3078+ }
 3079+ };
 3080+
 3081+ toggle(false);
 3082+
 3083+ $toggleLink.click( function( e ) { e.stopPropagation(); toggle(); } );
 3084+
 3085+ $j( moreDiv ).addClass( 'mwe-upwiz-toggled' );
 3086+ },
 3087+
 3088+ /**
 3089+ * remove an item from an array. Tests for === identity to remove the item
 3090+ * XXX the entire rationale for this file may be wrong.
 3091+ * XXX The jQuery way would be to query the DOM for objects, not to keep a separate array hanging around
 3092+ * @param items the array where we want to remove an item
 3093+ * @param item the item to remove
 3094+ */
 3095+ removeItem: function( items, item ) {
 3096+ for ( var i = 0; i < items.length; i++ ) {
 3097+ if ( items[i] === item ) {
 3098+ items.splice( i, 1 );
 3099+ break;
 3100+ }
 3101+ }
 3102+ },
 3103+
 3104+ /**
 3105+ * Capitalise first letter and replace spaces by underscores
 3106+ * @param filename (basename, without directories)
 3107+ * @return typical title as would appear on MediaWiki
 3108+ */
 3109+ pathToTitle: function ( filename ) {
 3110+ return mw.ucfirst( $j.trim( filename ).replace(/ /g, '_' ) );
 3111+ },
 3112+
 3113+ /**
 3114+ * Capitalise first letter and replace underscores by spaces
 3115+ * @param title typical title as would appear on MediaWiki
 3116+ * @return plausible local filename
 3117+ */
 3118+ titleToPath: function ( title ) {
 3119+ return mw.ucfirst( $j.trim( title ).replace(/_/g, ' ' ) );
 3120+ },
 3121+
 3122+
 3123+ /**
 3124+ * Transform "File:title_with_spaces.jpg" into "title with spaces"
 3125+ * @param typical title that would appear on mediawiki, with File: and extension, may include underscores
 3126+ * @return human readable title
 3127+ */
 3128+ fileTitleToHumanTitle: function( title ) {
 3129+ var extension = mw.UploadWizardUtil.getExtension( title );
 3130+ if ( typeof extension !== 'undefined' ) {
 3131+ // the -1 is to get the '.'
 3132+ title = title.substr( 0, title.length - extension.length - 1 );
 3133+ }
 3134+ // usually File:
 3135+ var namespace = wgFormattedNamespaces[wgNamespaceIds['file']];
 3136+ if ( title.indexOf( namespace + ':' ) === 0 ) {
 3137+ title = title.substr( namespace.length + 1 );
 3138+ }
 3139+ return mw.UploadWizardUtil.titleToPath( title );
 3140+ },
 3141+
 3142+
 3143+ /**
 3144+ * Slice extension off a path
 3145+ * We assume that extensions are 1-4 characters in length
 3146+ * @param path to file, like "foo/bar/baz.jpg"
 3147+ * @return extension, like ".jpg" or undefined if it doesn't look lke an extension.
 3148+ */
 3149+ getExtension: function( path ) {
 3150+ var extension = undefined;
 3151+ var idx = path.lastIndexOf( '.' );
 3152+ if (idx > 0 && ( idx > ( path.length - 5 ) ) && ( idx < ( path.length - 1 ) ) ) {
 3153+ extension = path.substr( idx + 1 ).toLowerCase();
 3154+ }
 3155+ return extension;
 3156+ },
 3157+
 3158+ /**
 3159+ * Last resort to guess a proper extension
 3160+ */
 3161+ mimetypeToExtension: {
 3162+ 'image/jpeg': 'jpg',
 3163+ 'image/gif': 'gif'
 3164+ // fill as needed
 3165+ }
 3166+
 3167+
 3168+};
 3169+
 3170+( function( $j ) {
 3171+
 3172+ $j.fn.tipsyPlus = function( optionsArg ) {
 3173+ // use extend!
 3174+ var titleOption = 'title';
 3175+ var htmlOption = false;
 3176+
 3177+ var options = $j.extend(
 3178+ { type: 'help', shadow: true },
 3179+ optionsArg
 3180+ );
 3181+
 3182+ var el = this;
 3183+
 3184+ if (options.plus) {
 3185+ htmlOption = true;
 3186+ titleOption = function() {
 3187+ return $j( '<span />' ).append(
 3188+ $j( this ).attr( 'original-title' ),
 3189+ $j( '<a class="mwe-upwiz-tooltip-link"/>' )
 3190+ .attr( 'href', '#' )
 3191+ .append( gM( 'mwe-upwiz-tooltip-more-info' ) )
 3192+ .mouseenter( function() {
 3193+ el.data('tipsy').sticky = true;
 3194+ } )
 3195+ .mouseleave( function() {
 3196+ el.data('tipsy').sticky = false;
 3197+ } )
 3198+ .click( function() {
 3199+ // show the wiki page with more
 3200+ alert( options.plus );
 3201+ // pass this in as a closure to be called on dismiss
 3202+ el.focus();
 3203+ el.data('tipsy').sticky = false;
 3204+ } )
 3205+ );
 3206+ };
 3207+ }
 3208+
 3209+ return this.tipsy( {
 3210+ gravity: 'w',
 3211+ trigger: 'focus',
 3212+ title: titleOption,
 3213+ html: htmlOption,
 3214+ type: options.type,
 3215+ shadow: options.shadow
 3216+ } );
 3217+
 3218+ };
 3219+
 3220+ /**
 3221+ * Create 'remove' control, an X which highlights in some standardized way.
 3222+ */
 3223+ $j.fn.removeCtrl = function( tooltipMsgKey, callback ) {
 3224+ return $j( '<div class="mwe-upwiz-remove-ctrl ui-corner-all" />' )
 3225+ .attr( 'title', gM( tooltipMsgKey ) )
 3226+ .click( callback )
 3227+ .hover( function() { $j( this ).addClass( 'hover' ); },
 3228+ function() { $j( this ).removeClass( 'hover' ); } )
 3229+ .append( $j( '<span class="ui-icon ui-icon-close" />' ) );
 3230+ };
 3231+
 3232+ /**
 3233+ * Prevent the closing of a window with a confirm message (the onbeforeunload event seems to
 3234+ * work in most browsers
 3235+ * e.g.
 3236+ * var allowCloseWindow = jQuery().preventCloseWindow( { message: "Don't go away!" } );
 3237+ * // ... do stuff that can't be interrupted ...
 3238+ * allowCloseWindow();
 3239+ *
 3240+ * @param options object which should have a message string, already internationalized
 3241+ * @return closure execute this when you want to allow the user to close the window
 3242+ */
 3243+ $j.fn.preventCloseWindow = function( options ) {
 3244+ if ( typeof options === 'undefined' ) {
 3245+ options = {};
 3246+ }
 3247+
 3248+ if ( typeof options.message === 'undefined' ) {
 3249+ options.message = 'Are you sure you want to close this window?';
 3250+ }
 3251+
 3252+ $j( window ).unload( function() {
 3253+ return options.message;
 3254+ } );
 3255+
 3256+ return function() {
 3257+ $j( window ).removeAttr( 'unload' );
 3258+ };
 3259+
 3260+ };
 3261+
 3262+
 3263+ $j.fn.notify = function ( message ) {
 3264+ // could do something here with Chrome's in-browser growl-like notifications.
 3265+ // play a sound?
 3266+ // if the current tab does not have focus, use an alert?
 3267+ // alert( message );
 3268+ };
 3269+
 3270+ $j.fn.enableNextButton = function() {
 3271+ return this.find( '.mwe-upwiz-button-next' )
 3272+ .removeAttr( 'disabled' );
 3273+ // .effect( 'pulsate', { times: 3 }, 1000 );
 3274+ };
 3275+
 3276+ $j.fn.disableNextButton = function() {
 3277+ return this.find( '.mwe-upwiz-button-next' )
 3278+ .attr( 'disabled', true );
 3279+ };
 3280+
 3281+ $j.fn.readonly = function() {
 3282+ return this.attr( 'readonly', 'readonly' ).addClass( 'mwe-readonly' );
 3283+ };
 3284+
 3285+ /* will change in RTL, but I can't think of an easy way to do this with only CSS */
 3286+ $j.fn.requiredFieldLabel = function() {
 3287+ this.addClass( 'mwe-upwiz-required-field' );
 3288+ return this.prepend( $j( '<span/>' ).append( '*' ).addClass( 'mwe-upwiz-required-marker' ) );
 3289+ };
 3290+
 3291+ /**
 3292+ * Upper-case the first letter of a string. XXX move to common library
 3293+ * @param string
 3294+ * @return string with first letter uppercased.
 3295+ */
 3296+ mw.ucfirst = function( s ) {
 3297+ return s.substring(0,1).toUpperCase() + s.substr(1);
 3298+ };
 3299+
 3300+
 3301+
 3302+ /**
 3303+ * jQuery extension. Makes a textarea automatically grow if you enter overflow
 3304+ * (This feature was in the old Commons interface with a confusing arrow icon; it's nicer to make it automatic.)
 3305+ */
 3306+ jQuery.fn.growTextArea = function( options ) {
 3307+
 3308+ // this is a jquery-style object
 3309+
 3310+ // in MSIE, this makes it possible to know what scrollheight is
 3311+ // Technically this means text could now dangle over the edge,
 3312+ // but it shouldn't because it will always grow to accomodate very quickly.
 3313+
 3314+ if ($j.msie) {
 3315+ this.each( function(i, textArea) {
 3316+ textArea.style.overflow = 'visible';
 3317+ } );
 3318+ }
 3319+
 3320+ var resizeIfNeeded = function() {
 3321+ // this is the dom element
 3322+ // is there a better way to do this?
 3323+ if (this.scrollHeight >= this.offsetHeight) {
 3324+ this.rows++;
 3325+ while (this.scrollHeight > this.offsetHeight) {
 3326+ this.rows++;
 3327+ }
 3328+ }
 3329+ return this;
 3330+ };
 3331+
 3332+ this.addClass( 'mwe-grow-textarea' );
 3333+
 3334+ this.bind( 'resizeEvent', resizeIfNeeded );
 3335+
 3336+ this.keyup( resizeIfNeeded );
 3337+ this.change( resizeIfNeeded );
 3338+
 3339+
 3340+ return this;
 3341+ };
 3342+
 3343+ jQuery.fn.mask = function( options ) {
 3344+
 3345+ // intercept clicks...
 3346+ // Note: the size of the div must be obtainable. Hence, this cannot be a div without layout (e.g. display:none).
 3347+ // some of this is borrowed from http://code.google.com/p/jquery-loadmask/ , but simplified
 3348+ $j.each( this, function( i, el ) {
 3349+
 3350+ if ( ! $j( el ).data( 'mask' ) ) {
 3351+
 3352+
 3353+ //fix for z-index bug with selects in IE6
 3354+ if ( $j.browser.msie && $j.browser.version.substring(0,1) === '6' ){
 3355+ el.find( "select" ).addClass( "masked-hidden" );
 3356+ }
 3357+
 3358+ var mask = $j( '<div />' )
 3359+ .css( { 'position' : 'absolute',
 3360+ 'top' : '0px',
 3361+ 'left' : '0px',
 3362+ 'width' : el.offsetWidth + 'px',
 3363+ 'height' : el.offsetHeight + 'px',
 3364+ 'z-index' : 100 } )
 3365+ .click( function( e ) { e.stopPropagation(); } );
 3366+
 3367+ $j( el ).css( { 'position' : 'relative' } )
 3368+ .fadeTo( 'fast', 0.5 )
 3369+ .append( mask )
 3370+ .data( 'mask', mask );
 3371+
 3372+ //auto height fix for IE -- not sure about this, i think offsetWidth + Height is a better solution. Test!
 3373+ /*
 3374+ if( $j.browser.msie ) {
 3375+ mask.height(el.height() + parseInt(el.css("padding-top")) + parseInt(el.css("padding-bottom")));
 3376+ mask.width(el.width() + parseInt(el.css("padding-left")) + parseInt(el.css("padding-right")));
 3377+ }
 3378+ */
 3379+
 3380+ }
 3381+
 3382+ // XXX bind to a custom event in case the div size changes : ?
 3383+
 3384+ } );
 3385+
 3386+ return this;
 3387+
 3388+ };
 3389+
 3390+ jQuery.fn.unmask = function( options ) {
 3391+
 3392+ $j.each( this, function( i, el ) {
 3393+ if ( $j( el ).data( 'mask' ) ) {
 3394+ var mask = $j( el ).data( 'mask' );
 3395+ $j( el ).removeData( 'mask' ); // from the data
 3396+ mask.remove(); // from the DOM
 3397+ $j( el ).fadeTo( 'fast', 1.0 );
 3398+ }
 3399+ } );
 3400+
 3401+
 3402+ return this;
 3403+ };
 3404+
 3405+
 3406+ /**
 3407+ * Safe hide and show
 3408+ * Rather than use display: none, this collapses the divs to zero height
 3409+ * This is good because then the elements in the divs still have layout and we can do things like mask and unmask (above)
 3410+ * XXX may be obsolete as we are not really doing this any more
 3411+ * disable form fields so we do not tab through them when hidden
 3412+ * XXX for some reason the disabling doesn't work with the date field.
 3413+ */
 3414+
 3415+ jQuery.fn.maskSafeHide = function( options ) {
 3416+ $j.each( this.find( ':enabled' ), function(i, input) {
 3417+ $j( input ).data( 'wasEnabled', true )
 3418+ .attr( 'disabled', 'disabled' );
 3419+ } );
 3420+ return this.css( { 'height' : '0px', 'overflow' : 'hidden' } );
 3421+ };
 3422+
 3423+ // may be causing scrollbar to appear when div changes size
 3424+ // re-enable form fields (disabled so we did not tab through them when hidden)
 3425+ jQuery.fn.maskSafeShow = function( options ) {
 3426+ $j.each( this.find( ':disabled' ), function (i, input) {
 3427+ if ($j( input ).data( 'wasEnabled' )) {
 3428+ $j( input ).removeAttr( 'disabled' )
 3429+ .removeData( 'wasEnabled' );
 3430+ }
 3431+ } );
 3432+ return this.css( { 'height' : 'auto', 'overflow' : 'visible' } );
 3433+ };
 3434+
 3435+ $j.validator.setDefaults( {
 3436+ debug: true,
 3437+ errorClass: 'mwe-validator-error'
 3438+ } );
 3439+
 3440+} )( jQuery );
Property changes on: trunk/extensions/UploadWizard/resources/mw.UploadWizard.js
___________________________________________________________________
Added: svn:eol-style
13441 + native
Index: trunk/extensions/UploadWizard/resources/mw.DestinationChecker.js
@@ -0,0 +1,211 @@
 2+/**
 3+ * Object to attach to a file name input, to be run on its change() event
 4+ * Largely derived from wgUploadWarningObj in old upload.js
 5+ * Perhaps this could be a jQuery ext
 6+ * @param options dictionary of options
 7+ * selector required, the selector for the input to check
 8+ * processResult required, function to execute on results. accepts two args:
 9+ * 1) filename that invoked this request -- should check if this is still current filename
 10+ * 2) an object with the following fields
 11+ * isUnique: boolean
 12+ * img: thumbnail image src (if not unique)
 13+ * href: the url of the full image (if not unique)
 14+ * title: normalized title of file (if not unique)
 15+ * spinner required, closure to execute to show progress: accepts true to start, false to stop
 16+ * apiUrl optional url to call for api. falls back to local api url
 17+ * delay optional how long to delay after a change in ms. falls back to configured default
 18+ * preprocess optional: function to apply to the contents of selector before testing
 19+ * events what events on the input trigger a check.
 20+ */
 21+mw.DestinationChecker = function( options ) {
 22+
 23+ var _this = this;
 24+ _this.selector = options.selector;
 25+ _this.spinner = options.spinner;
 26+ _this.processResult = options.processResult;
 27+
 28+ // optional overrides
 29+
 30+ if (options.apiUrl) {
 31+ _this.apiUrl = options.apiUrl;
 32+ } else {
 33+ _this.apiUrl = mw.UploadWizard.config[ 'apiUrl' ];
 34+ }
 35+
 36+ $j.each( ['preprocess', 'delay', 'events'], function( i, option ) {
 37+ if ( options[option] ) {
 38+ _this[option] = options[option];
 39+ }
 40+ } );
 41+
 42+
 43+ // initialize!
 44+
 45+ var check = _this.getDelayedChecker();
 46+
 47+ $j.each( _this.events, function(i, eventName) {
 48+ $j( _this.selector )[eventName]( check );
 49+ } );
 50+
 51+};
 52+
 53+mw.DestinationChecker.prototype = {
 54+
 55+ // events that the input undergoes which fire off a check
 56+ events: [ 'change', 'keyup' ],
 57+
 58+ // how long the input muse be "idle" before doing call (don't want to check on each key press)
 59+ delay: 500, // ms;
 60+
 61+ // what tracks the wait
 62+ timeoutId: null,
 63+
 64+ // cached results from api calls
 65+ cachedResult: {},
 66+
 67+ /**
 68+ * There is an option to preprocess the name (in order to perhaps convert it from
 69+ * title to path, e.g. spaces to underscores, or to add the "File:" part.) Depends on
 70+ * exactly what your input field represents.
 71+ * In the event that the invoker doesn't supply a name preprocessor, use this identity function
 72+ * as default
 73+ *
 74+ * @param something
 75+ * @return that same thing
 76+ */
 77+ preprocess: function(x) { return x; },
 78+
 79+ /**
 80+ * fire when the input changes value or keypress
 81+ * will trigger a check of the name if the field has been idle for delay ms.
 82+ */
 83+ getDelayedChecker: function() {
 84+ var checker = this;
 85+ return function() {
 86+ var el = this; // but we don't use it, since we already have it in _this.selector
 87+
 88+ // if we changed before the old timeout ran, clear that timeout.
 89+ if ( checker.timeoutId ) {
 90+ window.clearTimeout( checker.timeoutId );
 91+ }
 92+
 93+ // and start another, hoping this time we'll be idle for delay ms.
 94+ checker.timeoutId = window.setTimeout(
 95+ function() { checker.checkUnique(); },
 96+ checker.delay
 97+ );
 98+ };
 99+ },
 100+
 101+ /**
 102+ * Get the current value of the input, with optional preprocessing
 103+ * @return the current input value, with optional processing
 104+ */
 105+ getName: function() {
 106+ var _this = this;
 107+ return _this.preprocess( $j( _this.selector ).val() );
 108+ },
 109+
 110+ /**
 111+ * Async check if a filename is unique. Can be attached to a field's change() event
 112+ * This is a more abstract version of AddMedia/UploadHandler.js::doDestCheck
 113+ */
 114+ checkUnique: function() {
 115+ var _this = this;
 116+
 117+ var found = false;
 118+ var name = _this.getName();
 119+
 120+ if ( _this.cachedResult[name] !== undefined ) {
 121+ _this.processResult( _this.cachedResult[name] );
 122+ return;
 123+ }
 124+
 125+ // set the spinner to spin
 126+ _this.spinner( true );
 127+
 128+ // Setup the request -- will return thumbnail data if it finds one
 129+ var request = {
 130+ 'titles': 'File:' + name,
 131+ 'prop': 'imageinfo',
 132+ 'iiprop': 'url|mime|size',
 133+ 'iiurlwidth': 150
 134+ };
 135+
 136+ // Do the destination check
 137+ mw.getJSON( _this.apiUrl, request, function( data ) {
 138+ // Remove spinner
 139+ _this.spinner( false );
 140+
 141+ // if the name's changed in the meantime, our result is useless
 142+ if ( name != _this.getName() ) {
 143+ return;
 144+ }
 145+
 146+ if ( !data || !data.query || !data.query.pages ) {
 147+ // Ignore a null result
 148+ mw.log(" No data in checkUnique result");
 149+ return;
 150+ }
 151+
 152+ var result = undefined;
 153+
 154+ if ( data.query.pages[-1] ) {
 155+ // No conflict found; this file name is unique
 156+ mw.log(" No pages in checkUnique result");
 157+ result = { isUnique: true };
 158+
 159+ } else {
 160+
 161+ for ( var page_id in data.query.pages ) {
 162+ if ( !data.query.pages[ page_id ].imageinfo ) {
 163+ continue;
 164+ }
 165+
 166+ // Conflict found, this filename is NOT unique
 167+ mw.log( " conflict! " );
 168+
 169+ var ntitle;
 170+ if ( data.query.normalized ) {
 171+ ntitle = data.query.normalized[0].to;
 172+ } else {
 173+ ntitle = data.query.pages[ page_id ].title;
 174+ }
 175+
 176+ var img = data.query.pages[ page_id ].imageinfo[0];
 177+
 178+ result = {
 179+ isUnique: false,
 180+ img: img,
 181+ title: ntitle,
 182+ href : img.descriptionurl
 183+ };
 184+
 185+ break;
 186+ }
 187+ }
 188+
 189+ if ( result !== undefined ) {
 190+ _this.cachedResult[name] = result;
 191+ _this.processResult( result );
 192+ }
 193+
 194+ } );
 195+ }
 196+
 197+};
 198+
 199+
 200+/**
 201+ * jQuery extension to make a field upload-checkable
 202+ */
 203+( function ( $ ) {
 204+ $.fn.destinationChecked = function( options ) {
 205+ var _this = this;
 206+ options.selector = _this;
 207+ var checker = new mw.DestinationChecker( options );
 208+ // this should really be done with triggers
 209+ _this.checkUnique = function() { checker.checkUnique(); };
 210+ return _this;
 211+ };
 212+} )( jQuery );
Property changes on: trunk/extensions/UploadWizard/resources/mw.DestinationChecker.js
___________________________________________________________________
Added: svn:eol-style
1213 + native
Index: trunk/extensions/UploadWizard/resources/toggle-open.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/UploadWizard/resources/toggle-open.png
___________________________________________________________________
Added: svn:mime-type
2214 + image/png
Index: trunk/extensions/UploadWizard/resources/arrow-tail.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/UploadWizard/resources/arrow-tail.png
___________________________________________________________________
Added: svn:mime-type
3215 + image/png
Index: trunk/extensions/UploadWizard/resources/toggle.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/UploadWizard/resources/toggle.png
___________________________________________________________________
Added: svn:mime-type
4216 + image/png
Index: trunk/extensions/UploadWizard/resources/mw.Utilities.js
@@ -0,0 +1,26 @@
 2+/**
 3+* Check if an object is empty or if its an empty string.
 4+*
 5+* @param {Object} object Object to be checked
 6+*/
 7+mw.isEmpty = function( object ) {
 8+ if( typeof object == 'string' ) {
 9+ if( object == '' ) return true;
 10+ // Non empty string:
 11+ return false;
 12+ }
 13+
 14+ // If an array check length:
 15+ if( Object.prototype.toString.call( object ) === "[object Array]"
 16+ && object.length == 0 ) {
 17+ return true;
 18+ }
 19+
 20+ // Else check as an object:
 21+ for( var i in object ) { return false; }
 22+
 23+ // Else object is empty:
 24+ return true;
 25+}
 26+
 27+
Property changes on: trunk/extensions/UploadWizard/resources/mw.Utilities.js
___________________________________________________________________
Added: svn:eol-style
128 + native
Index: trunk/extensions/UploadWizard/resources/mw.ApiUploadHandler.js
@@ -0,0 +1,100 @@
 2+/**
 3+ * An attempt to refactor out the stuff that does API-via-iframe transport
 4+ * In the hopes that this will eventually work for AddMediaWizard too
 5+ */
 6+
 7+// n.b. if there are message strings, or any assumption about HTML structure of the form.
 8+// then we probably did it wrong
 9+
 10+/**
 11+ * Represents an object which configures a form to upload its files via an iframe talking to the MediaWiki API.
 12+ * @param an UploadInterface object, which contains a .form property which points to a real HTML form in the DOM
 13+ */
 14+mw.ApiUploadHandler = function( upload ) {
 15+ var _this = this;
 16+ _this.upload = upload;
 17+
 18+ _this.configureForm();
 19+
 20+ // hardcoded for now
 21+ // can also use Xhr Binary depending on config
 22+ _this.transport = new mw.IframeTransport(
 23+ _this.upload.ui.form,
 24+ function( fraction ){ _this.upload.setTransportProgress( fraction ); },
 25+ function( result ) { _this.upload.setTransported( result ); }
 26+ );
 27+
 28+};
 29+
 30+mw.ApiUploadHandler.prototype = {
 31+ /**
 32+ * Configure an HTML form so that it will submit its files to our transport (an iframe)
 33+ * with proper params for the API
 34+ * @param callback
 35+ */
 36+ configureForm: function() {
 37+ var apiUrl = mw.UploadWizard.config[ 'apiUrl' ]; // XXX or? throw new Error( "configuration", "no API url" );
 38+ if ( ! ( mw.UploadWizard.config[ 'token' ] ) ) {
 39+ throw new Error( "configuration", "no edit token" );
 40+ }
 41+
 42+ var _this = this;
 43+ mw.log( "configuring form for Upload API" );
 44+
 45+ // Set the form action
 46+ try {
 47+ $j( _this.upload.ui.form )
 48+ .attr( 'action', apiUrl )
 49+ .attr( 'method', 'POST' )
 50+ .attr( 'enctype', 'multipart/form-data' );
 51+ } catch ( e ) {
 52+ alert( "oops, form modification didn't work in ApiUploadHandler" );
 53+ mw.log( "IE for some reason error's out when you change the action" );
 54+ // well, if IE fucks this up perhaps we should do something to make sure it writes correctly
 55+ // from the outset?
 56+ }
 57+
 58+ _this.addFormInputIfMissing( 'token', mw.UploadWizard.config[ 'token' ]);
 59+ _this.addFormInputIfMissing( 'action', 'upload' );
 60+ _this.addFormInputIfMissing( 'format', 'jsonfm' );
 61+
 62+ // XXX only for testing, so it stops complaining about dupes
 63+ if ( mw.UploadWizard.config[ 'debug' ]) {
 64+ _this.addFormInputIfMissing( 'ignorewarnings', '1' );
 65+ }
 66+ },
 67+
 68+ /**
 69+ * Add a hidden input to a form if it was not already there.
 70+ * @param name the name of the input
 71+ * @param value the value of the input
 72+ */
 73+ addFormInputIfMissing: function( name, value ) {
 74+ var _this = this;
 75+ var $jForm = $j( _this.upload.ui.form );
 76+ if ( $jForm.find( "[name='" + name + "']" ).length === 0 ) {
 77+ $jForm.append(
 78+ $j( '<input />' )
 79+ .attr( {
 80+ 'type': "hidden",
 81+ 'name' : name,
 82+ 'value' : value
 83+ } )
 84+ );
 85+ }
 86+ },
 87+
 88+ /**
 89+ * Kick off the upload!
 90+ */
 91+ start: function() {
 92+ var _this = this;
 93+ mw.log( "api: upload start!" );
 94+ _this.beginTime = ( new Date() ).getTime();
 95+ _this.upload.ui.busy();
 96+ $j( this.upload.ui.form ).submit();
 97+ }
 98+};
 99+
 100+
 101+
Property changes on: trunk/extensions/UploadWizard/resources/mw.ApiUploadHandler.js
___________________________________________________________________
Added: svn:eol-style
1102 + native
Index: trunk/extensions/UploadWizard/resources/mw.UploadApiProcessor.js
@@ -0,0 +1,296 @@
 2+// might be useful to raise exceptions when the api goes wrong
 3+// however, when everything is async, then who receives them?
 4+mw.UploadApiProcessor = function(doneCb, errorCb) {
 5+ var _this = this;
 6+ _this.doneCb = doneCb;
 7+ _this.errorCb = errorCb;
 8+};
 9+
 10+
 11+mw.UploadApiProcessor.prototype = {
 12+
 13+ warnings: [
 14+ 'badfilename', // was the resultant filename different from desired? If so return what we actually got in the 'badfilename' warnings
 15+ 'filetype-unwanted-type', // bad filetype, as determined from extenstion. content is the bad extension
 16+ 'large-file', // $wgUploadSizeWarning, numeric value of largest size (bytes, kb, what?)
 17+ 'emptyfile', // set to "true" if file was empty
 18+ 'exists', // set to true if file by that name already existed
 19+ 'duplicate', // hash collision found
 20+ 'duplicate-archive' // hash collision found in archives
 21+ ],
 22+
 23+ errors: [
 24+ 'empty-file',
 25+ 'filetype-missing', //(missing an extension)
 26+ 'filetype-banned', // (extension banned) // also returns: { filetype => the filetype we thought it was, allowed => [ extensions ] }
 27+ 'filename-tooshort',
 28+ 'illegal-filename', // { filename => verification[filtered] }
 29+ 'overwrite', // overwrite existing file (failed?)
 30+ 'verification-error', // {details => verification-details}
 31+ 'hookaborted', // error => verificationerror }
 32+ 'unknown_error'
 33+ ],
 34+
 35+
 36+ // NB
 37+ // It's not clear if we can even get these errors (error_msg_key, error_onlykey) any more
 38+
 39+ // There are many possible error messages here, so we don't load all
 40+ // message text in advance, instead we use mw.getRemoteMsg() for some.
 41+ //
 42+ // This code is similar to the error handling code formerly in
 43+ // SpecialUpload::processUpload()
 44+ error_msg_key: {
 45+ '2' : 'largefileserver',
 46+ '3' : 'emptyfile',
 47+ '4' : 'minlength1',
 48+ '5' : 'illegalfilename'
 49+ },
 50+
 51+ // NOTE:: handle these error types
 52+ error_onlykey: {
 53+ '1': 'BEFORE_PROCESSING',
 54+ '6': 'PROTECTED_PAGE',
 55+ '7': 'OVERWRITE_EXISTING_FILE',
 56+ '8': 'FILETYPE_MISSING',
 57+ '9': 'FILETYPE_BADTYPE',
 58+ '10': 'VERIFICATION_ERROR',
 59+ '11': 'UPLOAD_VERIFICATION_ERROR',
 60+ '12': 'UPLOAD_WARNING',
 61+ '13': 'INTERNAL_ERROR',
 62+ '14': 'MIN_LENGTH_PARTNAME'
 63+ },
 64+
 65+
 66+ /**
 67+ * Process the result of an action=upload API request, into a useful
 68+ * data structure.
 69+ * upload,
 70+ * errors,
 71+ * warnings
 72+ * Augment with error messages in the local language if possible
 73+ *
 74+ */
 75+ processResult: function( result ) {
 76+ var _this = this;
 77+ mw.log( 'processResult::' );
 78+
 79+ // debugger;
 80+
 81+ var parsedResult = _this.parseResult(result);
 82+
 83+ if ( _this.doneCb && typeof _this.doneCb == 'function' ) {
 84+ mw.log( "call doneCb" );
 85+ _this.doneCb( parsedResult );
 86+
 87+ }
 88+ return true;
 89+ },
 90+
 91+
 92+ parseResult: function( result ) {
 93+ if ( result.upload && result.upload.imageinfo && result.upload.imageinfo.descriptionurl ) {
 94+ result.isSuccess = true;
 95+ }
 96+
 97+ return result;
 98+
 99+ }
 100+
 101+
 102+};
 103+
 104+
 105+ if ( result.error || ( result.upload && result.upload.result == "Failure" ) ) {
 106+
 107+ // Check a few places for the error code
 108+ var error_code = 0;
 109+ var errorReplaceArg = '';
 110+ if ( result.error && result.error.code ) {
 111+ error_code = result.error.code;
 112+ } else if ( result.upload.code ) {
 113+ if ( typeof result.upload.code == 'object' ) {
 114+ if ( result.upload.code[0] ) {
 115+ error_code = result.upload.code[0];
 116+ }
 117+ if ( result.upload.code['status'] ) {
 118+ error_code = result.upload.code['status'];
 119+ if ( result.upload.code['filtered'] ) {
 120+ errorReplaceArg = result.upload.code['filtered'];
 121+ }
 122+ }
 123+ } else {
 124+ result.upload.code; // XXX ??
 125+ }
 126+ }
 127+
 128+ var error_msg = '';
 129+ if ( typeof result.error == 'string' ) {
 130+ error_msg = result.error;
 131+ }
 132+
 133+ if ( !error_code || error_code == 'unknown-error' ) {
 134+ if ( typeof JSON != 'undefined' ) {
 135+ mw.log( 'Error: result: ' + JSON.stringify( result ) );
 136+ }
 137+ if ( result.upload.error == 'internal-error' ) {
 138+ // Do a remote message load
 139+ errorKey = result.upload.details[0];
 140+
 141+ mw.getRemoteMsg( errorKey, function() {
 142+ _this.ui.setPrompt( gM( 'mwe-uploaderror' ), gM( errorKey ), buttons );
 143+
 144+ });
 145+ return false;
 146+ }
 147+
 148+ _this.ui.setPrompt(
 149+ gM('mwe-uploaderror'),
 150+ gM('mwe-unknown-error') + '<br>' + error_msg,
 151+ buttons );
 152+ return false;
 153+ }
 154+
 155+ if ( result.error && result.error.info ) {
 156+ _this.ui.setPrompt( gM( 'mwe-uploaderror' ), result.error.info, buttons );
 157+ return false;
 158+ }
 159+
 160+ if ( typeof error_code == 'number'
 161+ && typeof _this.error_msg_key[error_code] == 'undefined' )
 162+ {
 163+ if ( result.upload.code.finalExt ) {
 164+ _this.ui.setPrompt(
 165+ gM( 'mwe-uploaderror' ),
 166+ gM( 'mwe-wgfogg_warning_bad_extension', result.upload.code.finalExt ),
 167+ buttons );
 168+ } else {
 169+ _this.ui.setPrompt(
 170+ gM( 'mwe-uploaderror' ),
 171+ gM( 'mwe-unknown-error' ) + ' : ' + error_code,
 172+ buttons );
 173+ }
 174+ return false;
 175+ }
 176+
 177+ mw.log( 'get key: ' + _this.error_msg_key[ error_code ] )
 178+ mw.getRemoteMsg( _this.error_msg_key[ error_code ], function() {
 179+ _this.ui.setPrompt(
 180+ gM( 'mwe-uploaderror' ),
 181+ gM( _this.error_msg_key[ error_code ], errorReplaceArg ),
 182+ buttons );
 183+ });
 184+ mw.log( "api.error" );
 185+ return false;
 186+ }
 187+
 188+
 189+ }
 190+ */
 191+
 192+/*
 193+ // this doesn't seem to do anything
 194+ // Check for warnings:
 195+ if ( result.upload && result.upload.warnings ) {
 196+ for ( var wtype in result.upload.warnings ) {
 197+ var winfo = result.upload.warnings[wtype]
 198+ switch ( wtype ) {
 199+ case 'duplicate':
 200+ case 'exists':
 201+ if ( winfo[1] && winfo[1].title && winfo[1].title.mTextform ) {
 202+ push warnings, { type: wtype, text: winfo[1].title.mTextform },
 203+ } else {
 204+ push warnings, { type: wtype }
 205+ }
 206+ break;
 207+ case 'file-thumbnail-no':
 208+ push warnings, { type: wtype, info: winfo }
 209+ break;
 210+ default:
 211+ push warnings: { type: wtype, }
 212+ break;
 213+ }
 214+ }
 215+
 216+ if ( result.upload.sessionkey ) {
 217+ _this.warnings_sessionkey = result.upload.sessionkey;
 218+ }
 219+
 220+ }
 221+*/
 222+/*
 223+ // Check upload.error
 224+ if ( result.upload && result.upload.error ) {
 225+ mw.log( ' result.upload.error: ' + result.upload.error );
 226+ _this.ui.setPrompt(
 227+ gM( 'mwe-uploaderror' ),
 228+ gM( 'mwe-unknown-error' ) + '<br>',
 229+ buttons );
 230+ return false;
 231+ }
 232+*/
 233+
 234+ /*
 235+ // this ONLY applies to copy by URL method -- factor out?
 236+ if ( result.upload && result.upload.upload_session_key ) {
 237+ // Async upload, do AJAX status polling
 238+ _this.upload_session_key = result.upload.upload_session_key;
 239+ _this.doAjaxUploadStatus();
 240+ mw.log( "set upload_session_key: " + _this.upload_session_key );
 241+ return;
 242+ }
 243+ */
 244+
 245+ /*
 246+ var buttons = {};
 247+ // "Return" button
 248+ buttons[ gM( 'mwe-return-to-form' ) ] = function() {
 249+ $j( this ).dialog( 'destroy' ).remove();
 250+ _this.form_post_override = false;
 251+ }
 252+ // "Go to resource" button
 253+ buttons[ gM('mwe-go-to-resource') ] = function() {
 254+ window.location = url;
 255+ };
 256+ _this.action_done = true;
 257+ _this.interface.setPrompt(
 258+ gM( 'mwe-successfulupload' ),
 259+ gM( 'mwe-upload_done', url),
 260+ buttons );
 261+ mw.log( 'result.upload.imageinfo::' + url );
 262+ return true;
 263+ */
 264+
 265+/*
 266+ // Create the "ignore warning" button
 267+ var buttons = {};
 268+ buttons[ gM( 'mwe-ignorewarning' ) ] = function() {
 269+ // Check if we have a stashed key:
 270+ if ( _this.warnings_sessionkey ) {
 271+ //set to "loading"
 272+ $j( '#upProgressDialog' ).loadingSpinner() );
 273+ //setup request:
 274+ var request = {
 275+ 'action': 'upload',
 276+ 'sessionkey': _this.warnings_sessionkey,
 277+ 'ignorewarnings': 1,
 278+ 'filename': $j( '#wpDestFile' ).val(),
 279+ 'token' : _this.editToken,
 280+ 'comment' : _this.getUploadDescription()
 281+ };
 282+ //run the upload from stash request
 283+ mw.getJSON(_this.api_url, request, function( data ) {
 284+ _this.processApiResult( data );
 285+ } );
 286+ } else {
 287+ mw.log( 'No session key re-sending upload' )
 288+
 289+
 290+ //do a stashed upload
 291+ $j( '#wpIgnoreWarning' ).attr( 'checked', true );
 292+ $j( _this.editForm ).submit();
 293+ }
 294+ };
 295+ */
 296+
 297+
Property changes on: trunk/extensions/UploadWizard/resources/mw.UploadApiProcessor.js
___________________________________________________________________
Added: svn:eol-style
1298 + native
Index: trunk/extensions/UploadWizard/resources/arrow-head.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/UploadWizard/resources/arrow-head.png
___________________________________________________________________
Added: svn:mime-type
2299 + image/png
Index: trunk/extensions/UploadWizard/resources/mw.IframeTransport.js
@@ -0,0 +1,108 @@
 2+/**
 3+ * Represents a "transport" for files to upload; in this case an iframe.
 4+ * The iframe is made to be the target of a form so that the existing page does not reload, even though it's a POST.
 5+ * @param form an HTML form
 6+ * @param progressCb callback to execute when we've started. (does not do float here because iframes can't
 7+ * monitor fractional progress).
 8+ * @param transportedCb callback to execute when we've finished the upload
 9+ */
 10+mw.IframeTransport = function( form, progressCb, transportedCb ) {
 11+ var _this = this;
 12+
 13+ _this.form = form;
 14+ _this.progressCb = progressCb;
 15+ _this.transportedCb = transportedCb;
 16+
 17+ _this.iframeId = 'f_' + ( $j( 'iframe' ).length + 1 );
 18+
 19+ //IE only works if you "create element with the name" ( not jquery style )
 20+ var iframe;
 21+ try {
 22+ iframe = document.createElement( '<iframe name="' + _this.iframeId + '">' );
 23+ } catch ( ex ) {
 24+ iframe = document.createElement( 'iframe' );
 25+ }
 26+
 27+ // we configure form on load, because the first time it loads, it's blank
 28+ // then we configure it to deal with an API submission
 29+ $j( iframe )
 30+ .attr( { 'src' : 'javascript:false;',
 31+ 'id' : _this.iframeId,
 32+ 'name' : _this.iframeId } )
 33+ .load( function() { _this.configureForm(); } )
 34+ .css( 'display', 'none' );
 35+
 36+ $j( "body" ).append( iframe );
 37+};
 38+
 39+mw.IframeTransport.prototype = {
 40+ /**
 41+ * Configure a form with a File Input so that it submits to the iframe
 42+ * Ensure callback on completion of upload
 43+ */
 44+ configureForm: function() {
 45+ mw.log( "configuring form for iframe transport" );
 46+ var _this = this;
 47+ // Set the form target to the iframe
 48+ var $jForm = $j( _this.form );
 49+ $jForm.attr( 'target', _this.iframeId );
 50+
 51+ // attach an additional handler to the form, so, when submitted, it starts showing the progress
 52+ // XXX this is lame .. there should be a generic way to indicate busy status...
 53+ $jForm.submit( function() {
 54+ mw.log( "submitting to iframe..." );
 55+ return true;
 56+ } );
 57+
 58+ // Set up the completion callback
 59+ $j( '#' + _this.iframeId ).load( function() {
 60+ mw.log( "received result in iframe" );
 61+ _this.progressCb( 1.0 );
 62+ _this.processIframeResult( $j( this ).get( 0 ) );
 63+ } );
 64+ },
 65+
 66+ /**
 67+ * Process the result of the form submission, returned to an iframe.
 68+ * This is the iframe's onload event.
 69+ *
 70+ * @param {Element} iframe iframe to extract result from
 71+ */
 72+ processIframeResult: function( iframe ) {
 73+ var _this = this;
 74+ var doc = iframe.contentDocument ? iframe.contentDocument : frames[iframe.id].document;
 75+ // Fix for Opera 9.26
 76+ if ( doc.readyState && doc.readyState != 'complete' ) {
 77+ mw.log( "not complete" );
 78+ return;
 79+ }
 80+
 81+ // Fix for Opera 9.64
 82+ if ( doc.body && doc.body.innerHTML == "false" ) {
 83+ mw.log( "no innerhtml" );
 84+ return;
 85+ }
 86+ var response;
 87+ if ( doc.XMLDocument ) {
 88+ // The response is a document property in IE
 89+ response = doc.XMLDocument;
 90+ } else if ( doc.body ) {
 91+ // Get the json string
 92+ // XXX wait... why are we grepping it out of an HTML doc? We requested jsonfm, why?
 93+ json = $j( doc.body ).find( 'pre' ).text();
 94+ mw.log( 'iframe:json::' + json );
 95+ if ( json ) {
 96+ response = window["eval"]( "( " + json + " )" );
 97+ } else {
 98+ response = {};
 99+ }
 100+ } else {
 101+ // Response is a xml document
 102+ response = doc;
 103+ }
 104+ // Process the API result
 105+ _this.transportedCb( response );
 106+ }
 107+};
 108+
 109+
Property changes on: trunk/extensions/UploadWizard/resources/mw.IframeTransport.js
___________________________________________________________________
Added: svn:eol-style
1110 + native
Index: trunk/extensions/UploadWizard/resources/jquery.tipsy.css
@@ -0,0 +1,30 @@
 2+.tipsy { padding: 5px; font-size: small; position: absolute; z-index: 100000; }
 3+ .tipsy-inner { padding: 5px 8px 4px 8px; background-color: black; color: white; max-width: 200px; text-align: center; }
 4+ .tipsy-inner { border-radius: 3px; -moz-border-radius:3px; -webkit-border-radius:3px; }
 5+ .tipsy-arrow { position: absolute; background: url('jquery.tipsy.gif') no-repeat top left; width: 9px; height: 5px; }
 6+ .tipsy-n .tipsy-arrow { top: 0; left: 50%; margin-left: -4px; }
 7+ .tipsy-nw .tipsy-arrow { top: 0; left: 10px; }
 8+ .tipsy-ne .tipsy-arrow { top: 0; right: 10px; }
 9+ .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -4px; background-position: bottom left; }
 10+ .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; background-position: bottom left; }
 11+ .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; background-position: bottom left; }
 12+ .tipsy-e .tipsy-arrow { top: 50%; margin-top: -4px; right: 0; width: 5px; height: 9px; background-position: top right; }
 13+ .tipsy-w .tipsy-arrow { top: 50%; margin-top: -4px; left: 0; width: 5px; height: 9px; }
 14+
 15+
 16+.tipsy-help .tipsy-inner { background-color: #96d8d9; color: black; }
 17+.tipsy-help .tipsy-arrow { background: url('jquery.tipsy.help.gif') }
 18+
 19+.tipsy-error .tipsy-inner { background-color: #f89c90; color: black; }
 20+.tipsy-error .tipsy-arrow { background: url('jquery.tipsy.error.gif') }
 21+
 22+.shadow {
 23+ /* offset left, top, thickness, color with alpha */
 24+ -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
 25+ -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
 26+ box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
 27+ /* IE */
 28+ filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray');
 29+ /* slightly different syntax for IE8 */
 30+ -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray')";
 31+}
Property changes on: trunk/extensions/UploadWizard/resources/jquery.tipsy.css
___________________________________________________________________
Added: svn:eol-style
132 + native
Index: trunk/extensions/UploadWizard/resources/calendar.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/UploadWizard/resources/calendar.gif
___________________________________________________________________
Added: svn:mime-type
233 + application/octet-stream
Index: trunk/extensions/UploadWizard/resources/mw.Log.js
@@ -0,0 +1,50 @@
 2+// placeholders for stuff we used to get from mwEmbed
 3+
 4+/**
 5+* Log a string msg to the console
 6+*
 7+* all mw.log statements will be removed on minification so
 8+* lots of mw.log calls will not impact performance in non debug mode
 9+*
 10+* @param {String} string String to output to console
 11+*/
 12+mw.log = function( string ) {
 13+
 14+ // Add any prepend debug strings if necessary
 15+ if ( mw.log.preAppendLog ) {
 16+ string = mw.log.preAppendLog + string;
 17+ }
 18+
 19+ if ( window.console ) {
 20+ window.console.log( string );
 21+ } else {
 22+
 23+ /**
 24+ * Old IE and non-Firebug debug: ( commented out for now )
 25+ */
 26+ var log_elm = document.getElementById('mv_js_log');
 27+ if(!log_elm) {
 28+ var body = document.getElementsByTagName("body")[0];
 29+ if (body) {
 30+ body.innerHTML = document.getElementsByTagName("body")[0].innerHTML +
 31+ '<div style="position:absolute;z-index:500;bottom:0px;left:0px;right:0px;height:100px;">'+
 32+ '<textarea id="mv_js_log" cols="120" rows="4"></textarea>'+
 33+ '</div>';
 34+ log_elm = document.getElementById('mv_js_log');
 35+ } else {
 36+ mw.logBuffered += string + "\n";
 37+ }
 38+ }
 39+ if(log_elm) {
 40+ if (mw.logBuffered.length) {
 41+ log_elm.value += mw.logBuffered;
 42+ mw.logBuffered = "";
 43+ }
 44+ log_elm.value+=string+"\n";
 45+ }
 46+
 47+ }
 48+}
 49+
 50+mw.logBuffered = "";
 51+
Property changes on: trunk/extensions/UploadWizard/resources/mw.Log.js
___________________________________________________________________
Added: svn:eol-style
152 + native
Index: trunk/extensions/UploadWizard/resources/jquery.arrowSteps.css
@@ -0,0 +1,40 @@
 2+.arrowSteps {
 3+ list-style-type: none;
 4+ list-style-image: none;
 5+ border: 1px solid #666666;
 6+ position: relative;
 7+}
 8+
 9+.arrowSteps li {
 10+ float: left;
 11+ padding: 0px;
 12+ margin: 0px;
 13+ border: 0 none;
 14+}
 15+
 16+.arrowSteps li div {
 17+ padding: 0.5em;
 18+ text-align: center;
 19+ white-space: nowrap;
 20+ overflow: hidden;
 21+}
 22+
 23+.arrowSteps li.arrow div {
 24+ background: url(inactive-arrow-divider.png) no-repeat right center;
 25+}
 26+
 27+/* applied to the element preceding the highlighted step */
 28+.arrowSteps li.arrow.tail div {
 29+ background: url(arrow-tail.png) no-repeat right center;
 30+}
 31+
 32+/* this applies to all highlighted, including the last */
 33+.arrowSteps li.head div {
 34+ background: url(arrow-head.png) no-repeat left center;
 35+ font-weight: bold;
 36+}
 37+
 38+/* this applies to all highlighted arrows except the last */
 39+.arrowSteps li.arrow.head div {
 40+ background: url(arrow-head.png) no-repeat right center;
 41+}
Property changes on: trunk/extensions/UploadWizard/resources/jquery.arrowSteps.css
___________________________________________________________________
Added: svn:eol-style
142 + native
Index: trunk/extensions/UploadWizard/resources/mw.LanguageUpWiz.js
@@ -0,0 +1,490 @@
 2+mw.addMessages({
 3+ "mwe-upwiz-code-unknown": "Unknown language"
 4+});
 5+
 6+/**
 7+ * Utility class which knows about languages, and how to construct HTML to select them
 8+ * TODO: make this a more common library, used by this and TimedText
 9+ */
 10+mw.LanguageUpWiz = {
 11+
 12+ defaultCode: 'en', // when we absolutely have no idea what language to preselect
 13+
 14+ initialized: false,
 15+
 16+ UNKNOWN: 'unknown',
 17+
 18+ /**
 19+ * List of all languages mediaWiki supports ( Avoid an api call to get this same info )
 20+ * http://commons.wikimedia.org/w/api.php?action=query&meta=siteinfo&siprop=languages&format=jsonfm
 21+ *
 22+ * Languages sorted by name, using tools in $SVNROOT/mediawiki/trunk/tools/langcodes
 23+ * This is somewhat better than sorting by code (which produces totally bizarre results) but is not
 24+ * a true lexicographic sort
 25+ */
 26+ languages: [
 27+ { code: "ace", text: "Ac\u00e8h" },
 28+ { code: "af", text: "Afrikaans" },
 29+ { code: "ak", text: "Akan" },
 30+ { code: "als", text: "Alemannisch" }, // XXX someone fix this please
 31+ { code: "gsw", text: "Alemannisch" }, //
 32+ { code: "ang", text: "Anglo-Saxon" },
 33+ { code: "an", text: "Aragon\u00e9s" },
 34+ { code: "roa-rup", text: "Arm\u00e3neashce" },
 35+ { code: "frp", text: "Arpetan" },
 36+ { code: "ast", text: "Asturianu" },
 37+ { code: "gn", text: "Ava\u00f1e'\u1ebd" },
 38+ { code: "ay", text: "Aymar aru" },
 39+ { code: "az", text: "Az\u0259rbaycan" },
 40+ { code: "id", text: "Bahasa Indonesia" },
 41+ { code: "ms", text: "Bahasa Melayu" },
 42+ { code: "bm", text: "Bamanankan" },
 43+ { code: "map-bms", text: "Basa Banyumasan" },
 44+ { code: "jv", text: "Basa Jawa" },
 45+ { code: "su", text: "Basa Sunda" },
 46+ { code: "bcl", text: "Bikol Central" },
 47+ { code: "bi", text: "Bislama" },
 48+ { code: "bar", text: "Boarisch" },
 49+ { code: "bs", text: "Bosanski" },
 50+ { code: "br", text: "Brezhoneg" },
 51+ { code: "en-gb", text: "British English" },
 52+ { code: "nan", text: "B\u00e2n-l\u00e2m-g\u00fa" },
 53+ { code: "zh-min-nan", text: "B\u00e2n-l\u00e2m-g\u00fa" },
 54+ { code: "ca", text: "Catal\u00e0" },
 55+ { code: "ceb", text: "Cebuano" },
 56+ { code: "ch", text: "Chamoru" },
 57+ { code: "cbk-zam", text: "Chavacano de Zamboanga" },
 58+ { code: "ny", text: "Chi-Chewa" },
 59+ { code: "cho", text: "Choctaw" },
 60+ { code: "sei", text: "Cmique Itom" },
 61+ { code: "co", text: "Corsu" },
 62+ { code: "cy", text: "Cymraeg" },
 63+ { code: "da", text: "Dansk" },
 64+ { code: "dk", text: "Dansk (deprecated:da)" }, // XXX deprecated?
 65+ { code: "pdc", text: "Deitsch" },
 66+ { code: "de", text: "Deutsch" },
 67+ { code: "de-formal", text: "Deutsch (Sie-Form)" },
 68+ { code: "nv", text: "Din\u00e9 bizaad" },
 69+ { code: "dsb", text: "Dolnoserbski" },
 70+ { code: "na", text: "Dorerin Naoero" },
 71+ { code: "mh", text: "Ebon" },
 72+ { code: "et", text: "Eesti" },
 73+ { code: "eml", text: "Emili\u00e0n e rumagn\u00f2l" },
 74+ { code: "en", text: "English" },
 75+ { code: "es", text: "Espa\u00f1ol" },
 76+ { code: "eo", text: "Esperanto" },
 77+ { code: "ext", text: "Estreme\u00f1u" },
 78+ { code: "eu", text: "Euskara" },
 79+ { code: "ee", text: "E\u028begbe" },
 80+ { code: "hif", text: "Fiji Hindi" }, // XXX fix this
 81+ { code: "hif-latn", text: "Fiji Hindi" }, //
 82+ { code: "fr", text: "Fran\u00e7ais" },
 83+ { code: "frc", text: "Fran\u00e7ais canadien" },
 84+ { code: "fy", text: "Frysk" },
 85+ { code: "ff", text: "Fulfulde" },
 86+ { code: "fur", text: "Furlan" },
 87+ { code: "fo", text: "F\u00f8royskt" },
 88+ { code: "ga", text: "Gaeilge" },
 89+ { code: "gv", text: "Gaelg" },
 90+ { code: "sm", text: "Gagana Samoa" },
 91+ { code: "gag", text: "Gagauz" },
 92+ { code: "gl", text: "Galego" },
 93+ { code: "aln", text: "Geg\u00eb" },
 94+ { code: "gd", text: "G\u00e0idhlig" },
 95+ { code: "ki", text: "G\u0129k\u0169y\u0169" },
 96+ { code: "hak", text: "Hak-k\u00e2-fa" },
 97+ { code: "haw", text: "Hawai`i" },
 98+ { code: "ho", text: "Hiri Motu" },
 99+ { code: "hsb", text: "Hornjoserbsce" },
 100+ { code: "hr", text: "Hrvatski" },
 101+ { code: "io", text: "Ido" },
 102+ { code: "ig", text: "Igbo" },
 103+ { code: "ilo", text: "Ilokano" },
 104+ { code: "hil", text: "Ilonggo" },
 105+ { code: "ia", text: "Interlingua" },
 106+ { code: "ie", text: "Interlingue" },
 107+ { code: "it", text: "Italiano" },
 108+ { code: "ik", text: "I\u00f1upiak" },
 109+ { code: "jut", text: "Jysk" },
 110+ { code: "kl", text: "Kalaallisut" },
 111+ { code: "kr", text: "Kanuri" },
 112+ { code: "pam", text: "Kapampangan" },
 113+ { code: "csb", text: "Kasz\u00ebbsczi" },
 114+ { code: "kw", text: "Kernowek" },
 115+ { code: "krj", text: "Kinaray-a" },
 116+ { code: "rw", text: "Kinyarwanda" },
 117+ { code: "rn", text: "Kirundi" },
 118+ { code: "sw", text: "Kiswahili" },
 119+ { code: "kg", text: "Kongo" },
 120+ { code: "avk", text: "Kotava" },
 121+ { code: "ht", text: "Krey\u00f2l ayisyen" },
 122+ { code: "kri", text: "Krio" },
 123+ { code: "ku", text: "Kurd\u00ee \/ \u0643\u0648\u0631\u062f\u06cc" },
 124+ { code: "kiu", text: "Kurmanc\u00ee" },
 125+ { code: "kj", text: "Kwanyama" },
 126+ { code: "lad", text: "Ladino" },
 127+ { code: "la", text: "Latina" },
 128+ { code: "lv", text: "Latvie\u0161u" },
 129+ { code: "lt", text: "Lietuvi\u0173" },
 130+ { code: "li", text: "Limburgs" },
 131+ { code: "lfn", text: "Lingua Franca Nova" },
 132+ { code: "ln", text: "Ling\u00e1la" },
 133+ { code: "jbo", text: "Lojban" },
 134+ { code: "lg", text: "Luganda" },
 135+ { code: "lmo", text: "Lumbaart" },
 136+ { code: "lb", text: "L\u00ebtzebuergesch" },
 137+ { code: "lij", text: "L\u00edguru" },
 138+ { code: "hu", text: "Magyar" },
 139+ { code: "mg", text: "Malagasy" },
 140+ { code: "mt", text: "Malti" },
 141+ { code: "arn", text: "Mapudungun" },
 142+ { code: "mwl", text: "Mirand\u00e9s" },
 143+ { code: "mus", text: "Mvskoke" },
 144+ { code: "cdo", text: "M\u00ecng-d\u0115\u0324ng-ng\u1e73\u0304" },
 145+ { code: "mi", text: "M\u0101ori" },
 146+ { code: "fj", text: "Na Vosa Vakaviti" },
 147+ { code: "nl", text: "Nederlands" },
 148+ { code: "nds-nl", text: "Nedersaksisch" },
 149+ { code: "niu", text: "Niu\u0113" },
 150+ { code: "nap", text: "Nnapulitano" },
 151+ { code: "pih", text: "Norfuk \/ Pitkern" },
 152+ { code: "nb", text: "Norsk (bokm\u00e5l)" },
 153+ { code: "no", text: "Norsk (bokm\u00e5l)" },
 154+ { code: "nn", text: "Norsk (nynorsk)" },
 155+ { code: "nrm", text: "Nouormand" },
 156+ { code: "nov", text: "Novial" },
 157+ { code: "nah", text: "N\u0101huatl" },
 158+ { code: "cr", text: "N\u0113hiyaw\u0113win \/ \u14c0\u1426\u1403\u152d\u140d\u140f\u1423" },
 159+ { code: "uz", text: "O'zbek" },
 160+ { code: "oc", text: "Occitan" },
 161+ { code: "om", text: "Oromoo" },
 162+ { code: "ng", text: "Oshiwambo" },
 163+ { code: "hz", text: "Otsiherero" },
 164+ { code: "pag", text: "Pangasinan" },
 165+ { code: "pap", text: "Papiamentu" },
 166+ { code: "pfl", text: "Pf\u00e4lzisch" },
 167+ { code: "pcd", text: "Picard" },
 168+ { code: "pms", text: "Piemont\u00e8is" },
 169+ { code: "nds", text: "Plattd\u00fc\u00fctsch" },
 170+ { code: "pdt", text: "Plautdietsch" },
 171+ { code: "pl", text: "Polski" },
 172+ { code: "pt", text: "Portugu\u00eas" },
 173+ { code: "pt-br", text: "Portugu\u00eas do Brasil" },
 174+ { code: "aa", text: "Qaf\u00e1r af" },
 175+ { code: "kaa", text: "Qaraqalpaqsha" },
 176+ { code: "crh", text: "Q\u0131r\u0131mtatarca" },
 177+ { code: "ty", text: "Reo M\u0101`ohi" },
 178+ { code: "ksh", text: "Ripoarisch" },
 179+ { code: "rmy", text: "Romani" },
 180+ { code: "ro", text: "Rom\u00e2n\u0103" },
 181+ { code: "rm", text: "Rumantsch" },
 182+ { code: "qu", text: "Runa Simi" },
 183+ { code: "sc", text: "Sardu" },
 184+ { code: "sdc", text: "Sassaresu" },
 185+ { code: "sli", text: "Schl\u00e4sch" },
 186+ { code: "de-ch", text: "Schweizer Hochdeutsch" },
 187+ { code: "sco", text: "Scots" },
 188+ { code: "stq", text: "Seeltersk" },
 189+ { code: "st", text: "Sesotho" },
 190+ { code: "nso", text: "Sesotho sa Leboa" },
 191+ { code: "tn", text: "Setswana" },
 192+ { code: "sq", text: "Shqip" },
 193+ { code: "ss", text: "SiSwati" },
 194+ { code: "scn", text: "Sicilianu" },
 195+ { code: "loz", text: "Silozi" },
 196+ { code: "simple", text: "Simple English" },
 197+ { code: "sk", text: "Sloven\u010dina" },
 198+ { code: "sl", text: "Sloven\u0161\u010dina" },
 199+ { code: "so", text: "Soomaaliga" },
 200+ { code: "ckb", text: "Soran\u00ee \/ \u06a9\u0648\u0631\u062f\u06cc" },
 201+ { code: "srn", text: "Sranantongo" },
 202+ { code: "sr-el", text: "Srpski (latinica)" },
 203+ { code: "sh", text: "Srpskohrvatski \/ \u0421\u0440\u043f\u0441\u043a\u043e\u0445\u0440\u0432\u0430\u0442\u0441\u043a\u0438" },
 204+ { code: "fi", text: "Suomi" },
 205+ { code: "sv", text: "Svenska" },
 206+ { code: "se", text: "S\u00e1megiella" },
 207+ { code: "sg", text: "S\u00e4ng\u00f6" },
 208+ { code: "tl", text: "Tagalog" },
 209+ { code: "kab", text: "Taqbaylit" },
 210+ { code: "roa-tara", text: "Tarand\u00edne" },
 211+ { code: "rif", text: "Tarifit" },
 212+ { code: "tt-latn", text: "Tatar\u00e7a" },
 213+ { code: "shi", text: "Ta\u0161l\u1e25iyt" },
 214+ { code: "tet", text: "Tetun" },
 215+ { code: "vi", text: "Ti\u1ebfng Vi\u1ec7t" },
 216+ { code: "tpi", text: "Tok Pisin" },
 217+ { code: "tokipona", text: "Toki Pona" },
 218+ { code: "tp", text: "Toki Pona (deprecated:tokipona)" }, // XXX deprecated?
 219+ { code: "chy", text: "Tsets\u00eahest\u00e2hese" },
 220+ { code: "ve", text: "Tshivenda" },
 221+ { code: "tw", text: "Twi" },
 222+ { code: "tk", text: "T\u00fcrkmen\u00e7e" },
 223+ { code: "tr", text: "T\u00fcrk\u00e7e" },
 224+ { code: "ug-latn", text: "Uyghurche\u200e" },
 225+ { code: "ug", text: "Uyghurche\u200e \/ \u0626\u06c7\u064a\u063a\u06c7\u0631\u0686\u06d5" },
 226+ { code: "za", text: "Vahcuengh" },
 227+ { code: "vep", text: "Vepsan kel'" },
 228+ { code: "ruq", text: "Vl\u0103he\u015fte" },
 229+ { code: "ruq-latn", text: "Vl\u0103he\u015fte" },
 230+ { code: "vo", text: "Volap\u00fck" },
 231+ { code: "vec", text: "V\u00e8neto" },
 232+ { code: "fiu-vro", text: "V\u00f5ro" },
 233+ { code: "vro", text: "V\u00f5ro" },
 234+ { code: "wa", text: "Walon" },
 235+ { code: "vls", text: "West-Vlams" },
 236+ { code: "war", text: "Winaray" },
 237+ { code: "wo", text: "Wolof" },
 238+ { code: "ts", text: "Xitsonga" },
 239+ { code: "yo", text: "Yor\u00f9b\u00e1" },
 240+ { code: "diq", text: "Zazaki" },
 241+ { code: "zea", text: "Ze\u00eauws" },
 242+ { code: "sn", text: "chiShona" },
 243+ { code: "tum", text: "chiTumbuka" },
 244+ { code: "ike-latn", text: "inuktitut" },
 245+ { code: "xh", text: "isiXhosa" },
 246+ { code: "zu", text: "isiZulu" },
 247+ { code: "to", text: "lea faka-Tonga" },
 248+ { code: "tg-latn", text: "tojik\u012b" },
 249+ { code: "is", text: "\u00cdslenska" },
 250+ { code: "de-at", text: "\u00d6sterreichisches Deutsch" },
 251+ { code: "szl", text: "\u015al\u016fnski" },
 252+ { code: "el", text: "\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac" },
 253+ { code: "pnt", text: "\u03a0\u03bf\u03bd\u03c4\u03b9\u03b1\u03ba\u03ac" },
 254+ { code: "av", text: "\u0410\u0432\u0430\u0440" },
 255+ { code: "ab", text: "\u0410\u04a7\u0441\u0443\u0430" },
 256+ { code: "ba", text: "\u0411\u0430\u0448\u04a1\u043e\u0440\u0442" },
 257+ { code: "be", text: "\u0411\u0435\u043b\u0430\u0440\u0443\u0441\u043a\u0430\u044f" },
 258+ { code: "be-tarask", text: "\u0411\u0435\u043b\u0430\u0440\u0443\u0441\u043a\u0430\u044f (\u0442\u0430\u0440\u0430\u0448\u043a\u0435\u0432\u0456\u0446\u0430)" },
 259+ { code: "be-x-old", text: "\u0411\u0435\u043b\u0430\u0440\u0443\u0441\u043a\u0430\u044f (\u0442\u0430\u0440\u0430\u0448\u043a\u0435\u0432\u0456\u0446\u0430)" },
 260+ { code: "bxr", text: "\u0411\u0443\u0440\u044f\u0430\u0434" },
 261+ { code: "bg", text: "\u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438" },
 262+ { code: "ruq-cyrl", text: "\u0412\u043b\u0430\u0445\u0435\u0441\u0442\u0435" },
 263+ { code: "inh", text: "\u0413\u0406\u0430\u043b\u0433\u0406\u0430\u0439 \u011eal\u011faj" },
 264+ { code: "os", text: "\u0418\u0440\u043e\u043d\u0430\u0443" },
 265+ { code: "kv", text: "\u041a\u043e\u043c\u0438" },
 266+ { code: "ky", text: "\u041a\u044b\u0440\u0433\u044b\u0437\u0447\u0430" },
 267+ { code: "lbe", text: "\u041b\u0430\u043a\u043a\u0443" },
 268+ { code: "lez", text: "\u041b\u0435\u0437\u0433\u0438" },
 269+ { code: "mk", text: "\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438" },
 270+ { code: "mdf", text: "\u041c\u043e\u043a\u0448\u0435\u043d\u044c" },
 271+ { code: "mo", text: "\u041c\u043e\u043b\u0434\u043e\u0432\u0435\u043d\u044f\u0441\u043a\u044d" },
 272+ { code: "mn", text: "\u041c\u043e\u043d\u0433\u043e\u043b" },
 273+ { code: "ce", text: "\u041d\u043e\u0445\u0447\u0438\u0439\u043d" },
 274+ { code: "mhr", text: "\u041e\u043b\u044b\u043a \u041c\u0430\u0440\u0438\u0439" },
 275+ { code: "ru", text: "\u0420\u0443\u0441\u0441\u043a\u0438\u0439" },
 276+ { code: "sah", text: "\u0421\u0430\u0445\u0430 \u0442\u044b\u043b\u0430" },
 277+ { code: "cu", text: "\u0421\u043b\u043e\u0432\u0463\u0301\u043d\u044c\u0441\u043a\u044a \/ \u2c14\u2c0e\u2c11\u2c02\u2c21\u2c10\u2c20\u2c14\u2c0d\u2c1f" },
 278+ { code: "sr-ec", text: "\u0421\u0440\u043f\u0441\u043a\u0438 (\u045b\u0438\u0440\u0438\u043b\u0438\u0446\u0430)" },
 279+ { code: "sr", text: "\u0421\u0440\u043f\u0441\u043a\u0438 \/ Srpski" },
 280+ { code: "tt-cyrl", text: "\u0422\u0430\u0442\u0430\u0440\u0447\u0430" },
 281+ { code: "tt", text: "\u0422\u0430\u0442\u0430\u0440\u0447\u0430\/Tatar\u00e7a" },
 282+ { code: "tg", text: "\u0422\u043e\u04b7\u0438\u043a\u04e3" },
 283+ { code: "tg-cyrl", text: "\u0422\u043e\u04b7\u0438\u043a\u04e3" },
 284+ { code: "tyv", text: "\u0422\u044b\u0432\u0430 \u0434\u044b\u043b" },
 285+ { code: "udm", text: "\u0423\u0434\u043c\u0443\u0440\u0442" },
 286+ { code: "uk", text: "\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430" },
 287+ { code: "xal", text: "\u0425\u0430\u043b\u044c\u043c\u0433" },
 288+ { code: "cv", text: "\u0427\u04d1\u0432\u0430\u0448\u043b\u0430" },
 289+ { code: "myv", text: "\u042d\u0440\u0437\u044f\u043d\u044c" },
 290+ { code: "kk", text: "\u049a\u0430\u0437\u0430\u049b\u0448\u0430" },
 291+ { code: "hy", text: "\u0540\u0561\u0575\u0565\u0580\u0565\u0576" },
 292+ { code: "yi", text: "\u05d9\u05d9\u05b4\u05d3\u05d9\u05e9" },
 293+ { code: "he", text: "\u05e2\u05d1\u05e8\u05d9\u05ea" },
 294+ { code: "ug-arab", text: "\u0626\u06c7\u064a\u063a\u06c7\u0631\u0686\u06d5" },
 295+ { code: "ur", text: "\u0627\u0631\u062f\u0648" },
 296+ { code: "ar", text: "\u0627\u0644\u0639\u0631\u0628\u064a\u0629" },
 297+ { code: "bqi", text: "\u0628\u062e\u062a\u064a\u0627\u0631\u064a" },
 298+ { code: "bcc", text: "\u0628\u0644\u0648\u0686\u06cc \u0645\u06a9\u0631\u0627\u0646\u06cc" },
 299+ { code: "sd", text: "\u0633\u0646\u068c\u064a" },
 300+ { code: "fa", text: "\u0641\u0627\u0631\u0633\u06cc" },
 301+ { code: "arz", text: "\u0645\u0635\u0631\u0649" },
 302+ { code: "mzn", text: "\u0645\u064e\u0632\u0650\u0631\u0648\u0646\u064a" },
 303+ { code: "ha", text: "\u0647\u064e\u0648\u064f\u0633\u064e" },
 304+ { code: "pnb", text: "\u067e\u0646\u062c\u0627\u0628\u06cc" },
 305+ { code: "ps", text: "\u067e\u069a\u062a\u0648" },
 306+ { code: "glk", text: "\u06af\u06cc\u0644\u06a9\u06cc" },
 307+ { code: "arc", text: "\u0710\u072a\u0721\u071d\u0710" },
 308+ { code: "dv", text: "\u078b\u07a8\u0788\u07ac\u0780\u07a8\u0784\u07a6\u0790\u07b0" },
 309+ { code: "ks", text: "\u0915\u0936\u094d\u092e\u0940\u0930\u0940 - (\u0643\u0634\u0645\u064a\u0631\u064a)" },
 310+ { code: "new", text: "\u0928\u0947\u092a\u093e\u0932 \u092d\u093e\u0937\u093e" },
 311+ { code: "ne", text: "\u0928\u0947\u092a\u093e\u0932\u0940" },
 312+ { code: "pi", text: "\u092a\u093e\u093f\u0934" },
 313+ { code: "hif-deva", text: "\u092b\u093c\u0940\u091c\u0940 \u0939\u093f\u0928\u094d\u0926\u0940" },
 314+ { code: "bh", text: "\u092d\u094b\u091c\u092a\u0941\u0930\u0940" },
 315+ { code: "mr", text: "\u092e\u0930\u093e\u0920\u0940" },
 316+ { code: "mai", text: "\u092e\u0948\u0925\u093f\u0932\u0940" },
 317+ { code: "sa", text: "\u0938\u0902\u0938\u094d\u0915\u0943\u0924" },
 318+ { code: "hi", text: "\u0939\u093f\u0928\u094d\u0926\u0940" },
 319+ { code: "as", text: "\u0985\u09b8\u09ae\u09c0\u09af\u09bc\u09be" },
 320+ { code: "bpy", text: "\u0987\u09ae\u09be\u09b0 \u09a0\u09be\u09b0\/\u09ac\u09bf\u09b7\u09cd\u09a3\u09c1\u09aa\u09cd\u09b0\u09bf\u09af\u09bc\u09be \u09ae\u09a3\u09bf\u09aa\u09c1\u09b0\u09c0" },
 321+ { code: "bn", text: "\u09ac\u09be\u0982\u09b2\u09be" },
 322+ { code: "pa", text: "\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40" },
 323+ { code: "gu", text: "\u0a97\u0ac1\u0a9c\u0ab0\u0abe\u0aa4\u0ac0" },
 324+ { code: "or", text: "\u0b13\u0b21\u0b3c\u0b3f\u0b06" },
 325+ { code: "ta", text: "\u0ba4\u0bae\u0bbf\u0bb4\u0bcd" },
 326+ { code: "te", text: "\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41" },
 327+ { code: "sma", text: "\u00c5arjelsaemien" },
 328+ { code: "kn", text: "\u0c95\u0ca8\u0ccd\u0ca8\u0ca1" },
 329+ { code: "tcy", text: "\u0ca4\u0cc1\u0cb3\u0cc1" },
 330+ { code: "ml", text: "\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02" },
 331+ { code: "si", text: "\u0dc3\u0dd2\u0d82\u0dc4\u0dbd" },
 332+ { code: "th", text: "\u0e44\u0e17\u0e22" },
 333+ { code: "lo", text: "\u0ea5\u0eb2\u0ea7" },
 334+ { code: "dz", text: "\u0f47\u0f7c\u0f44\u0f0b\u0f41" },
 335+ { code: "bo", text: "\u0f56\u0f7c\u0f51\u0f0b\u0f61\u0f72\u0f42" },
 336+ { code: "my", text: "\u1019\u103c\u1014\u103a\u1019\u102c\u1018\u102c\u101e\u102c" },
 337+ { code: "cs", text: "\u010cesky" },
 338+ { code: "xmf", text: "\u10db\u10d0\u10e0\u10d2\u10d0\u10da\u10e3\u10e0\u10d8" },
 339+ { code: "ka", text: "\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8" },
 340+ { code: "ti", text: "\u1275\u130d\u122d\u129b" },
 341+ { code: "am", text: "\u12a0\u121b\u122d\u129b" },
 342+ { code: "chr", text: "\u13e3\u13b3\u13a9" },
 343+ { code: "ike-cans", text: "\u1403\u14c4\u1483\u144e\u1450\u1466" },
 344+ { code: "iu", text: "\u1403\u14c4\u1483\u144e\u1450\u1466\/inuktitut" },
 345+ { code: "km", text: "\u1797\u17b6\u179f\u17b6\u1781\u17d2\u1798\u17c2\u179a" },
 346+ { code: "bat-smg", text: "\u017demait\u0117\u0161ka" },
 347+ { code: "bug", text: "\u1a05\u1a14 \u1a15\u1a18\u1a01\u1a17" },
 348+ { code: "grc", text: "\u1f08\u03c1\u03c7\u03b1\u03af\u03b1 \u1f11\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u1f74" },
 349+ { code: "ku-latn", text: "\u202aKurd\u00ee (lat\u00een\u00ee)\u202c" },
 350+ { code: "kk-tr", text: "\u202aQazaq\u015fa (T\u00fcrk\u00efya)\u202c" },
 351+ { code: "kk-latn", text: "\u202aQazaq\u015fa (lat\u0131n)\u202c" },
 352+ { code: "crh-latn", text: "\u202aQ\u0131r\u0131mtatarca (Latin)\u202c" },
 353+ { code: "crh-cyrl", text: "\u202a\u041a\u044a\u044b\u0440\u044b\u043c\u0442\u0430\u0442\u0430\u0440\u0434\u0436\u0430 (\u041a\u0438\u0440\u0438\u043b\u043b)\u202c" },
 354+ { code: "kk-cyrl", text: "\u202a\u049a\u0430\u0437\u0430\u049b\u0448\u0430 (\u043a\u0438\u0440\u0438\u043b)\u202c" },
 355+ { code: "kk-kz", text: "\u202a\u049a\u0430\u0437\u0430\u049b\u0448\u0430 (\u049a\u0430\u0437\u0430\u049b\u0441\u0442\u0430\u043d)\u202c" },
 356+ { code: "kk-arab", text: "\u202b\u0642\u0627\u0632\u0627\u0642\u0634\u0627 (\u062a\u0674\u0648\u062a\u06d5)\u202c" },
 357+ { code: "kk-cn", text: "\u202b\u0642\u0627\u0632\u0627\u0642\u0634\u0627 (\u062c\u06c7\u0646\u06af\u0648)\u202c" },
 358+ { code: "ku-arab", text: "\u202b\u0643\u0648\u0631\u062f\u064a (\u0639\u06d5\u0631\u06d5\u0628\u06cc)\u202c" },
 359+ { code: "zh", text: "\u4e2d\u6587" },
 360+ { code: "zh-cn", text: "\u4e2d\u6587(\u4e2d\u56fd\u5927\u9646)" },
 361+ { code: "zh-tw", text: "\u4e2d\u6587(\u53f0\u7063)" },
 362+ { code: "zh-sg", text: "\u4e2d\u6587(\u65b0\u52a0\u5761)" },
 363+ { code: "zh-mo", text: "\u4e2d\u6587(\u6fb3\u9580)" },
 364+ { code: "zh-hans", text: "\u4e2d\u6587(\u7b80\u4f53)" },
 365+ { code: "zh-hant", text: "\u4e2d\u6587(\u7e41\u9ad4)" },
 366+ { code: "zh-hk", text: "\u4e2d\u6587(\u9999\u6e2f)" },
 367+ { code: "zh-my", text: "\u4e2d\u6587(\u9a6c\u6765\u897f\u4e9a)" },
 368+ { code: "wuu", text: "\u5434\u8bed" },
 369+ { code: "lzh", text: "\u6587\u8a00" },
 370+ { code: "zh-classical", text: "\u6587\u8a00" },
 371+ { code: "ja", text: "\u65e5\u672c\u8a9e" },
 372+ { code: "yue", text: "\u7cb5\u8a9e" },
 373+ { code: "zh-yue", text: "\u7cb5\u8a9e" },
 374+ { code: "gan", text: "\u8d1b\u8a9e" },
 375+ { code: "gan-hant", text: "\u8d1b\u8a9e(\u7e41\u9ad4)" },
 376+ { code: "gan-hans", text: "\u8d63\u8bed(\u7b80\u4f53)" },
 377+ { code: "ii", text: "\ua187\ua259" },
 378+ { code: "ko", text: "\ud55c\uad6d\uc5b4" },
 379+ { code: "ko-kp", text: "\ud55c\uad6d\uc5b4 (\uc870\uc120)" },
 380+ { code: "got", text: "\ud800\udf32\ud800\udf3f\ud800\udf44\ud800\udf39\ud800\udf43\ud800\udf3a" }
 381+ ],
 382+
 383+ /**
 384+ * cache some useful objects
 385+ * 1) mostly ready-to-go language HTML menu. When/if we upgrade, make it a jQuery combobox
 386+ * 2) dict of language code to name -- useful for testing for existence, maybe other things.
 387+ */
 388+ initialize: function() {
 389+ if ( mw.LanguageUpWiz.initialized ) {
 390+ return;
 391+ }
 392+ mw.LanguageUpWiz._codes = {};
 393+ var select = $j( '<select/>' );
 394+ $j.each( mw.LanguageUpWiz.languages, function( i, language ) {
 395+ select.append(
 396+ $j( '<option>' )
 397+ .attr( 'value', language.code )
 398+ .append( language.text )
 399+ );
 400+ mw.LanguageUpWiz._codes[language.code] = language.text;
 401+ } );
 402+ mw.LanguageUpWiz.$_select = select;
 403+ mw.LanguageUpWiz.initialized = true;
 404+ },
 405+
 406+ /**
 407+ * Get an HTML select menu of all our languages.
 408+ * @param name desired name of select element
 409+ * @param code desired default language code
 410+ * @return HTML select element configured as desired
 411+ */
 412+ getMenu: function( name, code ) {
 413+ mw.LanguageUpWiz.initialize();
 414+ var $select = mw.LanguageUpWiz.$_select.clone();
 415+ $select.attr( 'name', name );
 416+ if ( code === mw.LanguageUpWiz.UNKNOWN ) {
 417+ // n.b. MediaWiki LanguageHandler has ability to add custom label for 'Unknown'; possibly as pseudo-label
 418+ $select.prepend( $j( '<option>' ).attr( 'value', mw.LanguageUpWiz.UNKNOWN ).append( gM( 'mwe-upwiz-code-unknown' )) );
 419+ $select.val( mw.LanguageUpWiz.UNKNOWN );
 420+ } else if ( code !== undefined ) {
 421+ $select.val( mw.LanguageUpWiz.getClosest( code ));
 422+ }
 423+ return $select.get( 0 );
 424+ },
 425+
 426+ /**
 427+ * Figure out the closest language we have to a supplied language code.
 428+ * It seems that people on Mediawiki set their language code as freetext, and it could be anything, even
 429+ * variants we don't have a record for, or ones that are not in any ISO standard.
 430+ *
 431+ * Logic copied from MediaWiki:LanguageHandler.js
 432+ * handle null cases, special cases for some Chinese variants
 433+ * Otherwise, if handed "foo-bar-baz" language, try to match most specific language,
 434+ * "foo-bar-baz", then "foo-bar", then "foo"
 435+ *
 436+ * @param code A string representing a language code, which we may or may not have.
 437+ * Expected to be separated with dashes as codes from ISO 639, e.g. "zh-tw" for Chinese ( Traditional )
 438+ * @return a language code which is close to the supplied parameter, or fall back to mw.LanguageUpWiz.defaultCode
 439+ */
 440+ getClosest: function( code ) {
 441+ mw.LanguageUpWiz.initialize();
 442+ if ( typeof ( code ) != 'string' || code === null || code.length === 0 ) {
 443+ return mw.LanguageUpWiz.defaultCode;
 444+ }
 445+ if ( code == 'nan' || code == 'minnan' ) {
 446+ return 'zh-min-nan';
 447+ } else if ( mw.LanguageUpWiz._codes[code] !== undefined ) {
 448+ return code;
 449+ }
 450+ return mw.LanguageUpWiz.getClosest( code.substring( 0, code.indexOf( '-' )) );
 451+ }
 452+
 453+
 454+ // enhance a simple text input to be an autocompleting language menu
 455+ // this will work when/if we move to jQuery 1.4. As of now the autocomplete is too underpowered for our needs without
 456+ // serious hackery
 457+ /*
 458+ $j.fn.languageMenu = function( options ) {
 459+ var _this = this;
 460+ _this.autocomplete( null, {
 461+ minChars: 0,
 462+ width: 310,
 463+ selectFirst: true,
 464+ autoFill: true,
 465+ mustMatch: true,
 466+ matchContains: false,
 467+ highlightItem: true,
 468+ scroll: true,
 469+ scrollHeight: 220,
 470+ formatItem: function( row, i, max, term ) {
 471+ return row.code + " " + row.code;
 472+ },
 473+ formatMatch: function( row, i, max, term ) {
 474+ return row.code + " " + row.code;
 475+ },
 476+ formatResult: function( row ) {
 477+ return row.code;
 478+ }
 479+ }, mw.Languages );
 480+
 481+ // and add a dropdown so we can see the thingy, too
 482+ return _this;
 483+ };
 484+ */
 485+
 486+ // XXX the concept of "internal language" exists in UploadForm.js -- seems to be how they handled i18n, with
 487+ // language codes that has underscores rather than dashes, ( "en_gb" rather than the correct "en-gb" ).
 488+ // although other info such as Information boxes was recorded correctly.
 489+ // This is presumed not to apply to the shiny new world of JS2, where i18n is handled in other ways.
 490+
 491+};
Property changes on: trunk/extensions/UploadWizard/resources/mw.LanguageUpWiz.js
___________________________________________________________________
Added: svn:mergeinfo
1492 Merged /branches/sqlite/js2/mwEmbed/modules/UploadWizard/mw.Language.js:r58211-58321
2493 Merged /branches/REL1_15/phase3/js2/mwEmbed/modules/UploadWizard/mw.Language.js:r51646
Added: svn:eol-style
3494 + native
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.tipsy.js
@@ -0,0 +1,201 @@
 2+// tipsy, facebook style tooltips for jquery
 3+// version 1.0.0a
 4+// (c) 2008-2010 jason frame [jason@onehackoranother.com]
 5+// releated under the MIT license
 6+
 7+(function($) {
 8+
 9+ function fixTitle($ele) {
 10+ if ($ele.attr('title') || typeof($ele.attr('original-title')) != 'string') {
 11+ $ele.attr('original-title', $ele.attr('title') || '').removeAttr('title');
 12+ }
 13+ }
 14+
 15+ function Tipsy(element, options) {
 16+ this.$element = $(element);
 17+ this.options = options;
 18+ this.enabled = true;
 19+ fixTitle(this.$element);
 20+ }
 21+
 22+ Tipsy.prototype = {
 23+ show: function() {
 24+ var title = this.getTitle();
 25+ if (title && this.enabled) {
 26+ var $tip = this.tip();
 27+ $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
 28+ // $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
 29+ // the remove strips events
 30+ //$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
 31+ $tip.css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
 32+
 33+ var pos = $.extend({}, this.$element.offset(), {
 34+ width: this.$element[0].offsetWidth,
 35+ height: this.$element[0].offsetHeight
 36+ });
 37+
 38+ var actualWidth = $tip[0].offsetWidth, actualHeight = $tip[0].offsetHeight;
 39+ var gravity = (typeof this.options.gravity == 'function')
 40+ ? this.options.gravity.call(this.$element[0])
 41+ : this.options.gravity;
 42+
 43+ var tp;
 44+ switch (gravity.charAt(0)) {
 45+ case 'n':
 46+ tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
 47+ break;
 48+ case 's':
 49+ tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
 50+ break;
 51+ case 'e':
 52+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
 53+ break;
 54+ case 'w':
 55+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
 56+ break;
 57+ }
 58+
 59+ if (gravity.length == 2) {
 60+ if (gravity.charAt(1) == 'w') {
 61+ tp.left = pos.left + pos.width / 2 - 15;
 62+ } else {
 63+ tp.left = pos.left + pos.width / 2 - actualWidth + 15;
 64+ }
 65+ }
 66+
 67+ $tip.css(tp).addClass('tipsy-' + gravity);
 68+
 69+ if (this.options.fade) {
 70+ $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
 71+ } else {
 72+ $tip.css({visibility: 'visible', opacity: this.options.opacity});
 73+ }
 74+ }
 75+ },
 76+
 77+ hide: function() {
 78+ if (!this.sticky) {
 79+ if (this.options.fade) {
 80+ this.tip().stop().fadeOut(function() { $(this).hide(); });
 81+ } else {
 82+ this.tip().hide();
 83+ }
 84+ }
 85+ },
 86+
 87+ getTitle: function() {
 88+ var title, $e = this.$element, o = this.options;
 89+ fixTitle($e);
 90+ var title, o = this.options;
 91+ if (typeof o.title == 'string') {
 92+ title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
 93+ title = ('' + title).replace(/(^\s*|\s*$)/, "");
 94+ } else if (typeof o.title == 'function') {
 95+ title = o.title.call($e[0]);
 96+ }
 97+ return title || o.fallback;
 98+ },
 99+
 100+ tip: function() {
 101+ var type = 'tipsy-' + this.options.type;
 102+ var shadow = this.options.shadow ? 'shadow' : '';
 103+ if (!this.$tip) {
 104+ this.$tip = $('<div class="tipsy ' + type + '"></div>')
 105+ .html('<div class="tipsy-arrow"></div><div class="tipsy-inner ' + shadow + '"/></div>');
 106+ }
 107+ return this.$tip;
 108+ },
 109+
 110+ validate: function() {
 111+ if (!this.$element[0].parentNode) this.hide();
 112+ },
 113+
 114+ enable: function() { this.enabled = true; },
 115+ disable: function() { this.enabled = false; },
 116+ toggleEnabled: function() { this.enabled = !this.enabled; }
 117+ };
 118+
 119+ $.fn.tipsy = function(options) {
 120+
 121+ if (options === true) {
 122+ return this.data('tipsy');
 123+ } else if (typeof options == 'string') {
 124+ return this.data('tipsy')[options]();
 125+ }
 126+
 127+ options = $.extend({}, $.fn.tipsy.defaults, options);
 128+
 129+ function get(ele) {
 130+ var tipsy = $.data(ele, 'tipsy');
 131+ if (!tipsy) {
 132+ tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
 133+ $.data(ele, 'tipsy', tipsy);
 134+ }
 135+ return tipsy;
 136+ }
 137+
 138+ function enter() {
 139+ var tipsy = get(this);
 140+ tipsy.hoverState = 'in';
 141+ if (options.delayIn == 0) {
 142+ tipsy.show();
 143+ } else {
 144+ setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
 145+ }
 146+ };
 147+
 148+ function leave() {
 149+ var tipsy = get(this);
 150+ tipsy.hoverState = 'out';
 151+ if (options.delayOut == 0) {
 152+ tipsy.hide();
 153+ } else {
 154+ setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
 155+ }
 156+ };
 157+
 158+ if (!options.live) this.each(function() { get(this); });
 159+
 160+ if (options.trigger != 'manual') {
 161+ var binder = options.live ? 'live' : 'bind',
 162+ eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
 163+ eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
 164+ this[binder](eventIn, enter)[binder](eventOut, leave);
 165+ }
 166+
 167+ return this;
 168+
 169+ };
 170+
 171+ $.fn.tipsy.defaults = {
 172+ delayIn: 0,
 173+ delayOut: 0,
 174+ fade: false,
 175+ fallback: '',
 176+ gravity: 'n',
 177+ html: false,
 178+ live: false,
 179+ offset: 0,
 180+ opacity: 1.0,
 181+ title: 'title',
 182+ trigger: 'hover',
 183+ type: 'help'
 184+ };
 185+
 186+ // Overwrite this method to provide options on a per-element basis.
 187+ // For example, you could store the gravity in a 'tipsy-gravity' attribute:
 188+ // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
 189+ // (remember - do not modify 'options' in place!)
 190+ $.fn.tipsy.elementOptions = function(ele, options) {
 191+ return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
 192+ };
 193+
 194+ $.fn.tipsy.autoNS = function() {
 195+ return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
 196+ };
 197+
 198+ $.fn.tipsy.autoWE = function() {
 199+ return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
 200+ };
 201+
 202+})(jQuery);
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.tipsy.js
___________________________________________________________________
Added: svn:eol-style
1203 + native
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.arrowSteps.js
@@ -0,0 +1,81 @@
 2+/**
 3+ * jQuery arrowSteps plugin
 4+ * Copyright Neil Kandalgaonkar, 2010
 5+ *
 6+ * This work is licensed under the terms of the GNU General Public License,
 7+ * version 2 or later.
 8+ * (see http://www.fsf.org/licensing/licenses/gpl.html).
 9+ * Derivative works and later versions of the code must be free software
 10+ * licensed under the same or a compatible license.
 11+ *
 12+ *
 13+ * DESCRIPTION
 14+ *
 15+ * Show users their progress through a series of steps, via a row of items that fit
 16+ * together like arrows. One item can be highlighted at a time.
 17+ *
 18+ *
 19+ * SYNOPSIS
 20+ *
 21+ * <ul id="robin-hood-daffy">
 22+ * <li id="guard"><div>Guard!</div></li>
 23+ * <li id="turn"><div>Turn!</div></li>
 24+ * <li id="parry"><div>Parry!</div></li>
 25+ * <li id="dodge"><div>Dodge!</div></li>
 26+ * <li id="spin"><div>Spin!</div></li>
 27+ * <li id="ha"><div>Ha!</div></li>
 28+ * <li id="thrust"><div>Thrust!</div></li>
 29+ * </ul>
 30+ *
 31+ * <script language="javascript"><!--
 32+ * $( '#robin-hood-daffy' ).arrowSteps();
 33+ *
 34+ * $( '#robin-hood-daffy' ).arrowStepsHighlight( '#guard' );
 35+ * // 'Guard!' is highlighted.
 36+ *
 37+ * // ... user completes the 'guard' step ...
 38+ *
 39+ * $( '#robin-hood-daffy' ).arrowStepsHighlight( '#turn' );
 40+ * // 'Turn!' is highlighted.
 41+ *
 42+ * //-->
 43+ * </script>
 44+ *
 45+ */
 46+
 47+( function( $j ) {
 48+ $j.fn.arrowSteps = function() {
 49+ this.addClass( 'arrowSteps' );
 50+ var $steps = this.find( 'li' );
 51+
 52+ var width = parseInt( 100 / $steps.length, 10 );
 53+ $steps.css( 'width', width + '%' );
 54+
 55+ // every step except the last one has an arrow at the right hand side. Also add in the padding
 56+ // for the calculated arrow width.
 57+ var arrowWidth = parseInt( this.outerHeight(), 10 );
 58+ $steps.filter( ':not(:last-child)' ).addClass( 'arrow' )
 59+ .find( 'div' ).css( 'padding-right', arrowWidth.toString() + 'px' );
 60+
 61+ this.data( 'arrowSteps', $steps );
 62+ return this;
 63+ };
 64+
 65+ $j.fn.arrowStepsHighlight = function( selector ) {
 66+ var $steps = this.data( 'arrowSteps' );
 67+ var $previous;
 68+ $j.each( $steps, function( i, step ) {
 69+ var $step = $j( step );
 70+ if ( $step.is( selector ) ) {
 71+ if ($previous) {
 72+ $previous.addClass( 'tail' );
 73+ }
 74+ $step.addClass( 'head' );
 75+ } else {
 76+ $step.removeClass( 'head tail lasthead' );
 77+ }
 78+ $previous = $step;
 79+ } );
 80+ };
 81+
 82+} )( jQuery );
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.arrowSteps.js
___________________________________________________________________
Added: svn:eol-style
183 + native
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.validate.js
@@ -0,0 +1,1145 @@
 2+/*
 3+ * jQuery validation plug-in 1.7
 4+ *
 5+ * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
 6+ * http://docs.jquery.com/Plugins/Validation
 7+ *
 8+ * Copyright (c) 2006 - 2008 Jörn Zaefferer
 9+ *
 10+ * $Id: jquery.validate.js 6403 2009-06-17 14:27:16Z joern.zaefferer $
 11+ *
 12+ * Dual licensed under the MIT and GPL licenses:
 13+ * http://www.opensource.org/licenses/mit-license.php
 14+ * http://www.gnu.org/licenses/gpl.html
 15+ */
 16+
 17+(function($) {
 18+
 19+$.extend($.fn, {
 20+ // http://docs.jquery.com/Plugins/Validation/validate
 21+ validate: function( options ) {
 22+
 23+ // if nothing is selected, return nothing; can't chain anyway
 24+ if (!this.length) {
 25+ options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" );
 26+ return;
 27+ }
 28+
 29+ // check if a validator for this form was already created
 30+ var validator = $.data(this[0], 'validator');
 31+ if ( validator ) {
 32+ return validator;
 33+ }
 34+
 35+ validator = new $.validator( options, this[0] );
 36+ $.data(this[0], 'validator', validator);
 37+
 38+ if ( validator.settings.onsubmit ) {
 39+
 40+ // allow suppresing validation by adding a cancel class to the submit button
 41+ this.find("input, button").filter(".cancel").click(function() {
 42+ validator.cancelSubmit = true;
 43+ });
 44+
 45+ // when a submitHandler is used, capture the submitting button
 46+ if (validator.settings.submitHandler) {
 47+ this.find("input, button").filter(":submit").click(function() {
 48+ validator.submitButton = this;
 49+ });
 50+ }
 51+
 52+ // validate the form on submit
 53+ this.submit( function( event ) {
 54+ if ( validator.settings.debug )
 55+ // prevent form submit to be able to see console output
 56+ event.preventDefault();
 57+
 58+ function handle() {
 59+ if ( validator.settings.submitHandler ) {
 60+ if (validator.submitButton) {
 61+ // insert a hidden input as a replacement for the missing submit button
 62+ var hidden = $("<input type='hidden'/>").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm);
 63+ }
 64+ validator.settings.submitHandler.call( validator, validator.currentForm );
 65+ if (validator.submitButton) {
 66+ // and clean up afterwards; thanks to no-block-scope, hidden can be referenced
 67+ hidden.remove();
 68+ }
 69+ return false;
 70+ }
 71+ return true;
 72+ }
 73+
 74+ // prevent submit for invalid forms or custom submit handlers
 75+ if ( validator.cancelSubmit ) {
 76+ validator.cancelSubmit = false;
 77+ return handle();
 78+ }
 79+ if ( validator.form() ) {
 80+ if ( validator.pendingRequest ) {
 81+ validator.formSubmitted = true;
 82+ return false;
 83+ }
 84+ return handle();
 85+ } else {
 86+ validator.focusInvalid();
 87+ return false;
 88+ }
 89+ });
 90+ }
 91+
 92+ return validator;
 93+ },
 94+ // http://docs.jquery.com/Plugins/Validation/valid
 95+ valid: function() {
 96+ if ( $(this[0]).is('form')) {
 97+ return this.validate().form();
 98+ } else {
 99+ var valid = true;
 100+ var validator = $(this[0].form).validate();
 101+ this.each(function() {
 102+ valid &= validator.element(this);
 103+ });
 104+ return valid;
 105+ }
 106+ },
 107+ // attributes: space seperated list of attributes to retrieve and remove
 108+ removeAttrs: function(attributes) {
 109+ var result = {},
 110+ $element = this;
 111+ $.each(attributes.split(/\s/), function(index, value) {
 112+ result[value] = $element.attr(value);
 113+ $element.removeAttr(value);
 114+ });
 115+ return result;
 116+ },
 117+ // http://docs.jquery.com/Plugins/Validation/rules
 118+ rules: function(command, argument) {
 119+ var element = this[0];
 120+
 121+ if (command) {
 122+ var settings = $.data(element.form, 'validator').settings;
 123+ var staticRules = settings.rules;
 124+ var existingRules = $.validator.staticRules(element);
 125+ switch(command) {
 126+ case "add":
 127+ $.extend(existingRules, $.validator.normalizeRule(argument));
 128+ staticRules[element.name] = existingRules;
 129+ if (argument.messages)
 130+ settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages );
 131+ break;
 132+ case "remove":
 133+ if (!argument) {
 134+ delete staticRules[element.name];
 135+ return existingRules;
 136+ }
 137+ var filtered = {};
 138+ $.each(argument.split(/\s/), function(index, method) {
 139+ filtered[method] = existingRules[method];
 140+ delete existingRules[method];
 141+ });
 142+ return filtered;
 143+ }
 144+ }
 145+
 146+ var data = $.validator.normalizeRules(
 147+ $.extend(
 148+ {},
 149+ $.validator.metadataRules(element),
 150+ $.validator.classRules(element),
 151+ $.validator.attributeRules(element),
 152+ $.validator.staticRules(element)
 153+ ), element);
 154+
 155+ // make sure required is at front
 156+ if (data.required) {
 157+ var param = data.required;
 158+ delete data.required;
 159+ data = $.extend({required: param}, data);
 160+ }
 161+
 162+ return data;
 163+ }
 164+});
 165+
 166+// Custom selectors
 167+$.extend($.expr[":"], {
 168+ // http://docs.jquery.com/Plugins/Validation/blank
 169+ blank: function(a) {return !$.trim("" + a.value);},
 170+ // http://docs.jquery.com/Plugins/Validation/filled
 171+ filled: function(a) {return !!$.trim("" + a.value);},
 172+ // http://docs.jquery.com/Plugins/Validation/unchecked
 173+ unchecked: function(a) {return !a.checked;}
 174+});
 175+
 176+// constructor for validator
 177+$.validator = function( options, form ) {
 178+ this.settings = $.extend( true, {}, $.validator.defaults, options );
 179+ this.currentForm = form;
 180+ this.init();
 181+};
 182+
 183+$.validator.format = function(source, params) {
 184+ if ( arguments.length == 1 )
 185+ return function() {
 186+ var args = $.makeArray(arguments);
 187+ args.unshift(source);
 188+ return $.validator.format.apply( this, args );
 189+ };
 190+ if ( arguments.length > 2 && params.constructor != Array ) {
 191+ params = $.makeArray(arguments).slice(1);
 192+ }
 193+ if ( params.constructor != Array ) {
 194+ params = [ params ];
 195+ }
 196+ $.each(params, function(i, n) {
 197+ source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
 198+ });
 199+ return source;
 200+};
 201+
 202+$.extend($.validator, {
 203+
 204+ defaults: {
 205+ messages: {},
 206+ groups: {},
 207+ rules: {},
 208+ errorClass: "error",
 209+ validClass: "valid",
 210+ errorElement: "label",
 211+ focusInvalid: true,
 212+ errorContainer: $( [] ),
 213+ errorLabelContainer: $( [] ),
 214+ onsubmit: true,
 215+ ignore: [],
 216+ ignoreTitle: false,
 217+ onfocusin: function(element) {
 218+ this.lastActive = element;
 219+
 220+ // hide error label and remove error class on focus if enabled
 221+ if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
 222+ this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
 223+ this.errorsFor(element).hide();
 224+ }
 225+ },
 226+ onfocusout: function(element) {
 227+ if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
 228+ this.element(element);
 229+ }
 230+ },
 231+ onkeyup: function(element) {
 232+ if ( element.name in this.submitted || element == this.lastElement ) {
 233+ this.element(element);
 234+ }
 235+ },
 236+ onclick: function(element) {
 237+ // click on selects, radiobuttons and checkboxes
 238+ if ( element.name in this.submitted )
 239+ this.element(element);
 240+ // or option elements, check parent select in that case
 241+ else if (element.parentNode.name in this.submitted)
 242+ this.element(element.parentNode);
 243+ },
 244+ highlight: function( element, errorClass, validClass ) {
 245+ $(element).addClass(errorClass).removeClass(validClass);
 246+ },
 247+ unhighlight: function( element, errorClass, validClass ) {
 248+ $(element).removeClass(errorClass).addClass(validClass);
 249+ }
 250+ },
 251+
 252+ // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
 253+ setDefaults: function(settings) {
 254+ $.extend( $.validator.defaults, settings );
 255+ },
 256+
 257+ messages: {
 258+ required: "This field is required.",
 259+ remote: "Please fix this field.",
 260+ email: "Please enter a valid email address.",
 261+ url: "Please enter a valid URL.",
 262+ date: "Please enter a valid date.",
 263+ dateISO: "Please enter a valid date (ISO).",
 264+ number: "Please enter a valid number.",
 265+ digits: "Please enter only digits.",
 266+ creditcard: "Please enter a valid credit card number.",
 267+ equalTo: "Please enter the same value again.",
 268+ accept: "Please enter a value with a valid extension.",
 269+ maxlength: $.validator.format("Please enter no more than {0} characters."),
 270+ minlength: $.validator.format("Please enter at least {0} characters."),
 271+ rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
 272+ range: $.validator.format("Please enter a value between {0} and {1}."),
 273+ max: $.validator.format("Please enter a value less than or equal to {0}."),
 274+ min: $.validator.format("Please enter a value greater than or equal to {0}.")
 275+ },
 276+
 277+ autoCreateRanges: false,
 278+
 279+ prototype: {
 280+
 281+ init: function() {
 282+ this.labelContainer = $(this.settings.errorLabelContainer);
 283+ this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
 284+ this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
 285+ this.submitted = {};
 286+ this.valueCache = {};
 287+ this.pendingRequest = 0;
 288+ this.pending = {};
 289+ this.invalid = {};
 290+ this.reset();
 291+
 292+ var groups = (this.groups = {});
 293+ $.each(this.settings.groups, function(key, value) {
 294+ $.each(value.split(/\s/), function(index, name) {
 295+ groups[name] = key;
 296+ });
 297+ });
 298+ var rules = this.settings.rules;
 299+ $.each(rules, function(key, value) {
 300+ rules[key] = $.validator.normalizeRule(value);
 301+ });
 302+
 303+ function delegate(event) {
 304+ var validator = $.data(this[0].form, "validator"),
 305+ eventType = "on" + event.type.replace(/^validate/, "");
 306+ validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] );
 307+ }
 308+ $(this.currentForm)
 309+ .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate)
 310+ .validateDelegate(":radio, :checkbox, select, option", "click", delegate);
 311+
 312+ if (this.settings.invalidHandler)
 313+ $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
 314+ },
 315+
 316+ // http://docs.jquery.com/Plugins/Validation/Validator/form
 317+ form: function() {
 318+ this.checkForm();
 319+ $.extend(this.submitted, this.errorMap);
 320+ this.invalid = $.extend({}, this.errorMap);
 321+ if (!this.valid())
 322+ $(this.currentForm).triggerHandler("invalid-form", [this]);
 323+ this.showErrors();
 324+ return this.valid();
 325+ },
 326+
 327+ checkForm: function() {
 328+ this.prepareForm();
 329+ for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
 330+ this.check( elements[i] );
 331+ }
 332+ return this.valid();
 333+ },
 334+
 335+ // http://docs.jquery.com/Plugins/Validation/Validator/element
 336+ element: function( element ) {
 337+ element = this.clean( element );
 338+ this.lastElement = element;
 339+ this.prepareElement( element );
 340+ this.currentElements = $(element);
 341+ var result = this.check( element );
 342+ if ( result ) {
 343+ delete this.invalid[element.name];
 344+ } else {
 345+ this.invalid[element.name] = true;
 346+ }
 347+ if ( !this.numberOfInvalids() ) {
 348+ // Hide error containers on last error
 349+ this.toHide = this.toHide.add( this.containers );
 350+ }
 351+ this.showErrors();
 352+ return result;
 353+ },
 354+
 355+ // http://docs.jquery.com/Plugins/Validation/Validator/showErrors
 356+ showErrors: function(errors) {
 357+ if(errors) {
 358+ // add items to error list and map
 359+ $.extend( this.errorMap, errors );
 360+ this.errorList = [];
 361+ for ( var name in errors ) {
 362+ this.errorList.push({
 363+ message: errors[name],
 364+ element: this.findByName(name)[0]
 365+ });
 366+ }
 367+ // remove items from success list
 368+ this.successList = $.grep( this.successList, function(element) {
 369+ return !(element.name in errors);
 370+ });
 371+ }
 372+ this.settings.showErrors
 373+ ? this.settings.showErrors.call( this, this.errorMap, this.errorList )
 374+ : this.defaultShowErrors();
 375+ },
 376+
 377+ // http://docs.jquery.com/Plugins/Validation/Validator/resetForm
 378+ resetForm: function() {
 379+ if ( $.fn.resetForm )
 380+ $( this.currentForm ).resetForm();
 381+ this.submitted = {};
 382+ this.prepareForm();
 383+ this.hideErrors();
 384+ this.elements().removeClass( this.settings.errorClass );
 385+ },
 386+
 387+ numberOfInvalids: function() {
 388+ return this.objectLength(this.invalid);
 389+ },
 390+
 391+ objectLength: function( obj ) {
 392+ var count = 0;
 393+ for ( var i in obj )
 394+ count++;
 395+ return count;
 396+ },
 397+
 398+ hideErrors: function() {
 399+ this.addWrapper( this.toHide ).hide();
 400+ },
 401+
 402+ valid: function() {
 403+ return this.size() == 0;
 404+ },
 405+
 406+ size: function() {
 407+ return this.errorList.length;
 408+ },
 409+
 410+ focusInvalid: function() {
 411+ if( this.settings.focusInvalid ) {
 412+ try {
 413+ $(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
 414+ .filter(":visible")
 415+ .focus()
 416+ // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
 417+ .trigger("focusin");
 418+ } catch(e) {
 419+ // ignore IE throwing errors when focusing hidden elements
 420+ }
 421+ }
 422+ },
 423+
 424+ findLastActive: function() {
 425+ var lastActive = this.lastActive;
 426+ return lastActive && $.grep(this.errorList, function(n) {
 427+ return n.element.name == lastActive.name;
 428+ }).length == 1 && lastActive;
 429+ },
 430+
 431+ elements: function() {
 432+ var validator = this,
 433+ rulesCache = {};
 434+
 435+ // select all valid inputs inside the form (no submit or reset buttons)
 436+ // workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved
 437+ return $([]).add(this.currentForm.elements)
 438+ .filter(":input")
 439+ .not(":submit, :reset, :image, [disabled]")
 440+ .not( this.settings.ignore )
 441+ .filter(function() {
 442+ !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
 443+
 444+ // select only the first element for each name, and only those with rules specified
 445+ if ( this.name in rulesCache || !validator.objectLength($(this).rules()) )
 446+ return false;
 447+
 448+ rulesCache[this.name] = true;
 449+ return true;
 450+ });
 451+ },
 452+
 453+ clean: function( selector ) {
 454+ return $( selector )[0];
 455+ },
 456+
 457+ errors: function() {
 458+ return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext );
 459+ },
 460+
 461+ reset: function() {
 462+ this.successList = [];
 463+ this.errorList = [];
 464+ this.errorMap = {};
 465+ this.toShow = $([]);
 466+ this.toHide = $([]);
 467+ this.currentElements = $([]);
 468+ },
 469+
 470+ prepareForm: function() {
 471+ this.reset();
 472+ this.toHide = this.errors().add( this.containers );
 473+ },
 474+
 475+ prepareElement: function( element ) {
 476+ this.reset();
 477+ this.toHide = this.errorsFor(element);
 478+ },
 479+
 480+ check: function( element ) {
 481+ element = this.clean( element );
 482+
 483+ // if radio/checkbox, validate first element in group instead
 484+ if (this.checkable(element)) {
 485+ element = this.findByName( element.name )[0];
 486+ }
 487+ var rules = $(element).rules();
 488+ var dependencyMismatch = false;
 489+ for( method in rules ) {
 490+ var rule = { method: method, parameters: rules[method] };
 491+ try {
 492+ var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters );
 493+
 494+ // if a method indicates that the field is optional and therefore valid,
 495+ // don't mark it as valid when there are no other rules
 496+ if ( result == "dependency-mismatch" ) {
 497+ dependencyMismatch = true;
 498+ continue;
 499+ }
 500+ dependencyMismatch = false;
 501+
 502+ if ( result == "pending" ) {
 503+ this.toHide = this.toHide.not( this.errorsFor(element) );
 504+ return;
 505+ }
 506+
 507+ if( !result ) {
 508+ this.formatAndAdd( element, rule );
 509+ return false;
 510+ }
 511+ } catch(e) {
 512+ this.settings.debug && window.console && console.log("exception occured when checking element " + element.id
 513+ + ", check the '" + rule.method + "' method", e);
 514+ throw e;
 515+ }
 516+ }
 517+ if (dependencyMismatch)
 518+ return;
 519+ if ( this.objectLength(rules) )
 520+ this.successList.push(element);
 521+ return true;
 522+ },
 523+
 524+ // return the custom message for the given element and validation method
 525+ // specified in the element's "messages" metadata
 526+ customMetaMessage: function(element, method) {
 527+ if (!$.metadata)
 528+ return;
 529+
 530+ var meta = this.settings.meta
 531+ ? $(element).metadata()[this.settings.meta]
 532+ : $(element).metadata();
 533+
 534+ return meta && meta.messages && meta.messages[method];
 535+ },
 536+
 537+ // return the custom message for the given element name and validation method
 538+ customMessage: function( name, method ) {
 539+ var m = this.settings.messages[name];
 540+ return m && (m.constructor == String
 541+ ? m
 542+ : m[method]);
 543+ },
 544+
 545+ // return the first defined argument, allowing empty strings
 546+ findDefined: function() {
 547+ for(var i = 0; i < arguments.length; i++) {
 548+ if (arguments[i] !== undefined)
 549+ return arguments[i];
 550+ }
 551+ return undefined;
 552+ },
 553+
 554+ defaultMessage: function( element, method) {
 555+ return this.findDefined(
 556+ this.customMessage( element.name, method ),
 557+ this.customMetaMessage( element, method ),
 558+ // title is never undefined, so handle empty string as undefined
 559+ !this.settings.ignoreTitle && element.title || undefined,
 560+ $.validator.messages[method],
 561+ "<strong>Warning: No message defined for " + element.name + "</strong>"
 562+ );
 563+ },
 564+
 565+ formatAndAdd: function( element, rule ) {
 566+ var message = this.defaultMessage( element, rule.method ),
 567+ theregex = /\$?\{(\d+)\}/g;
 568+ if ( typeof message == "function" ) {
 569+ message = message.call(this, rule.parameters, element);
 570+ } else if (theregex.test(message)) {
 571+ message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters);
 572+ }
 573+ this.errorList.push({
 574+ message: message,
 575+ element: element
 576+ });
 577+
 578+ this.errorMap[element.name] = message;
 579+ this.submitted[element.name] = message;
 580+ },
 581+
 582+ addWrapper: function(toToggle) {
 583+ if ( this.settings.wrapper )
 584+ toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
 585+ return toToggle;
 586+ },
 587+
 588+ defaultShowErrors: function() {
 589+ for ( var i = 0; this.errorList[i]; i++ ) {
 590+ var error = this.errorList[i];
 591+ this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
 592+ this.showLabel( error.element, error.message );
 593+ }
 594+ if( this.errorList.length ) {
 595+ this.toShow = this.toShow.add( this.containers );
 596+ }
 597+ if (this.settings.success) {
 598+ for ( var i = 0; this.successList[i]; i++ ) {
 599+ this.showLabel( this.successList[i] );
 600+ }
 601+ }
 602+ if (this.settings.unhighlight) {
 603+ for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) {
 604+ this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
 605+ }
 606+ }
 607+ this.toHide = this.toHide.not( this.toShow );
 608+ this.hideErrors();
 609+ this.addWrapper( this.toShow ).show();
 610+ },
 611+
 612+ validElements: function() {
 613+ return this.currentElements.not(this.invalidElements());
 614+ },
 615+
 616+ invalidElements: function() {
 617+ return $(this.errorList).map(function() {
 618+ return this.element;
 619+ });
 620+ },
 621+
 622+ showLabel: function(element, message) {
 623+ var label = this.errorsFor( element );
 624+ if ( label.length ) {
 625+ // refresh error/success class
 626+ label.removeClass().addClass( this.settings.errorClass );
 627+
 628+ // check if we have a generated label, replace the message then
 629+ label.attr("generated") && label.html(message);
 630+ } else {
 631+ // create label
 632+ label = $("<" + this.settings.errorElement + "/>")
 633+ .attr({"for": this.idOrName(element), generated: true})
 634+ .addClass(this.settings.errorClass)
 635+ .html(message || "");
 636+ if ( this.settings.wrapper ) {
 637+ // make sure the element is visible, even in IE
 638+ // actually showing the wrapped element is handled elsewhere
 639+ label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
 640+ }
 641+ if ( !this.labelContainer.append(label).length )
 642+ this.settings.errorPlacement
 643+ ? this.settings.errorPlacement(label, $(element) )
 644+ : label.insertAfter(element);
 645+ }
 646+ if ( !message && this.settings.success ) {
 647+ label.text("");
 648+ typeof this.settings.success == "string"
 649+ ? label.addClass( this.settings.success )
 650+ : this.settings.success( label );
 651+ }
 652+ this.toShow = this.toShow.add(label);
 653+ },
 654+
 655+ errorsFor: function(element) {
 656+ var name = this.idOrName(element);
 657+ return this.errors().filter(function() {
 658+ return $(this).attr('for') == name;
 659+ });
 660+ },
 661+
 662+ idOrName: function(element) {
 663+ return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
 664+ },
 665+
 666+ checkable: function( element ) {
 667+ return /radio|checkbox/i.test(element.type);
 668+ },
 669+
 670+ findByName: function( name ) {
 671+ // select by name and filter by form for performance over form.find("[name=...]")
 672+ var form = this.currentForm;
 673+ return $(document.getElementsByName(name)).map(function(index, element) {
 674+ return element.form == form && element.name == name && element || null;
 675+ });
 676+ },
 677+
 678+ getLength: function(value, element) {
 679+ switch( element.nodeName.toLowerCase() ) {
 680+ case 'select':
 681+ return $("option:selected", element).length;
 682+ case 'input':
 683+ if( this.checkable( element) )
 684+ return this.findByName(element.name).filter(':checked').length;
 685+ }
 686+ return value.length;
 687+ },
 688+
 689+ depend: function(param, element) {
 690+ return this.dependTypes[typeof param]
 691+ ? this.dependTypes[typeof param](param, element)
 692+ : true;
 693+ },
 694+
 695+ dependTypes: {
 696+ "boolean": function(param, element) {
 697+ return param;
 698+ },
 699+ "string": function(param, element) {
 700+ return !!$(param, element.form).length;
 701+ },
 702+ "function": function(param, element) {
 703+ return param(element);
 704+ }
 705+ },
 706+
 707+ optional: function(element) {
 708+ return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch";
 709+ },
 710+
 711+ startRequest: function(element) {
 712+ if (!this.pending[element.name]) {
 713+ this.pendingRequest++;
 714+ this.pending[element.name] = true;
 715+ }
 716+ },
 717+
 718+ stopRequest: function(element, valid) {
 719+ this.pendingRequest--;
 720+ // sometimes synchronization fails, make sure pendingRequest is never < 0
 721+ if (this.pendingRequest < 0)
 722+ this.pendingRequest = 0;
 723+ delete this.pending[element.name];
 724+ if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) {
 725+ $(this.currentForm).submit();
 726+ this.formSubmitted = false;
 727+ } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) {
 728+ $(this.currentForm).triggerHandler("invalid-form", [this]);
 729+ this.formSubmitted = false;
 730+ }
 731+ },
 732+
 733+ previousValue: function(element) {
 734+ return $.data(element, "previousValue") || $.data(element, "previousValue", {
 735+ old: null,
 736+ valid: true,
 737+ message: this.defaultMessage( element, "remote" )
 738+ });
 739+ }
 740+
 741+ },
 742+
 743+ classRuleSettings: {
 744+ required: {required: true},
 745+ email: {email: true},
 746+ url: {url: true},
 747+ date: {date: true},
 748+ dateISO: {dateISO: true},
 749+ dateDE: {dateDE: true},
 750+ number: {number: true},
 751+ numberDE: {numberDE: true},
 752+ digits: {digits: true},
 753+ creditcard: {creditcard: true}
 754+ },
 755+
 756+ addClassRules: function(className, rules) {
 757+ className.constructor == String ?
 758+ this.classRuleSettings[className] = rules :
 759+ $.extend(this.classRuleSettings, className);
 760+ },
 761+
 762+ classRules: function(element) {
 763+ var rules = {};
 764+ var classes = $(element).attr('class');
 765+ classes && $.each(classes.split(' '), function() {
 766+ if (this in $.validator.classRuleSettings) {
 767+ $.extend(rules, $.validator.classRuleSettings[this]);
 768+ }
 769+ });
 770+ return rules;
 771+ },
 772+
 773+ attributeRules: function(element) {
 774+ var rules = {};
 775+ var $element = $(element);
 776+
 777+ for (method in $.validator.methods) {
 778+ var value = $element.attr(method);
 779+ if (value) {
 780+ rules[method] = value;
 781+ }
 782+ }
 783+
 784+ // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
 785+ if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) {
 786+ delete rules.maxlength;
 787+ }
 788+
 789+ return rules;
 790+ },
 791+
 792+ metadataRules: function(element) {
 793+ if (!$.metadata) return {};
 794+
 795+ var meta = $.data(element.form, 'validator').settings.meta;
 796+ return meta ?
 797+ $(element).metadata()[meta] :
 798+ $(element).metadata();
 799+ },
 800+
 801+ staticRules: function(element) {
 802+ var rules = {};
 803+ var validator = $.data(element.form, 'validator');
 804+ if (validator.settings.rules) {
 805+ rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
 806+ }
 807+ return rules;
 808+ },
 809+
 810+ normalizeRules: function(rules, element) {
 811+ // handle dependency check
 812+ $.each(rules, function(prop, val) {
 813+ // ignore rule when param is explicitly false, eg. required:false
 814+ if (val === false) {
 815+ delete rules[prop];
 816+ return;
 817+ }
 818+ if (val.param || val.depends) {
 819+ var keepRule = true;
 820+ switch (typeof val.depends) {
 821+ case "string":
 822+ keepRule = !!$(val.depends, element.form).length;
 823+ break;
 824+ case "function":
 825+ keepRule = val.depends.call(element, element);
 826+ break;
 827+ }
 828+ if (keepRule) {
 829+ rules[prop] = val.param !== undefined ? val.param : true;
 830+ } else {
 831+ delete rules[prop];
 832+ }
 833+ }
 834+ });
 835+
 836+ // evaluate parameters
 837+ $.each(rules, function(rule, parameter) {
 838+ rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
 839+ });
 840+
 841+ // clean number parameters
 842+ $.each(['minlength', 'maxlength', 'min', 'max'], function() {
 843+ if (rules[this]) {
 844+ rules[this] = Number(rules[this]);
 845+ }
 846+ });
 847+ $.each(['rangelength', 'range'], function() {
 848+ if (rules[this]) {
 849+ rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
 850+ }
 851+ });
 852+
 853+ if ($.validator.autoCreateRanges) {
 854+ // auto-create ranges
 855+ if (rules.min && rules.max) {
 856+ rules.range = [rules.min, rules.max];
 857+ delete rules.min;
 858+ delete rules.max;
 859+ }
 860+ if (rules.minlength && rules.maxlength) {
 861+ rules.rangelength = [rules.minlength, rules.maxlength];
 862+ delete rules.minlength;
 863+ delete rules.maxlength;
 864+ }
 865+ }
 866+
 867+ // To support custom messages in metadata ignore rule methods titled "messages"
 868+ if (rules.messages) {
 869+ delete rules.messages;
 870+ }
 871+
 872+ return rules;
 873+ },
 874+
 875+ // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
 876+ normalizeRule: function(data) {
 877+ if( typeof data == "string" ) {
 878+ var transformed = {};
 879+ $.each(data.split(/\s/), function() {
 880+ transformed[this] = true;
 881+ });
 882+ data = transformed;
 883+ }
 884+ return data;
 885+ },
 886+
 887+ // http://docs.jquery.com/Plugins/Validation/Validator/addMethod
 888+ addMethod: function(name, method, message) {
 889+ $.validator.methods[name] = method;
 890+ $.validator.messages[name] = message != undefined ? message : $.validator.messages[name];
 891+ if (method.length < 3) {
 892+ $.validator.addClassRules(name, $.validator.normalizeRule(name));
 893+ }
 894+ },
 895+
 896+ methods: {
 897+
 898+ // http://docs.jquery.com/Plugins/Validation/Methods/required
 899+ required: function(value, element, param) {
 900+ // check if dependency is met
 901+ if ( !this.depend(param, element) )
 902+ return "dependency-mismatch";
 903+ switch( element.nodeName.toLowerCase() ) {
 904+ case 'select':
 905+ // could be an array for select-multiple or a string, both are fine this way
 906+ var val = $(element).val();
 907+ return val && val.length > 0;
 908+ case 'input':
 909+ if ( this.checkable(element) )
 910+ return this.getLength(value, element) > 0;
 911+ default:
 912+ return $.trim(value).length > 0;
 913+ }
 914+ },
 915+
 916+ // http://docs.jquery.com/Plugins/Validation/Methods/remote
 917+ remote: function(value, element, param) {
 918+ if ( this.optional(element) )
 919+ return "dependency-mismatch";
 920+
 921+ var previous = this.previousValue(element);
 922+ if (!this.settings.messages[element.name] )
 923+ this.settings.messages[element.name] = {};
 924+ previous.originalMessage = this.settings.messages[element.name].remote;
 925+ this.settings.messages[element.name].remote = previous.message;
 926+
 927+ param = typeof param == "string" && {url:param} || param;
 928+
 929+ if ( previous.old !== value ) {
 930+ previous.old = value;
 931+ var validator = this;
 932+ this.startRequest(element);
 933+ var data = {};
 934+ data[element.name] = value;
 935+ $.ajax($.extend(true, {
 936+ url: param,
 937+ mode: "abort",
 938+ port: "validate" + element.name,
 939+ dataType: "json",
 940+ data: data,
 941+ success: function(response) {
 942+ validator.settings.messages[element.name].remote = previous.originalMessage;
 943+ var valid = response === true;
 944+ if ( valid ) {
 945+ var submitted = validator.formSubmitted;
 946+ validator.prepareElement(element);
 947+ validator.formSubmitted = submitted;
 948+ validator.successList.push(element);
 949+ validator.showErrors();
 950+ } else {
 951+ var errors = {};
 952+ var message = (previous.message = response || validator.defaultMessage( element, "remote" ));
 953+ errors[element.name] = $.isFunction(message) ? message(value) : message;
 954+ validator.showErrors(errors);
 955+ }
 956+ previous.valid = valid;
 957+ validator.stopRequest(element, valid);
 958+ }
 959+ }, param));
 960+ return "pending";
 961+ } else if( this.pending[element.name] ) {
 962+ return "pending";
 963+ }
 964+ return previous.valid;
 965+ },
 966+
 967+ // http://docs.jquery.com/Plugins/Validation/Methods/minlength
 968+ minlength: function(value, element, param) {
 969+ return this.optional(element) || this.getLength($.trim(value), element) >= param;
 970+ },
 971+
 972+ // http://docs.jquery.com/Plugins/Validation/Methods/maxlength
 973+ maxlength: function(value, element, param) {
 974+ return this.optional(element) || this.getLength($.trim(value), element) <= param;
 975+ },
 976+
 977+ // http://docs.jquery.com/Plugins/Validation/Methods/rangelength
 978+ rangelength: function(value, element, param) {
 979+ var length = this.getLength($.trim(value), element);
 980+ return this.optional(element) || ( length >= param[0] && length <= param[1] );
 981+ },
 982+
 983+ // http://docs.jquery.com/Plugins/Validation/Methods/min
 984+ min: function( value, element, param ) {
 985+ return this.optional(element) || value >= param;
 986+ },
 987+
 988+ // http://docs.jquery.com/Plugins/Validation/Methods/max
 989+ max: function( value, element, param ) {
 990+ return this.optional(element) || value <= param;
 991+ },
 992+
 993+ // http://docs.jquery.com/Plugins/Validation/Methods/range
 994+ range: function( value, element, param ) {
 995+ return this.optional(element) || ( value >= param[0] && value <= param[1] );
 996+ },
 997+
 998+ // http://docs.jquery.com/Plugins/Validation/Methods/email
 999+ email: function(value, element) {
 1000+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
 1001+ return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
 1002+ },
 1003+
 1004+ // http://docs.jquery.com/Plugins/Validation/Methods/url
 1005+ url: function(value, element) {
 1006+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
 1007+ return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
 1008+ },
 1009+
 1010+ // http://docs.jquery.com/Plugins/Validation/Methods/date
 1011+ date: function(value, element) {
 1012+ return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
 1013+ },
 1014+
 1015+ // http://docs.jquery.com/Plugins/Validation/Methods/dateISO
 1016+ dateISO: function(value, element) {
 1017+ return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value);
 1018+ },
 1019+
 1020+ // http://docs.jquery.com/Plugins/Validation/Methods/number
 1021+ number: function(value, element) {
 1022+ return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value);
 1023+ },
 1024+
 1025+ // http://docs.jquery.com/Plugins/Validation/Methods/digits
 1026+ digits: function(value, element) {
 1027+ return this.optional(element) || /^\d+$/.test(value);
 1028+ },
 1029+
 1030+ // http://docs.jquery.com/Plugins/Validation/Methods/creditcard
 1031+ // based on http://en.wikipedia.org/wiki/Luhn
 1032+ creditcard: function(value, element) {
 1033+ if ( this.optional(element) )
 1034+ return "dependency-mismatch";
 1035+ // accept only digits and dashes
 1036+ if (/[^0-9-]+/.test(value))
 1037+ return false;
 1038+ var nCheck = 0,
 1039+ nDigit = 0,
 1040+ bEven = false;
 1041+
 1042+ value = value.replace(/\D/g, "");
 1043+
 1044+ for (var n = value.length - 1; n >= 0; n--) {
 1045+ var cDigit = value.charAt(n);
 1046+ var nDigit = parseInt(cDigit, 10);
 1047+ if (bEven) {
 1048+ if ((nDigit *= 2) > 9)
 1049+ nDigit -= 9;
 1050+ }
 1051+ nCheck += nDigit;
 1052+ bEven = !bEven;
 1053+ }
 1054+
 1055+ return (nCheck % 10) == 0;
 1056+ },
 1057+
 1058+ // http://docs.jquery.com/Plugins/Validation/Methods/accept
 1059+ accept: function(value, element, param) {
 1060+ param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif";
 1061+ return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i"));
 1062+ },
 1063+
 1064+ // http://docs.jquery.com/Plugins/Validation/Methods/equalTo
 1065+ equalTo: function(value, element, param) {
 1066+ // bind to the blur event of the target in order to revalidate whenever the target field is updated
 1067+ // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
 1068+ var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
 1069+ $(element).valid();
 1070+ });
 1071+ return value == target.val();
 1072+ }
 1073+
 1074+ }
 1075+
 1076+});
 1077+
 1078+// deprecated, use $.validator.format instead
 1079+$.format = $.validator.format;
 1080+
 1081+})(jQuery);
 1082+
 1083+// ajax mode: abort
 1084+// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
 1085+// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
 1086+;(function($) {
 1087+ var ajax = $.ajax;
 1088+ var pendingRequests = {};
 1089+ $.ajax = function(settings) {
 1090+ // create settings for compatibility with ajaxSetup
 1091+ settings = $.extend(settings, $.extend({}, $.ajaxSettings, settings));
 1092+ var port = settings.port;
 1093+ if (settings.mode == "abort") {
 1094+ if ( pendingRequests[port] ) {
 1095+ pendingRequests[port].abort();
 1096+ }
 1097+ return (pendingRequests[port] = ajax.apply(this, arguments));
 1098+ }
 1099+ return ajax.apply(this, arguments);
 1100+ };
 1101+})(jQuery);
 1102+
 1103+// provides cross-browser focusin and focusout events
 1104+// IE has native support, in other browsers, use event caputuring (neither bubbles)
 1105+
 1106+// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
 1107+// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
 1108+;(function($) {
 1109+ // only implement if not provided by jQuery core (since 1.4)
 1110+ // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs
 1111+ if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) {
 1112+ $.each({
 1113+ focus: 'focusin',
 1114+ blur: 'focusout'
 1115+ }, function( original, fix ){
 1116+ $.event.special[fix] = {
 1117+ setup:function() {
 1118+ this.addEventListener( original, handler, true );
 1119+ },
 1120+ teardown:function() {
 1121+ this.removeEventListener( original, handler, true );
 1122+ },
 1123+ handler: function(e) {
 1124+ arguments[0] = $.event.fix(e);
 1125+ arguments[0].type = fix;
 1126+ return $.event.handle.apply(this, arguments);
 1127+ }
 1128+ };
 1129+ function handler(e) {
 1130+ e = $.event.fix(e);
 1131+ e.type = fix;
 1132+ return $.event.handle.call(this, e);
 1133+ }
 1134+ });
 1135+ };
 1136+ $.extend($.fn, {
 1137+ validateDelegate: function(delegate, type, handler) {
 1138+ return this.bind(type, function(event) {
 1139+ var target = $(event.target);
 1140+ if (target.is(delegate)) {
 1141+ return handler.apply(target, arguments);
 1142+ }
 1143+ });
 1144+ }
 1145+ });
 1146+})(jQuery);
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.validate.js
___________________________________________________________________
Added: svn:eol-style
11147 + native
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.autoSuggest.js
@@ -0,0 +1,368 @@
 2+ /*
 3+ * AutoSuggest
 4+ * Copyright 2009-2010 Drew Wilson
 5+ * www.drewwilson.com
 6+ * code.drewwilson.com/entry/autosuggest-jquery-plugin
 7+ *
 8+ * Version 1.4 - Updated: Mar. 23, 2010
 9+ *
 10+ * This Plug-In will auto-complete or auto-suggest completed search queries
 11+ * for you as you type. You can add multiple selections and remove them on
 12+ * the fly. It supports keybord navigation (UP + DOWN + RETURN), as well
 13+ * as multiple AutoSuggest fields on the same page.
 14+ *
 15+ * Inspied by the Autocomplete plugin by: J�rn Zaefferer
 16+ * and the Facelist plugin by: Ian Tearle (iantearle.com)
 17+ *
 18+ * This AutoSuggest jQuery plug-in is dual licensed under the MIT and GPL licenses:
 19+ * http://www.opensource.org/licenses/mit-license.php
 20+ * http://www.gnu.org/licenses/gpl.html
 21+ */
 22+
 23+(function($){
 24+ $.fn.autoSuggest = function(data, options) {
 25+ var defaults = {
 26+ asHtmlID: false,
 27+ startText: "Enter Name Here",
 28+ emptyText: "No Results Found",
 29+ preFill: {},
 30+ limitText: "No More Selections Are Allowed",
 31+ selectedItemProp: "value", //name of object property
 32+ selectedValuesProp: "value", //name of object property
 33+ searchObjProps: "value", //comma separated list of object property names
 34+ queryParam: "q",
 35+ retrieveLimit: false, //number for 'limit' param on ajax request
 36+ extraParams: "",
 37+ matchCase: false,
 38+ minChars: 1,
 39+ keyDelay: 400,
 40+ resultsHighlight: true,
 41+ neverSubmit: false,
 42+ selectionLimit: false,
 43+ showResultList: true,
 44+ start: function(){},
 45+ selectionClick: function(elem){},
 46+ selectionAdded: function(elem){},
 47+ selectionRemoved: function(elem){ elem.remove(); },
 48+ formatList: false, //callback function
 49+ beforeRetrieve: function(string){ return string; },
 50+ retrieveComplete: function(data){ return data; },
 51+ resultClick: function(data){},
 52+ resultsComplete: function(){}
 53+ };
 54+ var opts = $.extend(defaults, options);
 55+
 56+ var d_type = "object";
 57+ var d_count = 0;
 58+ if(typeof data == "string") {
 59+ d_type = "string";
 60+ var req_string = data;
 61+ } else {
 62+ var org_data = data;
 63+ for (k in data) if (data.hasOwnProperty(k)) d_count++;
 64+ }
 65+ if((d_type == "object" && d_count > 0) || d_type == "string"){
 66+ return this.each(function(x){
 67+ if(!opts.asHtmlID){
 68+ x = x+""+Math.floor(Math.random()*100); //this ensures there will be unique IDs on the page if autoSuggest() is called multiple times
 69+ var x_id = "as-input-"+x;
 70+ } else {
 71+ x = opts.asHtmlID;
 72+ var x_id = x;
 73+ }
 74+ opts.start.call(this);
 75+ var input = $(this);
 76+ input.attr("autocomplete","off").addClass("as-input").attr("id",x_id).val(opts.startText);
 77+ var input_focus = false;
 78+
 79+ // Setup basic elements and render them to the DOM
 80+ input.wrap('<ul class="as-selections" id="as-selections-'+x+'"></ul>').wrap('<li class="as-original" id="as-original-'+x+'"></li>');
 81+ var selections_holder = $("#as-selections-"+x);
 82+ var org_li = $("#as-original-"+x);
 83+ var results_holder = $('<div class="as-results" id="as-results-'+x+'"></div>').hide();
 84+ var results_ul = $('<ul class="as-list"></ul>');
 85+ var values_input = $('<input type="hidden" class="as-values" name="as_values_'+x+'" id="as-values-'+x+'" />');
 86+ var prefill_value = "";
 87+ if(typeof opts.preFill == "string"){
 88+ var vals = opts.preFill.split(",");
 89+ for(var i=0; i < vals.length; i++){
 90+ var v_data = {};
 91+ v_data[opts.selectedValuesProp] = vals[i];
 92+ if(vals[i] != ""){
 93+ add_selected_item(v_data, "000"+i);
 94+ }
 95+ }
 96+ prefill_value = opts.preFill;
 97+ } else {
 98+ prefill_value = "";
 99+ var prefill_count = 0;
 100+ for (k in opts.preFill) if (opts.preFill.hasOwnProperty(k)) prefill_count++;
 101+ if(prefill_count > 0){
 102+ for(var i=0; i < prefill_count; i++){
 103+ var new_v = opts.preFill[i][opts.selectedValuesProp];
 104+ if(new_v == undefined){ new_v = ""; }
 105+ prefill_value = prefill_value+new_v+",";
 106+ if(new_v != ""){
 107+ add_selected_item(opts.preFill[i], "000"+i);
 108+ }
 109+ }
 110+ }
 111+ }
 112+ if(prefill_value != ""){
 113+ input.val("");
 114+ var lastChar = prefill_value.substring(prefill_value.length-1);
 115+ if(lastChar != ","){ prefill_value = prefill_value+","; }
 116+ values_input.val(","+prefill_value);
 117+ $("li.as-selection-item", selections_holder).addClass("blur").removeClass("selected");
 118+ }
 119+ input.after(values_input);
 120+ selections_holder.click(function(){
 121+ input_focus = true;
 122+ input.focus();
 123+ }).mousedown(function(){ input_focus = false; }).after(results_holder);
 124+
 125+ var timeout = null;
 126+ var prev = "";
 127+ var totalSelections = 0;
 128+ var tab_press = false;
 129+
 130+ // Handle input field events
 131+ input.focus(function(){
 132+ if($(this).val() == opts.startText && values_input.val() == ""){
 133+ $(this).val("");
 134+ } else if(input_focus){
 135+ $("li.as-selection-item", selections_holder).removeClass("blur");
 136+ if($(this).val() != ""){
 137+ results_ul.css("width",selections_holder.outerWidth());
 138+ results_holder.show();
 139+ }
 140+ }
 141+ input_focus = true;
 142+ return true;
 143+ }).blur(function(){
 144+ if($(this).val() == "" && values_input.val() == "" && prefill_value == ""){
 145+ $(this).val(opts.startText);
 146+ } else if(input_focus){
 147+ $("li.as-selection-item", selections_holder).addClass("blur").removeClass("selected");
 148+ results_holder.hide();
 149+ }
 150+ }).keydown(function(e) {
 151+ // track last key pressed
 152+ lastKeyPressCode = e.keyCode;
 153+ first_focus = false;
 154+ switch(e.keyCode) {
 155+ case 38: // up
 156+ e.preventDefault();
 157+ moveSelection("up");
 158+ break;
 159+ case 40: // down
 160+ e.preventDefault();
 161+ moveSelection("down");
 162+ break;
 163+ case 8: // delete
 164+ if(input.val() == ""){
 165+ var last = values_input.val().split(",");
 166+ last = last[last.length - 2];
 167+ selections_holder.children().not(org_li.prev()).removeClass("selected");
 168+ if(org_li.prev().hasClass("selected")){
 169+ values_input.val(values_input.val().replace(","+last+",",","));
 170+ opts.selectionRemoved.call(this, org_li.prev());
 171+ } else {
 172+ opts.selectionClick.call(this, org_li.prev());
 173+ org_li.prev().addClass("selected");
 174+ }
 175+ }
 176+ if(input.val().length == 1){
 177+ results_holder.hide();
 178+ prev = "";
 179+ }
 180+ if($(":visible",results_holder).length > 0){
 181+ if (timeout){ clearTimeout(timeout); }
 182+ timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay);
 183+ }
 184+ break;
 185+ case 9: case 188: // tab or comma
 186+ tab_press = true;
 187+ var i_input = input.val().replace(/(,)/g, "");
 188+ if(i_input != "" && values_input.val().search(","+i_input+",") < 0 && i_input.length >= opts.minChars){
 189+ e.preventDefault();
 190+ var n_data = {};
 191+ n_data[opts.selectedItemProp] = i_input;
 192+ n_data[opts.selectedValuesProp] = i_input;
 193+ var lis = $("li", selections_holder).length;
 194+ add_selected_item(n_data, "00"+(lis+1));
 195+ input.val("");
 196+ }
 197+ case 13: // return
 198+ tab_press = false;
 199+ var active = $("li.active:first", results_holder);
 200+ if(active.length > 0){
 201+ active.click();
 202+ results_holder.hide();
 203+ }
 204+ if(opts.neverSubmit || active.length > 0){
 205+ e.preventDefault();
 206+ }
 207+ break;
 208+ default:
 209+ if(opts.showResultList){
 210+ if(opts.selectionLimit && $("li.as-selection-item", selections_holder).length >= opts.selectionLimit){
 211+ results_ul.html('<li class="as-message">'+opts.limitText+'</li>');
 212+ results_holder.show();
 213+ } else {
 214+ if (timeout){ clearTimeout(timeout); }
 215+ timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay);
 216+ }
 217+ }
 218+ break;
 219+ }
 220+ });
 221+
 222+ function keyChange() {
 223+ // ignore if the following keys are pressed: [del] [shift] [capslock]
 224+ if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ){ return results_holder.hide(); }
 225+ var string = input.val().replace(/[\\]+|[\/]+/g,"");
 226+ if (string == prev) return;
 227+ prev = string;
 228+ if (string.length >= opts.minChars) {
 229+ selections_holder.addClass("loading");
 230+ if(d_type == "string"){
 231+ var limit = "";
 232+ if(opts.retrieveLimit){
 233+ limit = "&limit="+encodeURIComponent(opts.retrieveLimit);
 234+ }
 235+ if(opts.beforeRetrieve){
 236+ string = opts.beforeRetrieve.call(this, string);
 237+ }
 238+ $.getJSON(req_string+"?"+opts.queryParam+"="+encodeURIComponent(string)+limit+opts.extraParams, function(data){
 239+ d_count = 0;
 240+ var new_data = opts.retrieveComplete.call(this, data);
 241+ for (k in new_data) if (new_data.hasOwnProperty(k)) d_count++;
 242+ processData(new_data, string);
 243+ });
 244+ } else {
 245+ if(opts.beforeRetrieve){
 246+ string = opts.beforeRetrieve.call(this, string);
 247+ }
 248+ processData(org_data, string);
 249+ }
 250+ } else {
 251+ selections_holder.removeClass("loading");
 252+ results_holder.hide();
 253+ }
 254+ }
 255+ var num_count = 0;
 256+ function processData(data, query){
 257+ if (!opts.matchCase){ query = query.toLowerCase(); }
 258+ var matchCount = 0;
 259+ results_holder.html(results_ul.html("")).hide();
 260+ for(var i=0;i<d_count;i++){
 261+ var num = i;
 262+ num_count++;
 263+ var forward = false;
 264+ if(opts.searchObjProps == "value") {
 265+ var str = data[num].value;
 266+ } else {
 267+ var str = "";
 268+ var names = opts.searchObjProps.split(",");
 269+ for(var y=0;y<names.length;y++){
 270+ var name = $.trim(names[y]);
 271+ str = str+data[num][name]+" ";
 272+ }
 273+ }
 274+ if(str){
 275+ if (!opts.matchCase){ str = str.toLowerCase(); }
 276+ if(str.search(query) != -1 && values_input.val().search(","+data[num][opts.selectedValuesProp]+",") == -1){
 277+ forward = true;
 278+ }
 279+ }
 280+ if(forward){
 281+ var formatted = $('<li class="as-result-item" id="as-result-item-'+num+'"></li>').click(function(){
 282+ var raw_data = $(this).data("data");
 283+ var number = raw_data.num;
 284+ if($("#as-selection-"+number, selections_holder).length <= 0 && !tab_press){
 285+ var data = raw_data.attributes;
 286+ input.val("").focus();
 287+ prev = "";
 288+ add_selected_item(data, number);
 289+ opts.resultClick.call(this, raw_data);
 290+ results_holder.hide();
 291+ }
 292+ tab_press = false;
 293+ }).mousedown(function(){ input_focus = false; }).mouseover(function(){
 294+ $("li", results_ul).removeClass("active");
 295+ $(this).addClass("active");
 296+ }).data("data",{attributes: data[num], num: num_count});
 297+ var this_data = $.extend({},data[num]);
 298+ if (!opts.matchCase){
 299+ var regx = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "gi");
 300+ } else {
 301+ var regx = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "g");
 302+ }
 303+
 304+ if(opts.resultsHighlight){
 305+ this_data[opts.selectedItemProp] = this_data[opts.selectedItemProp].replace(regx,"<em>$1</em>");
 306+ }
 307+ if(!opts.formatList){
 308+ formatted = formatted.html(this_data[opts.selectedItemProp]);
 309+ } else {
 310+ formatted = opts.formatList.call(this, this_data, formatted);
 311+ }
 312+ results_ul.append(formatted);
 313+ delete this_data;
 314+ matchCount++;
 315+ if(opts.retrieveLimit && opts.retrieveLimit == matchCount ){ break; }
 316+ }
 317+ }
 318+ selections_holder.removeClass("loading");
 319+ if(matchCount <= 0){
 320+ results_ul.html('<li class="as-message">'+opts.emptyText+'</li>');
 321+ }
 322+ results_ul.css("width", selections_holder.outerWidth());
 323+ results_holder.show();
 324+ opts.resultsComplete.call(this);
 325+ }
 326+
 327+ function add_selected_item(data, num){
 328+ values_input.val(values_input.val()+data[opts.selectedValuesProp]+",");
 329+ var item = $('<li class="as-selection-item" id="as-selection-'+num+'"></li>').click(function(){
 330+ opts.selectionClick.call(this, $(this));
 331+ selections_holder.children().removeClass("selected");
 332+ $(this).addClass("selected");
 333+ }).mousedown(function(){ input_focus = false; });
 334+ var close = $('<a class="as-close">&times;</a>').click(function(){
 335+ values_input.val(values_input.val().replace(","+data[opts.selectedValuesProp]+",",","));
 336+ opts.selectionRemoved.call(this, item);
 337+ input_focus = true;
 338+ input.focus();
 339+ return false;
 340+ });
 341+ org_li.before(item.html(data[opts.selectedItemProp]).prepend(close));
 342+ opts.selectionAdded.call(this, org_li.prev());
 343+ }
 344+
 345+ function moveSelection(direction){
 346+ if($(":visible",results_holder).length > 0){
 347+ var lis = $("li", results_holder);
 348+ if(direction == "down"){
 349+ var start = lis.eq(0);
 350+ } else {
 351+ var start = lis.filter(":last");
 352+ }
 353+ var active = $("li.active:first", results_holder);
 354+ if(active.length > 0){
 355+ if(direction == "down"){
 356+ start = active.next();
 357+ } else {
 358+ start = active.prev();
 359+ }
 360+ }
 361+ lis.removeClass("active");
 362+ start.addClass("active");
 363+ }
 364+ }
 365+
 366+ });
 367+ }
 368+ }
 369+})(jQuery);
\ No newline at end of file
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.autoSuggest.js
___________________________________________________________________
Added: svn:eol-style
1370 + native
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.mwCoolCats.js
@@ -0,0 +1,130 @@
 2+( function ( $j ) { $j.fn.mwCoolCats = function( options ) {
 3+
 4+ var defaults = {
 5+ buttontext: 'Add'
 6+ };
 7+
 8+ var settings = $j.extend( {}, defaults, options);
 9+
 10+ // usually Category:Foo
 11+ var categoryNamespace = wgFormattedNamespaces[wgNamespaceIds['category']];
 12+
 13+ var $container;
 14+ return this.each( function() {
 15+ var _this = $j( this );
 16+ _this.addClass( 'categoryInput' );
 17+
 18+ _this.suggestions( {
 19+ 'fetch': _fetchSuggestions,
 20+ 'cancel': function() {
 21+ var req = $j( this ).data( 'request' );
 22+ if ( req.abort ) {
 23+ req.abort();
 24+ }
 25+ }
 26+ } );
 27+ _this.suggestions();
 28+
 29+ _this.wrap('<div class="cat-widget"></div>');
 30+ $container = _this.parent(); // set to the cat-widget class we just wrapped
 31+ $container.append( $j( '<button type="button">'+settings.buttontext+'</button>' )
 32+ .click( function(e) {
 33+ e.stopPropagation();
 34+ e.preventDefault();
 35+ _processInput();
 36+ return false;
 37+ }) );
 38+ $container.prepend('<ul class="cat-list pkg"></ul>');
 39+
 40+ //XXX ensure this isn't blocking other stuff needed.
 41+ _this.parents('form').submit( function() {
 42+ _processInput();
 43+ });
 44+
 45+ _this.keyup(function(e) {
 46+ if(e.keyCode == 13) {
 47+ e.stopPropagation();
 48+ e.preventDefault();
 49+ _processInput();
 50+ }
 51+ });
 52+
 53+ this.getWikiText = function() {
 54+ return _getCats().map( function() { return '[[' + categoryNamespace + ':' + this + ']]'; } )
 55+ .toArray()
 56+ .join( "\n" );
 57+ };
 58+
 59+ _processInput();
 60+ });
 61+
 62+ function _processInput() {
 63+ var $input = $container.find( 'input' );
 64+ _insertCat( $input.val().trim() );
 65+ $input.val("");
 66+ }
 67+
 68+ function _insertCat( cat ) {
 69+ if ( mw.isEmpty( cat ) || _containsCat( cat ) ) {
 70+ return;
 71+ }
 72+ var href = _catLink( cat );
 73+ var $li = $j( '<li class="cat"></li>' );
 74+ $container.find( 'ul' ).append( $li );
 75+ $li.append( '<a class="cat" target="_new" href="' + href + '">' + cat +' </a>' );
 76+ $li.append( $j.fn.removeCtrl( 'mwe-upwiz-category-remove', function() { $li.remove(); } ) );
 77+ }
 78+
 79+ function _catLink( cat ) {
 80+ var catLink =
 81+ encodeURIComponent( categoryNamespace )
 82+ + ':'
 83+ + encodeURIComponent( mw.ucfirst( cat.replace(/ /g, '_' ) ) );
 84+
 85+ // wgServer typically like 'http://commons.prototype.wikimedia.org'
 86+ // wgArticlePath typically like '/wiki/$1'
 87+ if ( ! ( mw.isEmpty( wgServer ) && mw.isEmpty( wgArticlePath ) ) ) {
 88+ catLink = wgServer + wgArticlePath.replace( /\$1/, catLink );
 89+ }
 90+
 91+ return catLink;
 92+ }
 93+
 94+ function _getCats() {
 95+ return $container.find('ul li a.cat').map( function() { return $j.trim( this.text ); } );
 96+ }
 97+
 98+ function _containsCat( cat ) {
 99+ return _getCats().filter( function() { return this == cat; } ).length !== 0;
 100+ }
 101+
 102+ function _fetchSuggestions( query ) {
 103+ var _this = this;
 104+ var request = $j.ajax( {
 105+ url: wgScriptPath + '/api.php',
 106+ data: {
 107+ 'action': 'query',
 108+ 'list': 'allpages',
 109+ 'apnamespace': wgNamespaceIds['category'],
 110+ 'apprefix': $j( this ).val(),
 111+ 'format': 'json'
 112+ },
 113+ dataType: 'json',
 114+ success: function( data ) {
 115+ // Process data.query.allpages into an array of titles
 116+ var pages = data.query.allpages;
 117+ var titleArr = [];
 118+
 119+ $j.each( pages, function( i, page ) {
 120+ var title = page.title.split( ':', 2 )[1];
 121+ titleArr.push( title );
 122+ } );
 123+
 124+ $j( _this ).suggestions( 'suggestions', titleArr );
 125+ }
 126+ } );
 127+
 128+ $j( _this ).data( 'request', request );
 129+ }
 130+
 131+}})(jQuery);
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.mwCoolCats.js
___________________________________________________________________
Added: svn:eol-style
1132 + native
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.morphCrossfade.js
@@ -0,0 +1,110 @@
 2+/**
 3+ * jQuery Morphing Crossfade plugin
 4+ * Copyright Neil Kandalgaonkar, 2010
 5+ *
 6+ * This work is licensed under the terms of the GNU General Public License,
 7+ * version 2 or later.
 8+ * (see http://www.fsf.org/licensing/licenses/gpl.html).
 9+ * Derivative works and later versions of the code must be free software
 10+ * licensed under the same or a compatible license.
 11+ *
 12+ * There are a lot of cross-fading plugins out there, but most assume that all
 13+ * elements are the same, fixed width. This will also grow or shrink the container
 14+ * vertically while crossfading. This can be useful when (for instance) you have a
 15+ * control panel and you want to switch from a simpler interface to a more advanced
 16+ * version. Or, perhaps you like the way the Mac OS X Preferences panel works, where
 17+ * you click on an icon and get a crossfade effect to another dialog, even if it's one
 18+ * with different dimensions.
 19+ *
 20+ * How to use it:
 21+ * Create some DOM structure where all the panels you want to crossfade are contained in
 22+ * one parent, e.g.
 23+ *
 24+ * <div id="container">
 25+ * <div id="panel1"/>
 26+ * <div id="panel2"/>
 27+ * <div id="panel3"/>
 28+ * </div>
 29+ *
 30+ * Initialize the crossfader:
 31+ *
 32+ * $( '#container' ).morphCrossfader();
 33+ *
 34+ * By default, this will hide all elements except the first child (in this case #panel1).
 35+ *
 36+ * Then, whenever you want to crossfade, do something like this. The currently selected panel
 37+ * will fade away, and your selection will fade in.
 38+ *
 39+ * $( '#container' ).morphCrossfade( '#panel2' );
 40+ *
 41+ */
 42+
 43+( function( $ ) {
 44+ /**
 45+ * Initialize crossfading of the children of an element
 46+ */
 47+ $.fn.morphCrossfader = function() {
 48+ // the elements that are immediate children are the crossfadables
 49+ // they must all be "on top" of each other, so position them relative
 50+ this.css( {
 51+ position : 'relative',
 52+ overflow : 'hidden',
 53+ scroll: 'none'
 54+ } );
 55+ this.children().css( {
 56+ position: 'absolute',
 57+ 'top': '0px',
 58+ left : '0px',
 59+ scroll: 'none',
 60+ opacity: 0,
 61+ visibility: 'hidden'
 62+ } );
 63+
 64+ // should achieve the same result as crossfade( this.children().first() ) but without
 65+ // animation etc.
 66+ this.morphCrossfade( this.children().first(), 0 );
 67+
 68+ return this;
 69+ };
 70+
 71+ /**
 72+ * Initialize crossfading of the children of an element
 73+ * @param selector of new thing to show; should be an immediate child of the crossfader element
 74+ * @param speed (optional) how fast to crossfade, in milliseconds
 75+ */
 76+ $.fn.morphCrossfade = function( newPanelSelector, speed ) {
 77+ var container = this;
 78+ if ( typeof speed === 'undefined' ) {
 79+ speed = 400;
 80+ }
 81+
 82+ container.css( { 'overflow' : 'hidden' } );
 83+
 84+ $oldPanel = $( container.data( 'crossfadeDisplay' ) );
 85+ if ( $oldPanel ) {
 86+ // remove auto setting of height from container, and
 87+ // make doubly sure that the container height is equal to oldPanel
 88+ container.css( { height: $oldPanel.outerHeight() } );
 89+ // take it out of the flow
 90+ $oldPanel.css( { position: 'absolute' } );
 91+ // fade WITHOUT hiding when opacity = 0
 92+ $oldPanel.animate( { opacity: 0 }, speed, 'linear', function() {
 93+ $oldPanel.css( { visibility: 'hidden'} )
 94+ } );
 95+ }
 96+ container.data( 'crossfadeDisplay', newPanelSelector );
 97+
 98+ var $newPanel = $( newPanelSelector );
 99+ $newPanel.css( { visibility: 'visible' } );
 100+ container.animate( { height: $newPanel.outerHeight() }, speed, 'linear', function() {
 101+ // we place it back into the flow, in case its size changes.
 102+ $newPanel.css( { position: 'relative' } );
 103+ // and allow the container to grow with it.
 104+ container.css( { height : 'auto' } );
 105+ } );
 106+ $newPanel.animate( { opacity: 1 }, speed );
 107+
 108+ return container;
 109+ };
 110+
 111+} )( jQuery );
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.morphCrossfade.js
___________________________________________________________________
Added: svn:eol-style
1112 + native
Index: trunk/extensions/UploadWizard/resources/jquery/jquery.validate.additional-methods.js
@@ -0,0 +1,259 @@
 2+(function() {
 3+
 4+ function stripHtml(value) {
 5+ // remove html tags and space chars
 6+ return value.replace(/<.[^<>]*?>/g, ' ').replace(/&nbsp;|&#160;/gi, ' ')
 7+ // remove numbers and punctuation
 8+ .replace(/[0-9.(),;:!?%#$'"_+=\/-]*/g,'');
 9+ }
 10+ jQuery.validator.addMethod("maxWords", function(value, element, params) {
 11+ return this.optional(element) || stripHtml(value).match(/\b\w+\b/g).length < params;
 12+ }, jQuery.validator.format("Please enter {0} words or less."));
 13+
 14+ jQuery.validator.addMethod("minWords", function(value, element, params) {
 15+ return this.optional(element) || stripHtml(value).match(/\b\w+\b/g).length >= params;
 16+ }, jQuery.validator.format("Please enter at least {0} words."));
 17+
 18+ jQuery.validator.addMethod("rangeWords", function(value, element, params) {
 19+ return this.optional(element) || stripHtml(value).match(/\b\w+\b/g).length >= params[0] && value.match(/bw+b/g).length < params[1];
 20+ }, jQuery.validator.format("Please enter between {0} and {1} words."));
 21+
 22+})();
 23+
 24+jQuery.validator.addMethod("letterswithbasicpunc", function(value, element) {
 25+ return this.optional(element) || /^[a-z-.,()'\"\s]+$/i.test(value);
 26+}, "Letters or punctuation only please");
 27+
 28+jQuery.validator.addMethod("alphanumeric", function(value, element) {
 29+ return this.optional(element) || /^\w+$/i.test(value);
 30+}, "Letters, numbers, spaces or underscores only please");
 31+
 32+jQuery.validator.addMethod("lettersonly", function(value, element) {
 33+ return this.optional(element) || /^[a-z]+$/i.test(value);
 34+}, "Letters only please");
 35+
 36+jQuery.validator.addMethod("nowhitespace", function(value, element) {
 37+ return this.optional(element) || /^\S+$/i.test(value);
 38+}, "No white space please");
 39+
 40+jQuery.validator.addMethod("ziprange", function(value, element) {
 41+ return this.optional(element) || /^90[2-5]\d\{2}-\d{4}$/.test(value);
 42+}, "Your ZIP-code must be in the range 902xx-xxxx to 905-xx-xxxx");
 43+
 44+jQuery.validator.addMethod("integer", function(value, element) {
 45+ return this.optional(element) || /^-?\d+$/.test(value);
 46+}, "A positive or negative non-decimal number please");
 47+
 48+/**
 49+* Return true, if the value is a valid vehicle identification number (VIN).
 50+*
 51+* Works with all kind of text inputs.
 52+*
 53+* @example <input type="text" size="20" name="VehicleID" class="{required:true,vinUS:true}" />
 54+* @desc Declares a required input element whose value must be a valid vehicle identification number.
 55+*
 56+* @name jQuery.validator.methods.vinUS
 57+* @type Boolean
 58+* @cat Plugins/Validate/Methods
 59+*/
 60+jQuery.validator.addMethod(
 61+ "vinUS",
 62+ function(v){
 63+ if (v.length != 17)
 64+ return false;
 65+ var i, n, d, f, cd, cdv;
 66+ var LL = ["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"];
 67+ var VL = [1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9];
 68+ var FL = [8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2];
 69+ var rs = 0;
 70+ for(i = 0; i < 17; i++){
 71+ f = FL[i];
 72+ d = v.slice(i,i+1);
 73+ if(i == 8){
 74+ cdv = d;
 75+ }
 76+ if(!isNaN(d)){
 77+ d *= f;
 78+ }
 79+ else{
 80+ for(n = 0; n < LL.length; n++){
 81+ if(d.toUpperCase() === LL[n]){
 82+ d = VL[n];
 83+ d *= f;
 84+ if(isNaN(cdv) && n == 8){
 85+ cdv = LL[n];
 86+ }
 87+ break;
 88+ }
 89+ }
 90+ }
 91+ rs += d;
 92+ }
 93+ cd = rs % 11;
 94+ if(cd == 10){cd = "X";}
 95+ if(cd == cdv){return true;}
 96+ return false;
 97+ },
 98+ "The specified vehicle identification number (VIN) is invalid."
 99+);
 100+
 101+/**
 102+ * Return true, if the value is a valid date, also making this formal check dd/mm/yyyy.
 103+ *
 104+ * @example jQuery.validator.methods.date("01/01/1900")
 105+ * @result true
 106+ *
 107+ * @example jQuery.validator.methods.date("01/13/1990")
 108+ * @result false
 109+ *
 110+ * @example jQuery.validator.methods.date("01.01.1900")
 111+ * @result false
 112+ *
 113+ * @example <input name="pippo" class="{dateITA:true}" />
 114+ * @desc Declares an optional input element whose value must be a valid date.
 115+ *
 116+ * @name jQuery.validator.methods.dateITA
 117+ * @type Boolean
 118+ * @cat Plugins/Validate/Methods
 119+ */
 120+jQuery.validator.addMethod(
 121+ "dateITA",
 122+ function(value, element) {
 123+ var check = false;
 124+ var re = /^\d{1,2}\/\d{1,2}\/\d{4}$/;
 125+ if( re.test(value)){
 126+ var adata = value.split('/');
 127+ var gg = parseInt(adata[0],10);
 128+ var mm = parseInt(adata[1],10);
 129+ var aaaa = parseInt(adata[2],10);
 130+ var xdata = new Date(aaaa,mm-1,gg);
 131+ if ( ( xdata.getFullYear() == aaaa ) && ( xdata.getMonth () == mm - 1 ) && ( xdata.getDate() == gg ) )
 132+ check = true;
 133+ else
 134+ check = false;
 135+ } else
 136+ check = false;
 137+ return this.optional(element) || check;
 138+ },
 139+ "Please enter a correct date"
 140+);
 141+
 142+jQuery.validator.addMethod("dateNL", function(value, element) {
 143+ return this.optional(element) || /^\d\d?[\.\/-]\d\d?[\.\/-]\d\d\d?\d?$/.test(value);
 144+ }, "Vul hier een geldige datum in."
 145+);
 146+
 147+jQuery.validator.addMethod("time", function(value, element) {
 148+ return this.optional(element) || /^([01][0-9])|(2[0123]):([0-5])([0-9])$/.test(value);
 149+ }, "Please enter a valid time, between 00:00 and 23:59"
 150+);
 151+
 152+/**
 153+ * matches US phone number format
 154+ *
 155+ * where the area code may not start with 1 and the prefix may not start with 1
 156+ * allows '-' or ' ' as a separator and allows parens around area code
 157+ * some people may want to put a '1' in front of their number
 158+ *
 159+ * 1(212)-999-2345
 160+ * or
 161+ * 212 999 2344
 162+ * or
 163+ * 212-999-0983
 164+ *
 165+ * but not
 166+ * 111-123-5434
 167+ * and not
 168+ * 212 123 4567
 169+ */
 170+jQuery.validator.addMethod("phoneUS", function(phone_number, element) {
 171+ phone_number = phone_number.replace(/\s+/g, "");
 172+ return this.optional(element) || phone_number.length > 9 &&
 173+ phone_number.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
 174+}, "Please specify a valid phone number");
 175+
 176+jQuery.validator.addMethod('phoneUK', function(phone_number, element) {
 177+return this.optional(element) || phone_number.length > 9 &&
 178+phone_number.match(/^(\(?(0|\+44)[1-9]{1}\d{1,4}?\)?\s?\d{3,4}\s?\d{3,4})$/);
 179+}, 'Please specify a valid phone number');
 180+
 181+jQuery.validator.addMethod('mobileUK', function(phone_number, element) {
 182+return this.optional(element) || phone_number.length > 9 &&
 183+phone_number.match(/^((0|\+44)7(5|6|7|8|9){1}\d{2}\s?\d{6})$/);
 184+}, 'Please specify a valid mobile number');
 185+
 186+// TODO check if value starts with <, otherwise don't try stripping anything
 187+jQuery.validator.addMethod("strippedminlength", function(value, element, param) {
 188+ return jQuery(value).text().length >= param;
 189+}, jQuery.validator.format("Please enter at least {0} characters"));
 190+
 191+// same as email, but TLD is optional
 192+jQuery.validator.addMethod("email2", function(value, element, param) {
 193+ return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
 194+}, jQuery.validator.messages.email);
 195+
 196+// same as url, but TLD is optional
 197+jQuery.validator.addMethod("url2", function(value, element, param) {
 198+ return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
 199+}, jQuery.validator.messages.url);
 200+
 201+// NOTICE: Modified version of Castle.Components.Validator.CreditCardValidator
 202+// Redistributed under the the Apache License 2.0 at http://www.apache.org/licenses/LICENSE-2.0
 203+// Valid Types: mastercard, visa, amex, dinersclub, enroute, discover, jcb, unknown, all (overrides all other settings)
 204+jQuery.validator.addMethod("creditcardtypes", function(value, element, param) {
 205+
 206+ if (/[^0-9-]+/.test(value))
 207+ return false;
 208+
 209+ value = value.replace(/\D/g, "");
 210+
 211+ var validTypes = 0x0000;
 212+
 213+ if (param.mastercard)
 214+ validTypes |= 0x0001;
 215+ if (param.visa)
 216+ validTypes |= 0x0002;
 217+ if (param.amex)
 218+ validTypes |= 0x0004;
 219+ if (param.dinersclub)
 220+ validTypes |= 0x0008;
 221+ if (param.enroute)
 222+ validTypes |= 0x0010;
 223+ if (param.discover)
 224+ validTypes |= 0x0020;
 225+ if (param.jcb)
 226+ validTypes |= 0x0040;
 227+ if (param.unknown)
 228+ validTypes |= 0x0080;
 229+ if (param.all)
 230+ validTypes = 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080;
 231+
 232+ if (validTypes & 0x0001 && /^(51|52|53|54|55)/.test(value)) { //mastercard
 233+ return value.length == 16;
 234+ }
 235+ if (validTypes & 0x0002 && /^(4)/.test(value)) { //visa
 236+ return value.length == 16;
 237+ }
 238+ if (validTypes & 0x0004 && /^(34|37)/.test(value)) { //amex
 239+ return value.length == 15;
 240+ }
 241+ if (validTypes & 0x0008 && /^(300|301|302|303|304|305|36|38)/.test(value)) { //dinersclub
 242+ return value.length == 14;
 243+ }
 244+ if (validTypes & 0x0010 && /^(2014|2149)/.test(value)) { //enroute
 245+ return value.length == 15;
 246+ }
 247+ if (validTypes & 0x0020 && /^(6011)/.test(value)) { //discover
 248+ return value.length == 16;
 249+ }
 250+ if (validTypes & 0x0040 && /^(3)/.test(value)) { //jcb
 251+ return value.length == 16;
 252+ }
 253+ if (validTypes & 0x0040 && /^(2131|1800)/.test(value)) { //jcb
 254+ return value.length == 15;
 255+ }
 256+ if (validTypes & 0x0080) { //unknown
 257+ return true;
 258+ }
 259+ return false;
 260+}, "Please enter a valid credit card number.");
Property changes on: trunk/extensions/UploadWizard/resources/jquery/jquery.validate.additional-methods.js
___________________________________________________________________
Added: svn:eol-style
1261 + native
Index: trunk/extensions/UploadWizard/resources/jquery.autoSuggest.css
@@ -0,0 +1,218 @@
 2+/* AutoSuggest CSS - Version 1.2 */
 3+
 4+ul.as-selections {
 5+ list-style-type: none;
 6+ list-style-image: none;
 7+ border-top: 1px solid #888;
 8+ border-bottom: 1px solid #b6b6b6;
 9+ border-left: 1px solid #aaa;
 10+ border-right: 1px solid #aaa;
 11+ padding: 4px 0 4px 4px;
 12+ margin: 0;
 13+ overflow: auto;
 14+ background-color: #fff;
 15+ box-shadow:inset 0 1px 2px #888;
 16+ -webkit-box-shadow:inset 0 1px 2px #888;
 17+ -moz-box-shadow:inset 0 1px 2px #888;
 18+}
 19+
 20+ul.as-selections.loading {
 21+ background-color: #eee;
 22+}
 23+
 24+ul.as-selections li {
 25+ float: left;
 26+ margin: 1px 4px 1px 0;
 27+}
 28+
 29+ul.as-selections li.as-selection-item {
 30+ color: #2b3840;
 31+ font-size: 13px;
 32+ font-family: "Lucida Grande", arial, sans-serif;
 33+ text-shadow: 0 1px 1px #fff;
 34+ background-color: #ddeefe;
 35+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ddeefe), to(#bfe0f1));
 36+ border: 1px solid #acc3ec;
 37+ border-top-color: #c0d9e9;
 38+ padding: 2px 7px 2px 10px;
 39+ border-radius: 12px;
 40+ -webkit-border-radius: 12px;
 41+ -moz-border-radius: 12px;
 42+ box-shadow: 0 1px 1px #e4edf2;
 43+ -webkit-box-shadow: 0 1px 1px #e4edf2;
 44+ -moz-box-shadow: 0 1px 1px #e4edf2;
 45+}
 46+
 47+ul.as-selections li.as-selection-item:last-child {
 48+ margin-left: 30px;
 49+}
 50+
 51+ul.as-selections li.as-selection-item a.as-close {
 52+ float: right;
 53+ margin: 1px 0 0 7px;
 54+ padding: 0 2px;
 55+ cursor: pointer;
 56+ color: #5491be;
 57+ font-family: "Helvetica", helvetica, arial, sans-serif;
 58+ font-size: 14px;
 59+ font-weight: bold;
 60+ text-shadow: 0 1px 1px #fff;
 61+ -webkit-transition: color .1s ease-in;
 62+}
 63+
 64+ul.as-selections li.as-selection-item.blur {
 65+ color: #666666;
 66+ background-color: #f4f4f4;
 67+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f4f4f4), to(#d5d5d5));
 68+ border-color: #bbb;
 69+ border-top-color: #ccc;
 70+ box-shadow: 0 1px 1px #e9e9e9;
 71+ -webkit-box-shadow: 0 1px 1px #e9e9e9;
 72+ -moz-box-shadow: 0 1px 1px #e9e9e9;
 73+}
 74+
 75+ul.as-selections li.as-selection-item.blur a.as-close {
 76+ color: #999;
 77+}
 78+
 79+ul.as-selections li:hover.as-selection-item {
 80+ color: #2b3840;
 81+ background-color: #bbd4f1;
 82+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#bbd4f1), to(#a3c2e5));
 83+ border-color: #6da0e0;
 84+ border-top-color: #8bb7ed;
 85+}
 86+
 87+ul.as-selections li:hover.as-selection-item a.as-close {
 88+ color: #4d70b0;
 89+}
 90+
 91+ul.as-selections li.as-selection-item.selected {
 92+ border-color: #1f30e4;
 93+}
 94+
 95+ul.as-selections li.as-selection-item a:hover.as-close {
 96+ color: #1b3c65;
 97+}
 98+
 99+ul.as-selections li.as-selection-item a:active.as-close {
 100+ color: #4d70b0;
 101+}
 102+
 103+ul.as-selections li.as-original {
 104+ margin-left: 0;
 105+}
 106+
 107+ul.as-selections li.as-original input {
 108+ border: none;
 109+ outline: none;
 110+ font-size: 13px;
 111+ width: 120px;
 112+ height: 18px;
 113+ padding-top: 3px;
 114+}
 115+
 116+ul.as-list {
 117+ position: absolute;
 118+ list-style-type: none;
 119+ margin: 2px 0 0 0;
 120+ padding: 0;
 121+ font-size: 14px;
 122+ color: #000;
 123+ font-family: "Lucida Grande", arial, sans-serif;
 124+ background-color: #fff;
 125+ background-color: rgba(255,255,255,0.95);
 126+ z-index: 2;
 127+ box-shadow: 0 2px 12px #222;
 128+ -webkit-box-shadow: 0 2px 12px #222;
 129+ -moz-box-shadow: 0 2px 12px #222;
 130+ border-radius: 5px;
 131+ -webkit-border-radius: 5px;
 132+ -moz-border-radius: 5px;
 133+}
 134+
 135+li.as-result-item, li.as-message {
 136+ margin: 0 0 0 0;
 137+ padding: 5px 12px;
 138+ background-color: transparent;
 139+ border: 1px solid #fff;
 140+ border-bottom: 1px solid #ddd;
 141+ cursor: pointer;
 142+ border-radius: 5px;
 143+ -webkit-border-radius: 5px;
 144+ -moz-border-radius: 5px;
 145+}
 146+
 147+li:first-child.as-result-item {
 148+ margin: 0;
 149+}
 150+
 151+li.as-message {
 152+ margin: 0;
 153+ cursor: default;
 154+}
 155+
 156+li.as-result-item.active {
 157+ background-color: #3668d9;
 158+ background-image: -webkit-gradient(linear, 0% 0%, 0% 64%, from(rgb(110, 129, 245)), to(rgb(62, 82, 242)));
 159+ border-color: #3342e8;
 160+ color: #fff;
 161+ text-shadow: 0 1px 2px #122042;
 162+}
 163+
 164+li.as-result-item em {
 165+ font-style: normal;
 166+ background: #444;
 167+ padding: 0 2px;
 168+ color: #fff;
 169+}
 170+
 171+li.as-result-item.active em {
 172+ background: #253f7a;
 173+ color: #fff;
 174+}
 175+
 176+/* Webkit Hacks */
 177+@media screen and (-webkit-min-device-pixel-ratio:0) {
 178+ ul.as-selections {
 179+ border-top-width: 2px;
 180+ }
 181+ ul.as-selections li.as-selection-item {
 182+ padding-top: 3px;
 183+ padding-bottom: 3px;
 184+ }
 185+ ul.as-selections li.as-selection-item a.as-close {
 186+ margin-top: -1px;
 187+ }
 188+ ul.as-selections li.as-original input {
 189+ height: 19px;
 190+ }
 191+}
 192+
 193+/* Opera Hacks */
 194+@media all and (-webkit-min-device-pixel-ratio:10000), not all and (-webkit-min-device-pixel-ratio:0) {
 195+ ul.as-list {
 196+ border: 1px solid #888;
 197+ }
 198+ ul.as-selections li.as-selection-item a.as-close {
 199+ margin-left: 4px;
 200+ margin-top: 0;
 201+ }
 202+}
 203+
 204+/* IE Hacks */
 205+ul.as-list {
 206+ border: 1px solid #888\9;
 207+}
 208+ul.as-selections li.as-selection-item a.as-close {
 209+ margin-left: 4px\9;
 210+ margin-top: 0\9;
 211+}
 212+
 213+/* Firefox 3.0 Hacks */
 214+ul.as-list, x:-moz-any-link, x:default {
 215+ border: 1px solid #888;
 216+}
 217+BODY:first-of-type ul.as-list, x:-moz-any-link, x:default { /* Target FF 3.5+ */
 218+ border: none;
 219+}
Property changes on: trunk/extensions/UploadWizard/resources/jquery.autoSuggest.css
___________________________________________________________________
Added: svn:eol-style
1220 + native
Index: trunk/extensions/UploadWizard/resources/jquery.mwCoolCats.css
@@ -0,0 +1,63 @@
 2+.cat-widget {
 3+ clear: both;
 4+ position: relative;
 5+}
 6+.cat-widget ul {
 7+ list-style: none;
 8+ padding-left: 0;
 9+ margin-top: 0;
 10+ margin-left: 0;
 11+}
 12+.cat-widget li {
 13+ float: left;
 14+ margin-right: 10px;
 15+ padding: 3px;
 16+}
 17+.cat-widget li a {
 18+ text-decoration: none;
 19+}
 20+.cat-widget li:hover a {
 21+ text-decoration: none;
 22+ color: white;
 23+}
 24+.cat-widget li {
 25+ background-color: #fff;
 26+ border-radius: 4px;
 27+ -moz-border-radius: 4px;
 28+ -webkit-border-radius: 4px;
 29+ border-top: 1px solid #fff;
 30+ border: 1px solid #fff;
 31+ color: black;
 32+ font-size: 12px;
 33+
 34+}
 35+.cat-widget li:hover {
 36+ background-color: #444;
 37+ border-radius: 4px;
 38+ -moz-border-radius: 4px;
 39+ -webkit-border-radius: 4px;
 40+ border-top: 1px solid #111;
 41+ border: 1px solid #222;
 42+ color:white;
 43+}
 44+
 45+.cat-widget li div.mwe-upwiz-remove-ctrl {
 46+ display: inline-block;
 47+}
 48+
 49+
 50+/* Utilities */
 51+.pkg:after, #content-inner:after {
 52+ content: " ";
 53+ display: block;
 54+ visibility: hidden;
 55+ clear: both;
 56+ height: 0.1px;
 57+ font-size: 0.1em;
 58+ line-height: 0;
 59+}
 60+.pkg, #content-inner { display: inline-block; }
 61+/* no ie mac \*/
 62+* html .pkg, * html #content-inner { height: 1%; }
 63+.pkg, #content-inner { display: block; }
 64+/* */
Property changes on: trunk/extensions/UploadWizard/resources/jquery.mwCoolCats.css
___________________________________________________________________
Added: svn:eol-style
165 + native
Index: trunk/extensions/UploadWizard/resources/jquery.tipsy.help.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/UploadWizard/resources/jquery.tipsy.help.gif
___________________________________________________________________
Added: svn:mime-type
266 + application/octet-stream
Index: trunk/extensions/UploadWizard/resources/jquery.tipsy.error.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/UploadWizard/resources/jquery.tipsy.error.gif
___________________________________________________________________
Added: svn:mime-type
367 + application/octet-stream
Index: trunk/extensions/UploadWizard/UploadWizard.php
@@ -6,7 +6,6 @@
77 * @ingroup Extensions
88 *
99 * This file contains the include file for UploadWizard
10 - * This is dependent on JS2Support.
1110 *
1211 * Usage: Include the following line in your LocalSettings.php
1312 * require_once( "$IP/extensions/UploadWizard/UploadWizard.php" );
@@ -29,22 +28,21 @@
3029 'url' => 'http://www.mediawiki.org/wiki/Extension:UploadWizard'
3130 );
3231
33 -// Check for JS2 support
34 -if( ! isset( $wgEnableJS2system ) ){
35 - throw new MWException( 'UploadWizard requires JS2 Support. Please include the JS2Support extension.');
36 -}
3732
38 -
3933 $dir = dirname(__FILE__) . '/';
4034
4135 $wgExtensionMessagesFiles['UploadWizard'] = $dir . 'UploadWizard.i18n.php';
4236 $wgExtensionAliasesFiles['UploadWizard'] = $dir . 'UploadWizard.alias.php';
4337
44 -# Add the special page
 38+# Require modules, includeing the special page
4539 $wgAutoloadLocalClasses[ 'SpecialUploadWizard' ] = $dir . 'SpecialUploadWizard.php';
 40+$wgAutoloadLocalClasses[ 'UploadWizardMessages' ] = $dir . 'UploadWizardMessages.php';
 41+
 42+# Let the special page be a special center of unique specialness
4643 $wgSpecialPages['UploadWizard'] = 'SpecialUploadWizard';
4744 $wgSpecialPageGroups['UploadWizard'] = 'media';
4845
 46+// JS2?
4947 $wgResourceLoaderNamedPaths[ 'UploadWizardPage' ] = 'extensions/UploadWizard/UploadWizardPage.js';
5048
5149 // Set up the javascript path for the loader and localization file.
Index: trunk/extensions/UploadWizard/SpecialUploadWizard.php
@@ -2,7 +2,7 @@
33 /**
44 * Special:UploadWizard
55 *
6 - * Usability Initiative multi-file upload page.
 6+ * Easy to use multi-file upload page.
77 *
88 * @file
99 * @ingroup SpecialPage
@@ -13,13 +13,9 @@
1414
1515 // $request is the request (usually wgRequest)
1616 // $par is everything in the URL after Special:UploadWizard. Not sure what we can use it for
17 - public function __construct( $request=null ) {
18 - global $wgEnableJS2, $wgEnableAPI, $wgRequest;
 17+ public function __construct( $request=null, $par=null ) {
 18+ global $wgEnableAPI, $wgRequest;
1919
20 - if (! $wgEnableJS2) {
21 - // XXX complain
22 - }
23 -
2420 if (! $wgEnableAPI) {
2521 // XXX complain
2622 }
@@ -39,60 +35,113 @@
4036 * @param subpage, e.g. the "foo" in Special:UploadWizard/foo.
4137 */
4238 public function execute( $subPage ) {
43 - global $wgUser, $wgOut;
 39+ global $wgMessageCache, $wgScriptPath, $wgLang, $wgUser, $wgOut;
4440
45 - # Check uploading enabled
46 - if( !UploadBase::isEnabled() ) {
47 - $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' );
 41+ // canUpload and canUserUpload have side effects;
 42+ // if we can't upload, will print error page to wgOut
 43+ // and return false
 44+ if (! ( $this->isUploadAllowed() && $this->isUserUploadAllowed( $wgUser ) ) ) {
4845 return;
4946 }
5047
51 - # Check permissions
52 - global $wgGroupPermissions;
53 - if( !$wgUser->isAllowed( 'upload' ) ) {
54 - if( !$wgUser->isLoggedIn() && ( $wgGroupPermissions['user']['upload']
55 - || $wgGroupPermissions['autoconfirmed']['upload'] ) ) {
56 - // Custom message if logged-in users without any special rights can upload
57 - $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
58 - } else {
59 - $wgOut->permissionRequired( 'upload' );
60 - }
61 - return;
62 - }
 48+ $langCode = $wgLang->getCode();
 49+
 50+ // XXX what does this really do??
 51+ $wgMessageCache->loadAllMessages();
6352
64 - # Check blocks
65 - if( $wgUser->isBlocked() ) {
66 - $wgOut->blockedPage();
67 - return;
68 - }
 53+ $this->setHeaders();
 54+ $this->outputHeader();
6955
70 - # Check whether we actually want to allow changing stuff
71 - if( wfReadOnly() ) {
72 - $wgOut->readOnlyPage();
73 - return;
 56+ /* Doing resource loading the old-fashioned way for now until there's some kind of script-loading
 57+ strategy that everyone agrees on, or is available generally */
 58+ $scripts = array(
 59+ // jquery is already loaded by vector.
 60+ // "resources/jquery-1.4.2.js",
 61+
 62+ // jquery standard stuff
 63+ "resources/jquery.ui/ui/ui.core.js",
 64+ "resources/jquery.ui/ui/ui.progressbar.js",
 65+ "resources/jquery.ui/ui/ui.datepicker.js",
 66+ "resources/jquery.autocomplete.js",
 67+
 68+ // miscellaneous utilities
 69+ "resources/mw.Utilities.js",
 70+ "resources/mw.UtilitiesTime.js",
 71+ "resources/mw.Log.js",
 72+ // "resources/mw.MockUploadHandler.js",
 73+
 74+ // message parsing and such
 75+ "resources/language/mw.Language.js",
 76+ "resources/language/mw.Parser.js",
 77+ "resources/mw.LanguageUpWiz.js",
 78+
 79+ // workhorse libraries
 80+ // "resources/mw.UploadApiProcessor.js",
 81+ "resources/mw.IframeTransport.js",
 82+ "resources/mw.ApiUploadHandler.js",
 83+ "resources/mw.DestinationChecker.js",
 84+
 85+ // interface helping stuff
 86+ "resources/jquery.tipsy.js",
 87+ "resources/jquery.morphCrossfade.js",
 88+ "resources/jquery.validate.js",
 89+ "resources/jquery.arrowSteps.js",
 90+ "resources/jquery.mwCoolCats.js",
 91+
 92+ // the thing that does most of it
 93+ "resources/mw.UploadWizard.js",
 94+
 95+ // finally the thing that launches it all
 96+ "UploadWizardPage.js"
 97+ );
 98+
 99+ if ($langCode !== 'en' ) {
 100+ $scripts[] = "js/language/classes/Language" . ucfirst( $langCode ) . ".js";
74101 }
 102+
 103+ $extensionPath = $wgScriptPath . "/extensions/UploadWizard";
 104+
 105+ foreach ( $scripts as $script ) {
 106+ $wgOut->addScriptFile( $extensionPath . "/" . $script );
 107+ }
 108+ // after scripts, get the i18n.php stuff
 109+ $wgOut->addInlineScript( UploadWizardMessages::getMessagesJs( 'UploadWizard', $wgLang ) );
75110
 111+ $styles = array(
 112+ "resources/jquery.tipsy.css",
 113+ "resources/uploadWizard.css",
 114+ "resources/jquery.arrowSteps.css",
 115+ "resources/jquery.mwCoolCats.css"
 116+ );
76117
77 - $this->setHeaders();
78 - $this->outputHeader();
79 -
 118+ // TODO RTL
 119+ foreach ( $styles as $style ) {
 120+ $wgOut->addStyle( $extensionPath . "/" . $style, '', '', 'ltr' );
 121+ }
 122+
 123+ $this->addJsVars( $subPage );
 124+
 125+
 126+ // where the uploadwizard will go
 127+ // TODO import more from UploadWizard itself.
80128 $wgOut->addHTML(
81129 '<div id="upload-licensing" class="upload-section" style="display: none;">Licensing tutorial</div>'
82130 . '<div id="upload-wizard" class="upload-section"><div class="loadingSpinner"></div></div>'
83131 );
 132+
84133
 134+ // fallback for non-JS
85135 $wgOut->addHTML('<noscript>');
86136 $this->simpleForm->show();
87137 $wgOut->addHTML('</noscript>');
88 -
89 - $this->addJS( $subPage );
 138+
90139 }
91140
92141 /**
93142 * Adds some global variables for our use, as well as initializes the UploadWizard
94143 * @param subpage, e.g. the "foo" in Special:UploadWizard/foo
95144 */
96 - public function addJS( $subPage ) {
 145+ public function addJsVars( $subPage ) {
97146 global $wgUser, $wgOut;
98147 global $wgUseAjax, $wgAjaxLicensePreview, $wgEnableAPI;
99148 global $wgEnableFirefogg, $wgFileExtensions;
@@ -115,27 +164,64 @@
116165 // XXX need to have a better function for testing viability of a filename
117166 // 'wgFilenamePrefixBlacklist' => UploadBase::getFilenamePrefixBlacklist()
118167
119 - ) )
120 - );
 168+ ) ) );
121169
122 - // not sure why -- can we even load libraries with an included script, or does that cause things to be out of order?
123 - global $wgScriptPath;
124 - $wgOut->addNamedResource( 'UploadWizardPage', 'page');
 170+ }
125171
 172+ /**
 173+ * Check if anyone can upload (or if other sitewide config prevents this)
 174+ * Side effect: will print error page to wgOut if cannot upload.
 175+ * @return boolean -- true if can upload
 176+ */
 177+ private function isUploadAllowed() {
 178+ global $wgOut;
126179
127 - // XXX unlike other vars this is specific to the file being uploaded -- re-upload context, for instance
128 - // Recorded here because we may probably need to
129 - // bring it back in some form later. Reupload forms may be special, only one file allowed
130 - /*
131 - $scriptVars = array(
132 - 'wgUploadAutoFill' => !$this->mForReUpload,
133 - 'wgUploadSourceIds' => $this->mSourceIds,
134 - );
135 - */
 180+ // Check uploading enabled
 181+ if( !UploadBase::isEnabled() ) {
 182+ $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' );
 183+ return false;
 184+ }
136185
 186+ // Check whether we actually want to allow changing stuff
 187+ if( wfReadOnly() ) {
 188+ $wgOut->readOnlyPage();
 189+ return false;
 190+ }
137191
 192+ // we got all the way here, so it must be okay to upload
 193+ return true;
138194 }
139195
 196+ /**
 197+ * Check if the user can upload
 198+ * Side effect: will print error page to wgOut if cannot upload.
 199+ * @param User
 200+ * @return boolean -- true if can upload
 201+ */
 202+ private function isUserUploadAllowed( $user ) {
 203+ global $wgOut, $wgGroupPermissions;
 204+
 205+ if( !$user->isAllowed( 'upload' ) ) {
 206+ if( !$user->isLoggedIn() && ( $wgGroupPermissions['user']['upload']
 207+ || $wgGroupPermissions['autoconfirmed']['upload'] ) ) {
 208+ // Custom message if logged-in users without any special rights can upload
 209+ $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
 210+ } else {
 211+ $wgOut->permissionRequired( 'upload' );
 212+ }
 213+ return false;
 214+ }
 215+
 216+ // Check blocks
 217+ if( $user->isBlocked() ) {
 218+ $wgOut->blockedPage();
 219+ return false;
 220+ }
 221+
 222+ // we got all the way here, so it must be okay to upload
 223+ return true;
 224+ }
 225+
140226 }
141227
142228
Index: trunk/extensions/UploadWizard/README
@@ -1,11 +1,10 @@
2 -UploadWizard is alpha software, not intended for use live Wikimedia sites yet (or anyone else's).
 2+Developers should not modify the contents of mwEmbed.i18n.php directly.
33
4 -UploadWizard dependent on js2 / mwEmbed, you must include JS2Support extension prior to UploadWizard.php
 4+mwEmbed.i18n.php is updated by maintenance scripts.
55
6 -# To enable add the following to localSettings.php
 6+Two scripts write to this file:
 7+* mwEmbed/includes/maintenance/mergeJavascriptMsg.php
 8+Copies English messages from every mwEmbed javascript module
79
8 -# JS2 / mwEmbed Support
9 -require_once( "$IP/extensions/JS2Support/JS2Support.php" );
10 -
11 -# Upload Wizard
12 -require_once( "$IP/extensions/UploadWizard/UploadWizard.php" );
 10+* Translate wiki script
 11+Copies the translate wiki localised msgs into the file.
\ No newline at end of file
Index: trunk/extensions/UploadWizard/UploadWizardPage.js
@@ -1,86 +1,87 @@
2 -
32 /*
43 * This script is run on [[Special:UploadWizard]].
54 * Creates an interface for uploading files in multiple steps, hence "wizard"
65 */
76
8 -
9 -
107 // create UploadWizard
11 -mw.ready( function() {
 8+mw.UploadWizardPage = function() {
129 // add the discussion link
13 - var discussListItem = addPortletLink( 'p-namespaces',
14 - 'http://usability.wikimedia.org/wiki/Multimedia_talk:Upload_wizard',
15 - 'Discussion',
16 - 'usability_upload_wizard_discussion',
17 - 'Discuss this experimental extension at the Usability wiki');
 10+ var discussListItem = addPortletLink( 'p-namespaces',
 11+ 'http://usability.wikimedia.org/wiki/Multimedia:Upload_wizard/Questions_%26_Answers',
 12+ 'Q & A',
 13+ 'usability_upload_wizard_qa',
 14+ 'Questions & answers about this experimental extension at the Usability wiki');
1815 var discussLink = discussListItem.getElementsByTagName( 'a' )[0];
1916 discussLink.setAttribute( 'target', 'usability_discussion' );
2017
21 - mw.load( 'UploadWizard.UploadWizard', function () {
22 - mw.setConfig( 'debug', true );
 18+
 19+ var apiUrl = false;
 20+ if ( typeof wgServer != 'undefined' && typeof wgScriptPath != 'undefined' ) {
 21+ apiUrl = wgServer + wgScriptPath + '/api.php';
 22+ }
2323
24 - mw.setDefaultConfig( 'uploadHandlerClass', null );
 24+ var config = {
 25+ debug: true,
 26+ userName: wgUserName,
 27+ userLanguage: wgUserLanguage,
 28+ fileExtensions: wgFileExtensions,
 29+ apiUrl: apiUrl,
2530
26 - mw.setConfig( {
27 - debug: true,
28 - userName: wgUserName,
29 - userLanguage: wgUserLanguage,
30 - fileExtensions: wgFileExtensions,
 31+ // XXX this is problematic, if the upload wizard is idle for a long time the token expires
 32+ // should get token just before uploading
 33+ token: wgEditToken,
 34+
 35+ thumbnailWidth: 120,
 36+ smallThumbnailWidth: 60,
 37+ maxAuthorLength: 50,
 38+ minAuthorLength: 2,
 39+ maxSourceLength: 200,
 40+ minSourceLength: 5,
 41+ maxTitleLength: 200,
 42+ minTitleLength: 5,
 43+ maxDescriptionLength: 4096,
 44+ minDescriptionLength: 5,
 45+ maxOtherInformationLength: 4096,
 46+ maxSimultaneousConnections: 2,
 47+ maxUploads: 10,
3148
32 - // XXX this is problematic, if the upload wizard is idle for a long time the token expires
33 - // should get token just before uploading
34 - token: wgEditToken,
35 -
36 - thumbnailWidth: 120,
37 - smallThumbnailWidth: 60,
38 - maxAuthorLength: 50,
39 - minAuthorLength: 2,
40 - maxSourceLength: 200,
41 - minSourceLength: 5,
42 - maxTitleLength: 200,
43 - minTitleLength: 5,
44 - maxDescriptionLength: 4096,
45 - minDescriptionLength: 5,
46 - maxOtherInformationLength: 4096,
47 - maxSimultaneousConnections: 2,
48 - maxUploads: 10,
 49+ // not for use with all wikis.
 50+ // The ISO 639 code for the language tagalog is "tl".
 51+ // Normally we name templates for languages by the ISO 639 code.
 52+ // Commons already had a template called 'tl: though.
 53+ // so, this workaround will cause tagalog descriptions to be saved with this template instead.
 54+ languageTemplateFixups: { tl: 'tgl' },
4955
50 - // not for use with all wikis.
51 - // The ISO 639 code for the language tagalog is "tl".
52 - // Normally we name templates for languages by the ISO 639 code.
53 - // Commons already had a template called 'tl: though.
54 - // so, this workaround will cause tagalog descriptions to be saved with this template instead.
55 - languageTemplateFixups: { tl: 'tgl' },
 56+ // names of all license templates, in order. Case sensitive!
 57+ // n.b. in the future, the licenses for a wiki will probably be defined in PHP or even LocalSettings.
 58+ licenses: [
 59+ { template: 'Cc-by-sa-3.0', messageKey: 'mwe-upwiz-license-cc-by-sa-3.0', 'default': true },
 60+ { template: 'Cc-by-3.0', messageKey: 'mwe-upwiz-license-cc-by-3.0', 'default': false },
 61+ { template: 'Cc-zero', messageKey: 'mwe-upwiz-license-cc-zero', 'default': false },
 62+ // n.b. the PD-US is only for testing purposes, obviously we need some geographical discrimination here...
 63+ { template: 'PD-US', messageKey: 'mwe-upwiz-license-pd-us', 'default': false },
 64+ { template: 'GFDL', messageKey: 'mwe-upwiz-license-gfdl', 'default': false }
 65+ ]
5666
57 - // names of all license templates, in order. Case sensitive!
58 - // n.b. in the future, the licenses for a wiki will probably be defined in PHP or even LocalSettings.
59 - licenses: [
60 - { template: 'Cc-by-sa-3.0', messageKey: 'mwe-upwiz-license-cc-by-sa-3.0', 'default': true },
61 - { template: 'Cc-by-3.0', messageKey: 'mwe-upwiz-license-cc-by-3.0', 'default': false },
62 - { template: 'Cc-zero', messageKey: 'mwe-upwiz-license-cc-zero', 'default': false },
63 - // n.b. the PD-US is only for testing purposes, obviously we need some geographical discrimination here...
64 - { template: 'PD-US', messageKey: 'mwe-upwiz-license-pd-us', 'default': false },
65 - { template: 'GFDL', messageKey: 'mwe-upwiz-license-gfdl', 'default': false }
66 - ],
 67+ // XXX this is horribly confusing -- some file restrictions are client side, others are server side
 68+ // the filename prefix blacklist is at least server side -- all this should be replaced with PHP regex config
 69+ // or actually, in an ideal world, we'd have some way to reliably detect gibberish, rather than trying to
 70+ // figure out what is bad via individual regexes, we'd detect badness. Might not be too hard.
 71+ //
 72+ // we can export these to JS if we so want.
 73+ // filenamePrefixBlacklist: wgFilenamePrefixBlacklist,
 74+ //
 75+ // filenameRegexBlacklist: [
 76+ // /^(test|image|img|bild|example?[\s_-]*)$/, // test stuff
 77+ // /^(\d{10}[\s_-][0-9a-f]{10}[\s_-][a-z])$/ // flickr
 78+ // ]
 79+ };
6780
68 - // XXX this is horribly confusing -- some file restrictions are client side, others are server side
69 - // the filename prefix blacklist is at least server side -- all this should be replaced with PHP regex config
70 - // or actually, in an ideal world, we'd have some way to reliably detect gibberish, rather than trying to
71 - // figure out what is bad via individual regexes, we'd detect badness. Might not be too hard.
72 - //
73 - // we can export these to JS if we so want.
74 - // filenamePrefixBlacklist: wgFilenamePrefixBlacklist,
75 - //
76 - // filenameRegexBlacklist: [
77 - // /^(test|image|img|bild|example?[\s_-]*)$/, // test stuff
78 - // /^(\d{10}[\s_-][0-9a-f]{10}[\s_-][a-z])$/ // flickr
79 - // ]
80 - });
 81+ var uploadWizard = new mw.UploadWizard( config );
 82+ uploadWizard.createInterface( '#upload-wizard' );
8183
82 - var uploadWizard = new mw.UploadWizard();
83 - uploadWizard.createInterface( '#upload-wizard' );
84 -
85 - } );
 84+}
 85+
 86+$j( document ).ready( function() {
 87+ mw.UploadWizardPage();
8688 } );
87 -
Index: trunk/extensions/UploadWizard/loader.js
@@ -60,36 +60,71 @@
6161
6262 var libraries = [
6363 [
64 - '$j.ui',
65 - '$j.ui.progressbar',
66 - '$j.ui.dialog',
67 - '$j.ui.draggable',
68 - '$j.ui.datepicker',
69 - '$j.effects',
70 - '$j.effects.slide',
71 - //'$j.effects.pulsate',
72 - '$j.fn.autocomplete',
73 - '$j.fn.tipsy',
74 - 'mw.style.tipsy',
75 - '$j.fn.morphCrossfade',
76 - '$j.fn.validate',
77 - '$j.fn.arrowSteps',
78 - '$j.fn.mwCoolCats',
79 - 'mw.style.arrowSteps',
80 - 'mw.style.autocomplete',
 64+ '$j.ui'
 65+ ],
 66+ [
 67+ '$j.ui.progressbar'
 68+ ],
 69+ [
 70+ '$j.ui.datepicker'
 71+ ],
 72+ [
 73+ '$j.effects'
 74+ ],
 75+ [
 76+ '$j.effects.slide'
 77+ ],
 78+ [
 79+ 'mw.style.autocomplete'
 80+ ],
 81+ [
 82+ '$j.fn.autocomplete'
 83+ ],
 84+ [
 85+ 'mw.style.tipsy'
 86+ ],
 87+ [
 88+ '$j.fn.tipsy'
 89+ ],
 90+ [
 91+ '$j.fn.morphCrossfade'
 92+ ],
 93+ [
 94+ '$j.fn.validate'
 95+ ],
 96+ [
 97+ 'mw.style.arrowSteps'
 98+ ],
 99+ [
 100+ '$j.fn.arrowSteps'
 101+ ],
 102+ [
81103 'mw.style.mwCoolCats'
82104 ],
83105 [
84 - 'mw.LanguageUpWiz',
85 - 'mw.IframeTransport',
86 - 'mw.ApiUploadHandler',
87 - 'mw.DestinationChecker',
88 - 'mw.UploadWizard',
 106+ '$j.fn.mwCoolCats'
 107+ ],
 108+ [
 109+ 'mw.LanguageUpWiz'
 110+ ],
 111+ [
 112+ 'mw.IframeTransport'
 113+ ],
 114+ [
 115+ 'mw.ApiUploadHandler'
 116+ ],
 117+ [
 118+ 'mw.DestinationChecker'
 119+ ],
 120+ [
89121 'mw.style.uploadWizard'
90122 ],
 123+ [
 124+ 'mw.UploadWizard'
 125+ ]
91126 ];
92127
93 - var testLibraries = libraries.slice( 0 )
 128+ var testLibraries = libraries.slice( 0 );
94129 testLibraries.push( [ 'mw.MockUploadHandler' ] );
95130
96131 /**
Index: trunk/extensions/UploadWizard/styles/inactive-arrow-divider.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Index: trunk/extensions/UploadWizard/styles/toggle-open.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Index: trunk/extensions/UploadWizard/styles/arrow-tail.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Index: trunk/extensions/UploadWizard/styles/toggle.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Index: trunk/extensions/UploadWizard/styles/arrow-head.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Index: trunk/extensions/UploadWizard/styles/calendar.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: trunk/extensions/UploadWizard/styles/jquery.tipsy.css
@@ -1,30 +0,0 @@
2 -.tipsy { padding: 5px; font-size: small; position: absolute; z-index: 100000; }
3 - .tipsy-inner { padding: 5px 8px 4px 8px; background-color: black; color: white; max-width: 200px; text-align: center; }
4 - .tipsy-inner { border-radius: 3px; -moz-border-radius:3px; -webkit-border-radius:3px; }
5 - .tipsy-arrow { position: absolute; background: url('jquery.tipsy.gif') no-repeat top left; width: 9px; height: 5px; }
6 - .tipsy-n .tipsy-arrow { top: 0; left: 50%; margin-left: -4px; }
7 - .tipsy-nw .tipsy-arrow { top: 0; left: 10px; }
8 - .tipsy-ne .tipsy-arrow { top: 0; right: 10px; }
9 - .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -4px; background-position: bottom left; }
10 - .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; background-position: bottom left; }
11 - .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; background-position: bottom left; }
12 - .tipsy-e .tipsy-arrow { top: 50%; margin-top: -4px; right: 0; width: 5px; height: 9px; background-position: top right; }
13 - .tipsy-w .tipsy-arrow { top: 50%; margin-top: -4px; left: 0; width: 5px; height: 9px; }
14 -
15 -
16 -.tipsy-help .tipsy-inner { background-color: #96d8d9; color: black; }
17 -.tipsy-help .tipsy-arrow { background: url('jquery.tipsy.help.gif') }
18 -
19 -.tipsy-error .tipsy-inner { background-color: #f89c90; color: black; }
20 -.tipsy-error .tipsy-arrow { background: url('jquery.tipsy.error.gif') }
21 -
22 -.shadow {
23 - /* offset left, top, thickness, color with alpha */
24 - -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
25 - -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
26 - box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
27 - /* IE */
28 - filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray');
29 - /* slightly different syntax for IE8 */
30 - -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray')";
31 -}
Index: trunk/extensions/UploadWizard/styles/jquery.arrowSteps.css
@@ -1,40 +0,0 @@
2 -.arrowSteps {
3 - list-style-type: none;
4 - list-style-image: none;
5 - border: 1px solid #666666;
6 - position: relative;
7 -}
8 -
9 -.arrowSteps li {
10 - float: left;
11 - padding: 0px;
12 - margin: 0px;
13 - border: 0 none;
14 -}
15 -
16 -.arrowSteps li div {
17 - padding: 0.5em;
18 - text-align: center;
19 - white-space: nowrap;
20 - overflow: hidden;
21 -}
22 -
23 -.arrowSteps li.arrow div {
24 - background: url(inactive-arrow-divider.png) no-repeat right center;
25 -}
26 -
27 -/* applied to the element preceding the highlighted step */
28 -.arrowSteps li.arrow.tail div {
29 - background: url(arrow-tail.png) no-repeat right center;
30 -}
31 -
32 -/* this applies to all highlighted, including the last */
33 -.arrowSteps li.head div {
34 - background: url(arrow-head.png) no-repeat left center;
35 - font-weight: bold;
36 -}
37 -
38 -/* this applies to all highlighted arrows except the last */
39 -.arrowSteps li.arrow.head div {
40 - background: url(arrow-head.png) no-repeat right center;
41 -}
Index: trunk/extensions/UploadWizard/styles/jquery.autoSuggest.css
@@ -1,218 +0,0 @@
2 -/* AutoSuggest CSS - Version 1.2 */
3 -
4 -ul.as-selections {
5 - list-style-type: none;
6 - list-style-image: none;
7 - border-top: 1px solid #888;
8 - border-bottom: 1px solid #b6b6b6;
9 - border-left: 1px solid #aaa;
10 - border-right: 1px solid #aaa;
11 - padding: 4px 0 4px 4px;
12 - margin: 0;
13 - overflow: auto;
14 - background-color: #fff;
15 - box-shadow:inset 0 1px 2px #888;
16 - -webkit-box-shadow:inset 0 1px 2px #888;
17 - -moz-box-shadow:inset 0 1px 2px #888;
18 -}
19 -
20 -ul.as-selections.loading {
21 - background-color: #eee;
22 -}
23 -
24 -ul.as-selections li {
25 - float: left;
26 - margin: 1px 4px 1px 0;
27 -}
28 -
29 -ul.as-selections li.as-selection-item {
30 - color: #2b3840;
31 - font-size: 13px;
32 - font-family: "Lucida Grande", arial, sans-serif;
33 - text-shadow: 0 1px 1px #fff;
34 - background-color: #ddeefe;
35 - background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ddeefe), to(#bfe0f1));
36 - border: 1px solid #acc3ec;
37 - border-top-color: #c0d9e9;
38 - padding: 2px 7px 2px 10px;
39 - border-radius: 12px;
40 - -webkit-border-radius: 12px;
41 - -moz-border-radius: 12px;
42 - box-shadow: 0 1px 1px #e4edf2;
43 - -webkit-box-shadow: 0 1px 1px #e4edf2;
44 - -moz-box-shadow: 0 1px 1px #e4edf2;
45 -}
46 -
47 -ul.as-selections li.as-selection-item:last-child {
48 - margin-left: 30px;
49 -}
50 -
51 -ul.as-selections li.as-selection-item a.as-close {
52 - float: right;
53 - margin: 1px 0 0 7px;
54 - padding: 0 2px;
55 - cursor: pointer;
56 - color: #5491be;
57 - font-family: "Helvetica", helvetica, arial, sans-serif;
58 - font-size: 14px;
59 - font-weight: bold;
60 - text-shadow: 0 1px 1px #fff;
61 - -webkit-transition: color .1s ease-in;
62 -}
63 -
64 -ul.as-selections li.as-selection-item.blur {
65 - color: #666666;
66 - background-color: #f4f4f4;
67 - background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f4f4f4), to(#d5d5d5));
68 - border-color: #bbb;
69 - border-top-color: #ccc;
70 - box-shadow: 0 1px 1px #e9e9e9;
71 - -webkit-box-shadow: 0 1px 1px #e9e9e9;
72 - -moz-box-shadow: 0 1px 1px #e9e9e9;
73 -}
74 -
75 -ul.as-selections li.as-selection-item.blur a.as-close {
76 - color: #999;
77 -}
78 -
79 -ul.as-selections li:hover.as-selection-item {
80 - color: #2b3840;
81 - background-color: #bbd4f1;
82 - background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#bbd4f1), to(#a3c2e5));
83 - border-color: #6da0e0;
84 - border-top-color: #8bb7ed;
85 -}
86 -
87 -ul.as-selections li:hover.as-selection-item a.as-close {
88 - color: #4d70b0;
89 -}
90 -
91 -ul.as-selections li.as-selection-item.selected {
92 - border-color: #1f30e4;
93 -}
94 -
95 -ul.as-selections li.as-selection-item a:hover.as-close {
96 - color: #1b3c65;
97 -}
98 -
99 -ul.as-selections li.as-selection-item a:active.as-close {
100 - color: #4d70b0;
101 -}
102 -
103 -ul.as-selections li.as-original {
104 - margin-left: 0;
105 -}
106 -
107 -ul.as-selections li.as-original input {
108 - border: none;
109 - outline: none;
110 - font-size: 13px;
111 - width: 120px;
112 - height: 18px;
113 - padding-top: 3px;
114 -}
115 -
116 -ul.as-list {
117 - position: absolute;
118 - list-style-type: none;
119 - margin: 2px 0 0 0;
120 - padding: 0;
121 - font-size: 14px;
122 - color: #000;
123 - font-family: "Lucida Grande", arial, sans-serif;
124 - background-color: #fff;
125 - background-color: rgba(255,255,255,0.95);
126 - z-index: 2;
127 - box-shadow: 0 2px 12px #222;
128 - -webkit-box-shadow: 0 2px 12px #222;
129 - -moz-box-shadow: 0 2px 12px #222;
130 - border-radius: 5px;
131 - -webkit-border-radius: 5px;
132 - -moz-border-radius: 5px;
133 -}
134 -
135 -li.as-result-item, li.as-message {
136 - margin: 0 0 0 0;
137 - padding: 5px 12px;
138 - background-color: transparent;
139 - border: 1px solid #fff;
140 - border-bottom: 1px solid #ddd;
141 - cursor: pointer;
142 - border-radius: 5px;
143 - -webkit-border-radius: 5px;
144 - -moz-border-radius: 5px;
145 -}
146 -
147 -li:first-child.as-result-item {
148 - margin: 0;
149 -}
150 -
151 -li.as-message {
152 - margin: 0;
153 - cursor: default;
154 -}
155 -
156 -li.as-result-item.active {
157 - background-color: #3668d9;
158 - background-image: -webkit-gradient(linear, 0% 0%, 0% 64%, from(rgb(110, 129, 245)), to(rgb(62, 82, 242)));
159 - border-color: #3342e8;
160 - color: #fff;
161 - text-shadow: 0 1px 2px #122042;
162 -}
163 -
164 -li.as-result-item em {
165 - font-style: normal;
166 - background: #444;
167 - padding: 0 2px;
168 - color: #fff;
169 -}
170 -
171 -li.as-result-item.active em {
172 - background: #253f7a;
173 - color: #fff;
174 -}
175 -
176 -/* Webkit Hacks */
177 -@media screen and (-webkit-min-device-pixel-ratio:0) {
178 - ul.as-selections {
179 - border-top-width: 2px;
180 - }
181 - ul.as-selections li.as-selection-item {
182 - padding-top: 3px;
183 - padding-bottom: 3px;
184 - }
185 - ul.as-selections li.as-selection-item a.as-close {
186 - margin-top: -1px;
187 - }
188 - ul.as-selections li.as-original input {
189 - height: 19px;
190 - }
191 -}
192 -
193 -/* Opera Hacks */
194 -@media all and (-webkit-min-device-pixel-ratio:10000), not all and (-webkit-min-device-pixel-ratio:0) {
195 - ul.as-list {
196 - border: 1px solid #888;
197 - }
198 - ul.as-selections li.as-selection-item a.as-close {
199 - margin-left: 4px;
200 - margin-top: 0;
201 - }
202 -}
203 -
204 -/* IE Hacks */
205 -ul.as-list {
206 - border: 1px solid #888\9;
207 -}
208 -ul.as-selections li.as-selection-item a.as-close {
209 - margin-left: 4px\9;
210 - margin-top: 0\9;
211 -}
212 -
213 -/* Firefox 3.0 Hacks */
214 -ul.as-list, x:-moz-any-link, x:default {
215 - border: 1px solid #888;
216 -}
217 -BODY:first-of-type ul.as-list, x:-moz-any-link, x:default { /* Target FF 3.5+ */
218 - border: none;
219 -}
Index: trunk/extensions/UploadWizard/styles/jquery.mwCoolCats.css
@@ -1,63 +0,0 @@
2 -.cat-widget {
3 - clear: both;
4 - position: relative;
5 -}
6 -.cat-widget ul {
7 - list-style: none;
8 - padding-left: 0;
9 - margin-top: 0;
10 - margin-left: 0;
11 -}
12 -.cat-widget li {
13 - float: left;
14 - margin-right: 10px;
15 - padding: 3px;
16 -}
17 -.cat-widget li a {
18 - text-decoration: none;
19 -}
20 -.cat-widget li:hover a {
21 - text-decoration: none;
22 - color: white;
23 -}
24 -.cat-widget li {
25 - background-color: #fff;
26 - border-radius: 4px;
27 - -moz-border-radius: 4px;
28 - -webkit-border-radius: 4px;
29 - border-top: 1px solid #fff;
30 - border: 1px solid #fff;
31 - color: black;
32 - font-size: 12px;
33 -
34 -}
35 -.cat-widget li:hover {
36 - background-color: #444;
37 - border-radius: 4px;
38 - -moz-border-radius: 4px;
39 - -webkit-border-radius: 4px;
40 - border-top: 1px solid #111;
41 - border: 1px solid #222;
42 - color:white;
43 -}
44 -
45 -.cat-widget li div.mwe-upwiz-remove-ctrl {
46 - display: inline-block;
47 -}
48 -
49 -
50 -/* Utilities */
51 -.pkg:after, #content-inner:after {
52 - content: " ";
53 - display: block;
54 - visibility: hidden;
55 - clear: both;
56 - height: 0.1px;
57 - font-size: 0.1em;
58 - line-height: 0;
59 -}
60 -.pkg, #content-inner { display: inline-block; }
61 -/* no ie mac \*/
62 -* html .pkg, * html #content-inner { height: 1%; }
63 -.pkg, #content-inner { display: block; }
64 -/* */
Index: trunk/extensions/UploadWizard/styles/jquery.tipsy.help.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: trunk/extensions/UploadWizard/styles/jquery.tipsy.error.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: trunk/extensions/UploadWizard/styles/jquery.tipsy.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: trunk/extensions/UploadWizard/styles/spinner-orange.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: trunk/extensions/UploadWizard/styles/checkmark.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: trunk/extensions/UploadWizard/styles/uploadWizard.css
@@ -1,558 +0,0 @@
2 -
3 -
4 -/* min-width is about 550px, maybe about 52em
5 -/* max-width is about 875px about 87em?
6 -#upload-wizard {
7 -
8 -}
9 -*/
10 -
11 -
12 -form.mwe-upwiz-form {
13 - display: inline;
14 -}
15 -
16 -#upload-wizard {
17 - margin-top: 18px;
18 - min-width: 32em;
19 - max-width: 64em;
20 -}
21 -
22 -/*
23 -.upload-section {
24 - padding: 1em;
25 - margin-bottom: 0.5em;
26 - margin-top: 0.5em;
27 - border: 1px solid #e0e0e0;
28 -}
29 -*/
30 -
31 -
32 -.mwe-upwiz-clearing {
33 - clear: left;
34 - width: 100%;
35 -}
36 -
37 -
38 -#mwe-upwiz-content {
39 - padding: 1em;
40 -}
41 -
42 -.mwe-upwiz-add-files-0, #mwe-upwiz-files {
43 - margin-top: 3em;
44 - margin-bottom: 3em;
45 -}
46 -
47 -.mwe-upwiz-add-files-0 {
48 - text-align: center;
49 - font-size: large;
50 -}
51 -
52 -#mwe-upwiz-add-file {
53 -}
54 -
55 -/* NOT a pseudoclass */
56 -#mwe-upwiz-add-file.hover {
57 - text-decoration: underline;
58 -}
59 -
60 -/* perhaps a general class for links that are actually "buttons" */
61 -#mwe-upwiz-add-file, .mwe-upwiz-remove-ctrl, .mwe-upwiz-more-options {
62 - outline: none;
63 - cursor: pointer;
64 -}
65 -
66 -/* CSS styling hack for file inputs - http://www.quirksmode.org/dom/inputfile.html */
67 -.mwe-upwiz-file-ctrl-container {
68 - position: absolute;
69 - overflow: hidden;
70 -}
71 -
72 -.mwe-upwiz-file-input, .mwe-upwiz-visible-file {
73 - cursor: pointer;
74 -}
75 -
76 -/* file inputs are freakishly large to overflow the containing div -- we get a div
77 - that can act as a more styleable single-click file input */
78 -.mwe-upwiz-file-input, .disabler {
79 - font-size: 100px;
80 - -moz-opacity: 0.3;
81 - filter:alpha(opacity: 0);
82 - opacity: 0;
83 - z-index: 2;
84 -}
85 -
86 -.mwe-upwiz-file.filled {
87 - position: relative;
88 -}
89 -
90 -.mwe-upwiz-file.odd .mwe-upwiz-visible-file {
91 - background: #f5f5f5;
92 -}
93 -
94 -
95 -
96 -.mwe-upwiz-remove-ctrl {
97 -}
98 -
99 -/* XXX this is highly specific to our installation
100 -.mwe-upwiz-remove-ctrl.hover {
101 -.ui-icon { background-image: url(/w/extensions/UsabilityInitiative/css/vector/images/ui-icons_cd0a0a_256x240.png) }
102 -}
103 -*/
104 -
105 -.mwe-upwiz-visible-file .mwe-upwiz-remove-ctrl {
106 - float: right;
107 - visibility: hidden;
108 - margin: 0.25em;
109 - padding: 0.25em;
110 -}
111 -
112 -.mwe-upwiz-file-indicator, .mwe-upwiz-count {
113 - float: right;
114 - margin-left: 0.5em;
115 - padding: 0.5em 0.5em 0.5em 20px; /* 20px for the icon */
116 -}
117 -
118 -.mwe-upwiz-visible-file .mwe-upwiz-file-indicator {
119 - visibility: hidden;
120 -}
121 -
122 -.mwe-upwiz-visible-file {
123 - display: none;
124 -}
125 -
126 -.mwe-upwiz-file.filled .mwe-upwiz-visible-file {
127 - display: block;
128 -}
129 -
130 -.mwe-upwiz-visible-file-filename {
131 - padding: 0.5em;
132 - margin-right: 40px;
133 - overflow: hidden;
134 -}
135 -
136 -.mwe-upwiz-progress-bar-etr-container {
137 - /* needed ? */
138 -}
139 -
140 -
141 -.mwe-upwiz-add-files-n {
142 - float: left;
143 - margin-top: 5px;
144 - margin-left: 4px;
145 -}
146 -
147 -#mwe-upwiz-add-file-container.mwe-upwiz-add-files-n, .mwe-upwiz-progress-bar-etr {
148 - width: 300px;
149 - padding-left: 5px;
150 -}
151 -
152 -
153 -#mwe-upwiz-add-file-container.mwe-upwiz-add-files-n {
154 - float: left;
155 -}
156 -
157 -.mwe-upwiz-visible-file {
158 - width: 100%; /* of containing mwe-upwiz-file */
159 - white-space: nowrap;
160 - overflow: hidden;
161 -}
162 -
163 -.mwe-upwiz-file.hover .mwe-upwiz-visible-file {
164 - background: #e0f0ff !important;
165 -}
166 -
167 -.mwe-upwiz-file.hover .mwe-upwiz-remove-ctrl {
168 - visibility: visible;
169 -}
170 -
171 -/* XXX we probably have a standard for this */
172 -.helper {
173 - color: #cccccc; /* or whatever we do for greyed out text */
174 - font-variant: italic, oblique;
175 - text-align: center;
176 -}
177 -
178 -
179 -#mwe-upwiz-files {
180 - margin-right: 8em;
181 - margin-left: 8em;
182 -}
183 -
184 -.mwe-upwiz-file {
185 -}
186 -
187 -#mwe-upwiz-upload-ctrls {
188 - margin-top: 1em;
189 -}
190 -
191 -#mwe-upwiz-add-file-container {
192 - /* empty; this changes a lot */
193 -}
194 -
195 -.mwe-upwiz-details-descriptions .mwe-upwiz-remove-ctrl {
196 - vertical-align: top;
197 - display: inline-block;
198 -}
199 -
200 -a[disabled=true] {
201 - color: #999999;
202 - text-decoration: none;
203 - cursor: default;
204 -}
205 -
206 -a[disabled=true]:hover {
207 - text-decoration: none;
208 -}
209 -
210 -.mwe-upwiz-status-progress {
211 - background: url(spinner-orange.gif) no-repeat left center;
212 - font-weight: bold;
213 - color: #ff9900;
214 -}
215 -
216 -.mwe-upwiz-status-completed {
217 - background: url(checkmark.gif) no-repeat left center;
218 - font-weight: bold;
219 - color: #009900;
220 -}
221 -
222 -.mwe-upwiz-progress {
223 - margin-top: 15px;
224 -}
225 -
226 -.mwe-upwiz-progress-bar-etr {
227 - float: left;
228 -}
229 -
230 -.mwe-upwiz-etr {
231 - text-align: center;
232 -}
233 -
234 -
235 -.mwe-upwiz-upload-warning {
236 - background: #ffffe0;
237 -}
238 -
239 -.mwe-upwiz-details-error {
240 - display: none;
241 - background: #ffffe0;
242 -}
243 -
244 -.mwe-upwiz-thumbnail, .mwe-upwiz-thumbnail-small {
245 - border: 1px solid #cccccc;
246 - text-align: center;
247 - background: #ffffff;
248 -}
249 -
250 -.mwe-upwiz-thumbnail {
251 - padding: 0.5em;
252 - width: 120px;
253 -}
254 -
255 -.mwe-upwiz-thumbnail-side {
256 - float: left;
257 - margin-bottom: 1em;
258 - margin-right: 1em;
259 -}
260 -
261 -.mwe-upwiz-thumbnail-small {
262 - padding: 0.25em;
263 - width: 60px;
264 -}
265 -
266 -#mwe-upwiz-deeds-thumbnails {
267 - text-align: center;
268 - margin: 1em 0;
269 - background: #f0f0f0;
270 -}
271 -
272 -#mwe-upwiz-deeds-thumbnails .mwe-upwiz-thumbnail-small {
273 - display: inline-block;
274 - margin: 1em;
275 - vertical-align: middle;
276 -}
277 -
278 -/* I don't like that this has to have width, to ensure that all the floats work out correctly.*/
279 -.mwe-upwiz-data {
280 - float: left;
281 - width: 46em;
282 -}
283 -
284 -
285 -/* XXX add spinner gif instead, maybe */
286 -.busy {
287 - /* background: yellow; */
288 -}
289 -
290 -.mwe-upwiz-stepdiv {
291 - height: 0px;
292 - overflow: hidden;
293 -}
294 -
295 -
296 -
297 -.shim {
298 - float:right;
299 - width: 1px;
300 -}
301 -
302 -.clearShim {
303 - clear: both;
304 - height: 1px;
305 - overflow: hidden;
306 -}
307 -
308 -.mwe-checkbox-hang-indent {
309 - float: left;
310 - width: 24px;
311 - margin-top: 6px;
312 -}
313 -
314 -.mwe-checkbox-hang-indent-text {
315 - margin-left: 24px;
316 -}
317 -
318 -.mwe-small-print {
319 - font-size: x-small;
320 -}
321 -
322 -.mwe-upwiz-deed {
323 - margin-left: 24px;
324 -}
325 -
326 -.mwe-upwiz-deed.selected .mwe-upwiz-deed-name {
327 - font-weight: bold;
328 -}
329 -
330 -.mwe-more-options, .mwe-upwiz-macro-deeds-return, .mwe-upwiz-deed-header-link {
331 - cursor: pointer;
332 -}
333 -
334 -.mwe-more-options {
335 - padding-bottom: 4px;
336 -}
337 -
338 -.mwe-upwiz-deed-license {
339 - margin-left: 24px;
340 -}
341 -
342 -#mwe-upwiz-macro-deeds {
343 - margin-top: 12px;
344 - margin-bottom: 24px;
345 -}
346 -
347 -#mwe-upwiz-macro-files {
348 - margin-top: 12px;
349 -}
350 -
351 -.mwe-upwiz-info-file {
352 - margin-bottom: 1em;
353 -}
354 -
355 -.mwe-upwiz-details-input {
356 - width: 33em;
357 - float: left;
358 -}
359 -
360 -.mwe-upwiz-details-fieldname {
361 - width: 10em;
362 - padding-top: 0.5em;
363 - float: left;
364 -}
365 -
366 -.mwe-upwiz-details-input-error {
367 - padding-left: 10em;
368 - /* XXX the following rules pop it open at a fixed width so layout doesn't change :( */
369 - /* min-height: 3em;
370 - position: relative; */
371 -}
372 -
373 -/* XXX the following rule keeps errors on the details page at the bottom of a fixed-height space, see above */
374 -/*
375 -.mwe-upwiz-details-input-error label.mwe-validator-error,
376 -.mwe-upwiz-details-input-error label.mwe-error {
377 - position: absolute;
378 - bottom: 0px;
379 -}
380 -*/
381 -
382 -
383 -.mwe-upwiz-desc-lang-select {
384 - width: 11em;
385 - font-family: sans-serif;
386 - font-size: small;
387 -}
388 -
389 -.mwe-upwiz-desc-lang-text {
390 - width: 20em;
391 - overflow: hidden;
392 - font-family: sans-serif; /* XXX is this right? */
393 - font-size: small;
394 -}
395 -
396 -.mwe-upwiz-details-descriptions-add {
397 - margin-left: 10em; /* width of mwe-upwiz-details-fieldname */
398 -}
399 -
400 -.mwe-upwiz-details-descriptions-add {
401 - margin-bottom: 1em;
402 -}
403 -
404 -.mwe-grow-textarea, .mwe-long-textarea {
405 - overflow: hidden;
406 - font-family: sans-serif; /* XXX is this right? */
407 - font-size: small;
408 -}
409 -
410 -.mwe-long-textarea {
411 - width: 31em;
412 -}
413 -
414 -fieldset .mwe-long-textarea {
415 - width: 17em;
416 -}
417 -
418 -
419 -.mwe-upwiz-details-fieldname-input {
420 - margin-bottom: 1em;
421 -}
422 -
423 -.mwe-upwiz-details-filename. {
424 - overflow: hidden;
425 - width: 350px;
426 -}
427 -
428 -.mwe-upwiz-other-textarea {
429 - /* width: 40.3em; */
430 -}
431 -
432 -fieldset.mwe-fieldset {
433 - width: 450px;
434 - border: 1px solid #cccccc;
435 - padding-left: 12px;
436 - padding-right: 12px;
437 -}
438 -
439 -legend.mwe-legend {
440 - padding: 0.5em 0.5em 0.5em 0.33em;
441 - color: #666666;
442 -}
443 -
444 -.masked-hidden {
445 - visibility: hidden !important;
446 -}
447 -
448 -.mwe-upwiz-thirdparty-fields {
449 - margin-bottom: .5em;
450 -}
451 -
452 -.mwe-upwiz-thirdparty-fields label {
453 - width: 9em;
454 - display: inline-block;
455 - padding-bottom: .5em;
456 -}
457 -
458 -.mwe-upwiz-thirdparty-fields textarea {
459 - margin: 0
460 -}
461 -
462 -.mwe-upwiz-thirdparty-license {
463 - margin-top: 8px;
464 -}
465 -
466 -.mwe-upwiz-deed-form-internal {
467 - padding: 0.5em 0 1.5em 3em;
468 -}
469 -
470 -
471 -.mwe-upwiz-copyright-info label {
472 - display: inline-block;
473 - padding-left: 15px;
474 - text-indent: -15px;
475 - margin-right: 15px;
476 -}
477 -
478 -/* the hanging indent checkboxes */
479 -.mwe-upwiz-copyright-info input.mwe-accept-deed {
480 - width: 13px;
481 - height: 13px;
482 - padding: 0;
483 - margin: 0px 3px 0px 0px;
484 - vertical-align: bottom;
485 - position: relative;
486 - top: -1px;
487 -}
488 -
489 -
490 -.mwe-upwiz-custom-deed {
491 - margin-top: 5px;
492 -}
493 -
494 -.mwe-upwiz-buttons {
495 - margin-top: 1em;
496 - padding-top: 10px;
497 - border-top: 1px solid #e0e0e0;
498 - text-align: right; /* works for now, only one 'next' button */
499 -}
500 -
501 -a.mwe-upwiz-tooltip-link {
502 - cursor: pointer;
503 -}
504 -
505 -.mwe-error, .mwe-validator-error {
506 - color: #ff0000;
507 -}
508 -
509 -.mwe-upwiz-deed-thirdparty .mwe-upwiz-deed-form-internal label.mwe-error,
510 -.mwe-upwiz-deed-thirdparty .mwe-upwiz-deed-form-internal label.mwe-validator-error {
511 - margin-left: 9em;
512 -}
513 -
514 -/* XXX fix this later -- we get non-rounded corners somehow */
515 -input[type='text'].mwe-error, textarea.mwe-error,
516 -input[type='text'].mwe-validator-error, textarea.mwe-validator-error {
517 - color: black;
518 - border-color: #ff0000;
519 -}
520 -
521 -.mwe-upwiz-toggler {
522 - margin-bottom: 0;
523 - padding: 4px 0 3px 18px;
524 - background: url('toggle.png') no-repeat left center;
525 -}
526 -
527 -.mwe-upwiz-toggler-open {
528 - background: url('toggle-open.png') no-repeat left center;
529 -}
530 -
531 -.mwe-upwiz-toggled {
532 - margin-top: 1em;
533 -}
534 -
535 -.mwe-upwiz-thanks {
536 - margin-bottom: 2em;
537 -}
538 -
539 -/* may change in RTL */
540 -.mwe-upwiz-required-field {
541 - /* font-weight: bold; */
542 -}
543 -
544 -.mwe-upwiz-required-marker {
545 - color: #0099cc;
546 -}
547 -
548 -.mwe-readonly {
549 - background-color: #ffffff;
550 -}
551 -
552 -.mwe-date-display {
553 - width: 100%;
554 - background: #ffffff url('calendar.gif') no-repeat right center;
555 -}
556 -
557 -.ui-datepicker-current-day a.ui-state-active {
558 - background: #ffff99;
559 -}

Comments

#Comment by Nikerabbit (talk | contribs)   07:55, 12 August 2010
9	* Translate wiki script
10	Copies the translate wiki localised msgs into the file.

We prefer to be called translatewiki.net

+               // XXX what does this really do??
+               $wgMessageCache->loadAllMessages();

Nothing anymore and can be safely removed.

+ * Inspied by the Autocomplete plugin by: J\x9Arn Zaefferer

Not UTF-8 (not introduced in this commit)

#Comment by NeilK (talk | contribs)   20:33, 12 August 2010

Thanks for the comments... will fix.

#Comment by NeilK (talk | contribs)   21:08, 12 August 2010

first two comments fixed in r70987, r70989. Will fix the signature issue a bit later.

Status & tagging log