r66230 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r66229‎ | r66230 | r66231 >
Date:22:05, 11 May 2010
Author:dale
Status:deferred
Tags:
Comment:
move UploadWizard javascript module into UploadWizard extension
Modified paths:
  • /trunk/extensions/UploadWizard/UploadWizardJsModule (added) (history)

Diff [purge]

Index: trunk/extensions/UploadWizard/UploadWizardJsModule/mw.LanguageUpWiz.js
@@ -0,0 +1,492 @@
 2+mw.addMessages({
 3+ "mwe-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: "ckb-latn", text: "\u202aSoran\u00ee (lat\u00een\u00ee)\u202c" },
 354+ { 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" },
 355+ { code: "kk-cyrl", text: "\u202a\u049a\u0430\u0437\u0430\u049b\u0448\u0430 (\u043a\u0438\u0440\u0438\u043b)\u202c" },
 356+ { code: "kk-kz", text: "\u202a\u049a\u0430\u0437\u0430\u049b\u0448\u0430 (\u049a\u0430\u0437\u0430\u049b\u0441\u0442\u0430\u043d)\u202c" },
 357+ { code: "kk-arab", text: "\u202b\u0642\u0627\u0632\u0627\u0642\u0634\u0627 (\u062a\u0674\u0648\u062a\u06d5)\u202c" },
 358+ { code: "kk-cn", text: "\u202b\u0642\u0627\u0632\u0627\u0642\u0634\u0627 (\u062c\u06c7\u0646\u06af\u0648)\u202c" },
 359+ { code: "ku-arab", text: "\u202b\u0643\u0648\u0631\u062f\u064a (\u0639\u06d5\u0631\u06d5\u0628\u06cc)\u202c" },
 360+ { code: "ckb-arab", text: "\u202b\u06a9\u0648\u0631\u062f\u06cc (\u0639\u06d5\u0631\u06d5\u0628\u06cc)\u202c" },
 361+ { code: "zh", text: "\u4e2d\u6587" },
 362+ { code: "zh-cn", text: "\u4e2d\u6587(\u4e2d\u56fd\u5927\u9646)" },
 363+ { code: "zh-tw", text: "\u4e2d\u6587(\u53f0\u7063)" },
 364+ { code: "zh-sg", text: "\u4e2d\u6587(\u65b0\u52a0\u5761)" },
 365+ { code: "zh-mo", text: "\u4e2d\u6587(\u6fb3\u9580)" },
 366+ { code: "zh-hans", text: "\u4e2d\u6587(\u7b80\u4f53)" },
 367+ { code: "zh-hant", text: "\u4e2d\u6587(\u7e41\u9ad4)" },
 368+ { code: "zh-hk", text: "\u4e2d\u6587(\u9999\u6e2f)" },
 369+ { code: "zh-my", text: "\u4e2d\u6587(\u9a6c\u6765\u897f\u4e9a)" },
 370+ { code: "wuu", text: "\u5434\u8bed" },
 371+ { code: "lzh", text: "\u6587\u8a00" },
 372+ { code: "zh-classical", text: "\u6587\u8a00" },
 373+ { code: "ja", text: "\u65e5\u672c\u8a9e" },
 374+ { code: "yue", text: "\u7cb5\u8a9e" },
 375+ { code: "zh-yue", text: "\u7cb5\u8a9e" },
 376+ { code: "gan", text: "\u8d1b\u8a9e" },
 377+ { code: "gan-hant", text: "\u8d1b\u8a9e(\u7e41\u9ad4)" },
 378+ { code: "gan-hans", text: "\u8d63\u8bed(\u7b80\u4f53)" },
 379+ { code: "ii", text: "\ua187\ua259" },
 380+ { code: "ko", text: "\ud55c\uad6d\uc5b4" },
 381+ { code: "ko-kp", text: "\ud55c\uad6d\uc5b4 (\uc870\uc120)" },
 382+ { code: "got", text: "\ud800\udf32\ud800\udf3f\ud800\udf44\ud800\udf39\ud800\udf43\ud800\udf3a" },
 383+ ],
 384+
 385+ /**
 386+ * cache some useful objects
 387+ * 1) mostly ready-to-go language HTML menu. When/if we upgrade, make it a jQuery combobox
 388+ * 2) dict of language code to name -- useful for testing for existence, maybe other things.
 389+ */
 390+ initialize: function() {
 391+ if ( mw.LanguageUpWiz.initialized ) {
 392+ return;
 393+ }
 394+ mw.LanguageUpWiz._codes = {};
 395+ var select = $j( '<select/>' );
 396+ $j.each( mw.LanguageUpWiz.languages, function( i, language ) {
 397+ select.append(
 398+ $j( '<option>' )
 399+ .attr( 'value', language.code )
 400+ .append( language.text )
 401+ );
 402+ mw.LanguageUpWiz._codes[language.code] = language.text;
 403+ } );
 404+ mw.LanguageUpWiz.$_select = select;
 405+ mw.LanguageUpWiz.initialized = true;
 406+ },
 407+
 408+ /**
 409+ * Get an HTML select menu of all our languages.
 410+ * @param name desired name of select element
 411+ * @param code desired default language code
 412+ * @return HTML select element configured as desired
 413+ */
 414+ getMenu: function( name, code ) {
 415+ mw.LanguageUpWiz.initialize();
 416+ var $select = mw.LanguageUpWiz.$_select.clone();
 417+ $select.attr( 'name', name );
 418+ if ( code === mw.LanguageUpWiz.UNKNOWN ) {
 419+ // n.b. MediaWiki LanguageHandler has ability to add custom label for 'Unknown'; possibly as pseudo-label
 420+ $select.prepend( $j( '<option>' ).attr( 'value', mw.LanguageUpWiz.UNKNOWN ).append( gM( 'mwe-code-unknown' )) );
 421+ $select.val( mw.LanguageUpWiz.UNKNOWN );
 422+ } else if ( code !== undefined ) {
 423+ $select.val( mw.LanguageUpWiz.getClosest( code ));
 424+ }
 425+ return $select.get( 0 );
 426+ },
 427+
 428+ /**
 429+ * Figure out the closest language we have to a supplied language code.
 430+ * It seems that people on Mediawiki set their language code as freetext, and it could be anything, even
 431+ * variants we don't have a record for, or ones that are not in any ISO standard.
 432+ *
 433+ * Logic copied from MediaWiki:LanguageHandler.js
 434+ * handle null cases, special cases for some Chinese variants
 435+ * Otherwise, if handed "foo-bar-baz" language, try to match most specific language,
 436+ * "foo-bar-baz", then "foo-bar", then "foo"
 437+ *
 438+ * @param code A string representing a language code, which we may or may not have.
 439+ * Expected to be separated with dashes as codes from ISO 639, e.g. "zh-tw" for Chinese ( Traditional )
 440+ * @return a language code which is close to the supplied parameter, or fall back to mw.LanguageUpWiz.defaultCode
 441+ */
 442+ getClosest: function( code ) {
 443+ mw.LanguageUpWiz.initialize();
 444+ if ( typeof ( code ) != 'string' || code === null || code.length === 0 ) {
 445+ return mw.LanguageUpWiz.defaultCode;
 446+ }
 447+ if ( code == 'nan' || code == 'minnan' ) {
 448+ return 'zh-min-nan';
 449+ } else if ( mw.LanguageUpWiz._codes[code] !== undefined ) {
 450+ return code;
 451+ }
 452+ return mw.LanguageUpWiz.getClosest( code.substring( 0, code.indexOf( '-' )) );
 453+ },
 454+
 455+
 456+ // enhance a simple text input to be an autocompleting language menu
 457+ // this will work when/if we move to jQuery 1.4. As of now the autocomplete is too underpowered for our needs without
 458+ // serious hackery
 459+ /*
 460+ $j.fn.languageMenu = function( options ) {
 461+ var _this = this;
 462+ _this.autocomplete( null, {
 463+ minChars: 0,
 464+ width: 310,
 465+ selectFirst: true,
 466+ autoFill: true,
 467+ mustMatch: true,
 468+ matchContains: false,
 469+ highlightItem: true,
 470+ scroll: true,
 471+ scrollHeight: 220,
 472+ formatItem: function( row, i, max, term ) {
 473+ return row.code + " " + row.code;
 474+ },
 475+ formatMatch: function( row, i, max, term ) {
 476+ return row.code + " " + row.code;
 477+ },
 478+ formatResult: function( row ) {
 479+ return row.code;
 480+ }
 481+ }, mw.Languages );
 482+
 483+ // and add a dropdown so we can see the thingy, too
 484+ return _this;
 485+ };
 486+ */
 487+
 488+ // XXX the concept of "internal language" exists in UploadForm.js -- seems to be how they handled i18n, with
 489+ // language codes that has underscores rather than dashes, ( "en_gb" rather than the correct "en-gb" ).
 490+ // although other info such as Information boxes was recorded correctly.
 491+ // This is presumed not to apply to the shiny new world of JS2, where i18n is handled in other ways.
 492+
 493+}
Property changes on: trunk/extensions/UploadWizard/UploadWizardJsModule/mw.LanguageUpWiz.js
___________________________________________________________________
Name: svn:mergeinfo
1494 + /branches/REL1_15/phase3/js2/mwEmbed/modules/UploadWizard/mw.Language.js:51646
/branches/sqlite/js2/mwEmbed/modules/UploadWizard/mw.Language.js:58211-58321
Name: svn:eol-style
2495 + native
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/loader.js
@@ -0,0 +1,105 @@
 2+/*
 3+* Loader for UploadWizard module:
 4+*/
 5+
 6+// Scope everythign in "mw" ( keeps the global namespace clean )
 7+( function( mw ) {
 8+
 9+ mw.addMessages( {
 10+ "mwe-loading-upwiz" : "Loading upload wizard"
 11+ });
 12+
 13+ // Add class file paths ( From ROOT )
 14+ mw.addClassFilePaths( {
 15+ "mw.LanguageUpWiz" : "mw.LanguageUpWiz.js",
 16+ "mw.UploadWizard" : "mw.UploadWizard.js",
 17+ "mw.style.uploadWizard" : "css/uploadWizard.css",
 18+
 19+ "mw.UploadApiProcessor" : "mw.UploadApiProcessor.js",
 20+ "mw.IframeTransport" : "mw.IframeTransport.js",
 21+ "mw.ApiUploadHandler" : "mw.ApiUploadHandler.js",
 22+ "mw.DestinationChecker" : "mw.DestinationChecker.js",
 23+
 24+ "mw.MockUploadHandler" : "mw.MockUploadHandler.js"
 25+
 26+ });
 27+
 28+ //Set a variable for the base upload interface for easy inclution
 29+ //
 30+ // var baseUploadlibraries = [
 31+ // [
 32+ // 'mw.UploadHandler',
 33+ // 'mw.UploadInterface',
 34+ // '$j.ui'
 35+ // ],
 36+ // [
 37+ // '$j.ui.progressbar',
 38+ // '$j.ui.dialog',
 39+ // '$j.ui.draggable',
 40+ // '$j.fn.autocomplete'
 41+ // ]
 42+ // ];
 43+ //
 44+ // var mwBaseFirefoggReq = baseUploadlibraries.slice( 0 )
 45+ // mwBaseFirefoggReq[0].push('mw.Firefogg');
 46+ //
 47+
 48+ var libraries = [
 49+ [
 50+ '$j.ui',
 51+ '$j.ui.progressbar',
 52+ '$j.ui.dialog',
 53+ '$j.ui.draggable',
 54+ '$j.ui.datepicker',
 55+ '$j.effects.core',
 56+ '$j.effects.slide',
 57+ //'$j.effects.pulsate',
 58+ '$j.fn.autocomplete',
 59+ '$j.fn.tipsy',
 60+ 'mw.style.tipsy',
 61+ 'mw.style.autocomplete'
 62+ ],
 63+ [
 64+ 'mw.LanguageUpWiz',
 65+ 'mw.IframeTransport',
 66+ 'mw.ApiUploadHandler',
 67+ 'mw.DestinationChecker',
 68+ 'mw.UploadWizard',
 69+ 'mw.style.uploadWizard'
 70+ ],
 71+ ];
 72+
 73+ var testLibraries = libraries.slice( 0 )
 74+ testLibraries.push( [ 'mw.MockUploadHandler' ] );
 75+
 76+ /**
 77+ * Note: We should move relevant parts of these style sheets to the addMedia/css folder
 78+ * phase 2: We should separate out sheet sets per sub-module:
 79+ */
 80+
 81+ mw.addModuleLoader( 'UploadWizard.UploadWizard', function( callback ) {
 82+ //Clone the array:
 83+ //var request = mwBaseFirefoggReq.slice( 0 ) ;
 84+
 85+ //Add uploadwizard classes to a new "request" var:
 86+ //request.push( libraries );
 87+ mw.load( libraries, function() {
 88+ callback( 'UploadWizard.UploadWizard' );
 89+ } );
 90+
 91+ } );
 92+
 93+ mw.addModuleLoader( 'UploadWizard.UploadWizardTest', function( callback ) {
 94+ //Clone the array:
 95+ //var request = mwBaseFirefoggReq.slice( 0 ) ;
 96+
 97+ //Add uploadwizard classes to a new "request" var:
 98+ //request.push( testLibraries );
 99+ //debugger;
 100+ mw.load( testLibraries, function() {
 101+ callback( 'UploadWizard.UploadWizardTest' );
 102+ } );
 103+
 104+ } );
 105+
 106+} )( window.mw );
Property changes on: trunk/extensions/UploadWizard/UploadWizardJsModule/loader.js
___________________________________________________________________
Name: svn:eol-style
1107 + native
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/mw.UploadWizard.js
@@ -0,0 +1,3170 @@
 2+mw.addMessages( {
 3+ "mwe-upwiz-step-file": "1. Upload your files",
 4+ "mwe-upwiz-step-deeds": "2. Add licenses",
 5+ "mwe-upwiz-step-details": "3. Add descriptions",
 6+ "mwe-upwiz-step-thanks": "4. Use your files",
 7+ "mwe-upwiz-intro": "Welcome to Wikimedia Commons, a repository of images, sounds, and movies that anyone can freely download and use. Add to humanity's knowledge by uploading files that could be used for an educational purpose.",
 8+
 9+ "mwe-upwiz-add-file-n": "Add another file",
 10+ "mwe-upwiz-add-file-0": "Click here to add a file for upload",
 11+ "mwe-upwiz-browse": "Browse...",
 12+ "mwe-upwiz-transported": "OK",
 13+ "mwe-upwiz-click-here": "Click here to select a file",
 14+ "mwe-upwiz-uploading": "uploading...",
 15+ "mwe-upwiz-editing": "editing...",
 16+ "mwe-upwiz-remove-upload": "Remove this file from the list of files to upload",
 17+ "mwe-upwiz-remove-description": "Remove this description",
 18+ "mwe-upwiz-upload": "Upload",
 19+ "mwe-upwiz-upload-count": "$1 of $2 files uploaded",
 20+ "mwe-upwiz-progressbar-uploading": "uploading",
 21+ "mwe-upwiz-remaining": "$1 remaining",
 22+ "mwe-upwiz-deeds-intro": "Thank you! Now we need to set a license for these files, so everyone can legally view or modify them. First, we'll have to know where you got them.",
 23+ "mwe-upwiz-details-intro": "Now we need some basic information about the files.",
 24+ "mwe-upwiz-source-ownwork": "This file is my own work.",
 25+ "mwe-upwiz-source-ownwork-plural": "These files are my own work.",
 26+ "mwe-upwiz-source-ownwork-assert": "I, $1, the copyright holder of this work, hereby grant anyone the right to use this work for any purpose, as long as they credit me and share derivative work under the same terms.",
 27+ "mwe-upwiz-source-ownwork-assert-plural": "I, $1, the copyright holder of these works, hereby grant anyone the right to use these works for any purpose, as long as they credit me and share derivative work under the same terms.",
 28+ "mwe-upwiz-source-ownwork-assert-custom": "I, $1, the copyright holder of this work, hereby publish this work under the following license(s):",
 29+ "mwe-upwiz-source-ownwork-assert-custom-plural": "I, $1, the copyright holder of these works, hereby publish these works under the following license(s):",
 30+ "mwe-upwiz-source-ownwork-assert-note": "This means you release your work under a double Creative Commons Attribution ShareAlike and GFDL license.",
 31+ "mwe-upwiz-source-permission": "Their author gave you explicit permission to upload them",
 32+ "mwe-upwiz-source-thirdparty": "This file is not my own work.",
 33+ "mwe-upwiz-source-thirdparty-plural": "These files are not my own work.",
 34+ "mwe-upwiz-source-thirdparty-intro" : "Please enter the address where you found each file.",
 35+ "mwe-upwiz-source-thirdparty-custom-plural-intro" : "If all files have the same source, author, and copyright status, you may enter them only once for all of them.",
 36+ "mwe-upwiz-source-thirdparty-license" : "The copyright holder of this work published them under the following license(s):",
 37+ "mwe-upwiz-source-thirdparty-license-plural" : "The copyright holder of these works published them under the following license(s):",
 38+ "mwe-upwiz-source-thirdparty-accept": "OK",
 39+ "mwe-upwiz-source-custom": "Did you know? You can <a href=\"$1\">customize</a> the default options you see here.",
 40+ "mwe-upwiz-more-options": "more options...",
 41+ "mwe-upwiz-fewer-options": "fewer options...",
 42+ "mwe-upwiz-desc": "Description in",
 43+ "mwe-upwiz-desc-add-n": "add a description in another language",
 44+ "mwe-upwiz-desc-add-0": "add a description",
 45+ "mwe-upwiz-title": "Title",
 46+ "mwe-upwiz-categories-intro": "Help people find your works by adding categories",
 47+ "mwe-upwiz-categories-another": "Add other categories",
 48+ "mwe-upwiz-previously-uploaded": "This file was previously uploaded to $1 and is already available <a href=\"$2\">here</a>.",
 49+ "mwe-upwiz-about-this-work": "About this work",
 50+ "mwe-upwiz-media-type": "Media type",
 51+ "mwe-upwiz-date-created": "Date created",
 52+ "mwe-upwiz-location": "Location",
 53+ "mwe-upwiz-copyright-info": "Copyright information",
 54+ "mwe-upwiz-author": "Author(s)",
 55+ "mwe-upwiz-license": "License",
 56+ "mwe-upwiz-about-format": "About the file",
 57+ "mwe-upwiz-autoconverted": "This file was automatically converted to the $1 format",
 58+ "mwe-upwiz-filename-tag": "File name:",
 59+ "mwe-upwiz-other": "Other information",
 60+ "mwe-upwiz-other-prefill": "Free wikitext field",
 61+ "mwe-upwiz-showall": "show all",
 62+ "mwe-upwiz-source": "Source",
 63+ "mwe-upwiz-macro-edit-intro": "Choose a license first above, then you can add some descriptions and other information to your uploads.",
 64+ "mwe-upwiz-macro-edit": "Update descriptions",
 65+ "mwe-upwiz-thanks-intro": "Thanks for uploading your works! You can now use your files on a Wikipedia article or link to them from elsewhere on the web.",
 66+ "mwe-upwiz-thanks-link": "This file is now available at <b><tt>$1</tt></b>.",
 67+ "mwe-upwiz-thanks-wikitext": "<b>To use it in a Wikipedia article</b>, copy this text into an article: ",
 68+ "mwe-upwiz-thanks-url": "<b>To link to it in HTML</b>, copy this HTML code: ",
 69+
 70+ "mwe-upwiz-upload-error-bad-filename-extension": "This wiki does not accept filenames with the extension \"$1\".",
 71+ "mwe-upwiz-upload-error-duplicate": "This file was previously uploaded to this wiki.",
 72+ "mwe-upwiz-upload-error-stashed-anyway": "Post anyway?",
 73+ "mwe-upwiz-ok": "OK",
 74+ "mwe-upwiz-cancel": "Cancel",
 75+ "mwe-upwiz-change": "(change)",
 76+
 77+ "mwe-fileexists" : "A file with this name exists already. Please check <b><tt>$1<\/tt><\/b> if you are not sure if you want to replace it.",
 78+ "mwe-thumbnail-more" : "Enlarge",
 79+ "mwe-upwiz-overwrite" : "Replace the file",
 80+
 81+ "mwe-copyright-macro": "As above",
 82+ "mwe-copyright-custom": "Custom",
 83+
 84+ "mwe-upwiz-next": "Next",
 85+ "mwe-upwiz-home": "Go to Wiki home page",
 86+ "mwe-upwiz-upload-another": "Upload more files",
 87+
 88+ "mwe-prevent-close": "Your files are still uploading. Are you sure you want to navigate away from this page?",
 89+
 90+ "mwe-upwiz-files-complete": "Your files finished uploading!",
 91+ "mwe-upwiz-deeds-later": "Set deeds and licenses for each file individually on the next page",
 92+
 93+ "mwe-upwiz-tooltip-author": "The name of the person who took the photo, or painted the picture, drew the drawing, etc.",
 94+ "mwe-upwiz-tooltip-source": "Where this digital file came from -- could be a URL, or a book or publication",
 95+ "mwe-upwiz-tooltip-sign": "You can use your wiki User name or your real name. In both cases, this will be linked to your wiki User page",
 96+ "mwe-upwiz-tooltip-title": "A short title for the image. You may use plain language with spaces, but no line breaks. This title must be unlike all other titles in this wiki.",
 97+ "mwe-upwiz-tooltip-description": "Briefly describe everything notable about the work. For a photo, mention the main things that are depicted, the occasion or the place.",
 98+ "mwe-upwiz-tooltip-other": "Any other information you want to include about this work. You may use wikitext code."
 99+
 100+} );
 101+
 102+
 103+/**
 104+ *
 105+ */
 106+mw.UploadWizardDeed = function() {
 107+ var _this = this;
 108+ // prevent from instantiating directly?
 109+};
 110+
 111+/* sort of an abstract class */
 112+mw.UploadWizardDeed.prototype = {
 113+ isReady: function() {
 114+ return false;
 115+ },
 116+
 117+ getSourceWikiText: function() {
 118+ return $j( this.sourceInput ).val();
 119+ },
 120+
 121+ getAuthorWikiText: function() {
 122+ return $j( this.authorInput ).val();
 123+ },
 124+
 125+ /**
 126+ * Get wikitext representing the licenses selected in the license object
 127+ * @return wikitext of all applicable license templates.
 128+ */
 129+ getLicenseWikiText: function() {
 130+ var _this = this;
 131+ var wikiText = '';
 132+ $j.each ( _this.licenseInput.getTemplates(), function( i, template ) {
 133+ wikiText += "{{" + template + "}}\n";
 134+ } );
 135+
 136+ return wikiText;
 137+ }
 138+
 139+};
 140+
 141+mw.ProgressBar = function( selector, text ) {
 142+ var _this = this;
 143+ // XXX need to figure out a way to put text inside bar
 144+
 145+ _this.progressBarDiv = $j('<div></div>')
 146+ .addClass("mwe-upwiz-progress-bar")
 147+ .progressbar( { value: 0 } );
 148+
 149+ _this.timeRemainingDiv = $j('<div></div>').addClass("mwe-upwiz-etr");
 150+
 151+ _this.countDiv = $j('<div></div>').addClass("mwe-upwiz-count");
 152+
 153+ _this.beginTime = undefined;
 154+
 155+ $j( selector ).html(
 156+ $j('<div />').addClass( 'mwe-upwiz-progress' )
 157+ .append( $j( '<div></div>' )
 158+ .addClass( 'mwe-upwiz-progress-bar-etr' )
 159+ .append( _this.progressBarDiv )
 160+ .append( _this.timeRemainingDiv ) )
 161+ .append( $j( _this.countDiv ) )
 162+ );
 163+
 164+};
 165+
 166+mw.ProgressBar.prototype = {
 167+
 168+ /**
 169+ * sets the beginning time (useful for figuring out estimated time remaining)
 170+ * if time parameter omitted, will set beginning time to now
 171+ *
 172+ * @param time optional; the time this bar is presumed to have started (epoch milliseconds)
 173+ */
 174+ setBeginTime: function( time ) {
 175+ var _this = this;
 176+ _this.beginTime = time ? time : ( new Date() ).getTime();
 177+ },
 178+
 179+ /**
 180+ * sets the total number of things we are tracking
 181+ * @param total an integer, for display e.g. uploaded 1 of 5, this is the 5
 182+ */
 183+ setTotal: function(total) {
 184+ var _this = this;
 185+ _this.total = total;
 186+ },
 187+
 188+ /**
 189+ * Show overall progress for the entire UploadWizard
 190+ * The current design doesn't have individual progress bars, just one giant one.
 191+ * We did some tricky calculations in startUploads to try to weight each individual file's progress against
 192+ * the overall progress.
 193+ * @param fraction the amount of whatever it is that's done whatever it's done
 194+ */
 195+ showProgress: function( fraction ) {
 196+ var _this = this;
 197+
 198+ _this.progressBarDiv.progressbar( 'value', parseInt( fraction * 100, 10 ) );
 199+
 200+ var remainingTime;
 201+ if (_this.beginTime === null) {
 202+ remainingTime = 0;
 203+ } else {
 204+ remainingTime = _this.getRemainingTime( fraction );
 205+ }
 206+
 207+ if ( remainingTime !== null ) {
 208+ _this.timeRemainingDiv
 209+ .html( gM( 'mwe-upwiz-remaining', mw.seconds2npt(parseInt(remainingTime / 1000), 10) ) );
 210+ }
 211+ },
 212+
 213+ /**
 214+ * Calculate remaining time for all uploads to complete.
 215+ *
 216+ * @param fraction fraction of progress to show
 217+ * @return estimated time remaining (in milliseconds)
 218+ */
 219+ getRemainingTime: function ( fraction ) {
 220+ var _this = this;
 221+ if ( _this.beginTime ) {
 222+ var elapsedTime = ( new Date() ).getTime() - _this.beginTime;
 223+ if ( fraction > 0.0 && elapsedTime > 0 ) { // or some other minimums for good data
 224+ var rate = fraction / elapsedTime;
 225+ return parseInt( ( 1.0 - fraction ) / rate, 10 );
 226+ }
 227+ }
 228+ return null;
 229+ },
 230+
 231+
 232+ /**
 233+ * Show the overall count as we upload
 234+ * @param count -- the number of items that have done whatever has been done e.g. in "uploaded 2 of 5", this is the 2
 235+ */
 236+ showCount: function( count ) {
 237+ var _this = this;
 238+ _this.countDiv.html( gM( 'mwe-upwiz-upload-count', [ count, _this.total ] ) );
 239+ }
 240+
 241+
 242+};
 243+
 244+
 245+
 246+//mw.setConfig('uploadHandlerClass', mw.MockUploadHandler); // ApiUploadHandler?
 247+
 248+// available licenses should be a configuration of the MediaWiki instance,
 249+// not hardcoded here.
 250+// but, MediaWiki has no real concept of a License as a first class object -- there are templates and then specially - parsed
 251+// texts to create menus -- hack on top of hacks -- a bit too much to deal with ATM
 252+/**
 253+ * Create a group of checkboxes for licenses
 254+ * @param div
 255+ * @param values (optional) array of license key names to activate by default
 256+ * @param change (optional) function to execute when any value changes
 257+ */
 258+mw.UploadWizardLicenseInput = function( div, values ) {
 259+ var _this = this;
 260+
 261+ _this.change = function() {};
 262+
 263+ var c = mw.UploadWizardLicenseInput.prototype.count++;
 264+
 265+ // XXX get these for real
 266+ _this.licenses = {
 267+ pd: { template: 'pd', text: 'Public Domain' },
 268+ cc0: { template: 'cc0', text: 'Creative Commons Zero waiver' },
 269+ cc_by_30: { template: 'cc-by-30', text: 'Creative Commons Attribution 3.0' },
 270+ cc_by_sa_30: { template: 'cc-by-sa-30', text: 'Creative Commons Attribution ShareAlike 3.0' },
 271+ gfdl: { template: 'gfdl', text: 'GFDL (GNU Free Documentation License)' }
 272+ };
 273+
 274+ _this.inputs = [];
 275+
 276+ $div = $j( div );
 277+ $j.each( _this.licenses, function( key, data ) {
 278+ var id = 'license_' + key + '_' + c;
 279+ var input = $j( '<input />' )
 280+ .attr( { id: id, type: 'checkbox', value: key } )
 281+ .click( function() { _this.change() } );
 282+ data.input = input.get(0);
 283+ $div.append(
 284+ data.input,
 285+ $j( '<label />' ).attr( { 'for': id } ).html( data.text ),
 286+ $j( '<br/>' )
 287+ );
 288+ } );
 289+
 290+ if ( values ) {
 291+ _this.setValues( values );
 292+ }
 293+};
 294+
 295+mw.UploadWizardLicenseInput.prototype = {
 296+ count: 0,
 297+
 298+ /**
 299+ * Sets the value(s) of a license input.
 300+ * @param object of license-key to boolean values, e.g. { cc_by_sa_30: true, gfdl: true }
 301+ */
 302+ setValues: function( licenseValues ) {
 303+ var _this = this;
 304+ $j.each( _this.licenses, function( key, data ) {
 305+ var checked = ~~!!licenseValues[key];
 306+ $j( _this.licenses[key].input ).attr( { 'checked' : checked } );
 307+ } );
 308+ _this.change();
 309+ },
 310+
 311+ /**
 312+ * Set the default configured licenses - should change per wiki
 313+ */
 314+ setDefaultValues: function() {
 315+ var _this = this;
 316+ var values = {};
 317+ $j.each( mw.getConfig('defaultLicenses'), function( i, license ) {
 318+ values[license] = true;
 319+ } );
 320+ _this.setValues( values );
 321+ },
 322+
 323+ /**
 324+ * Gets which values are set to true
 325+ * @return object of object of license-key to boolean values, e.g. { cc_by_sa_30: true, gfdl: true }
 326+ */
 327+ getValues: function() {
 328+ var _this = this;
 329+ var values = {};
 330+ $j.each( _this.licenses, function( key, data ) {
 331+ if ( $j( _this.licenses[key].input ).is( ':checked' ) ) {
 332+ values[key] = data;
 333+ }
 334+ } );
 335+ return values;
 336+ },
 337+
 338+ /**
 339+ * Gets the templates associated with values set to true
 340+ * @return templates of licenses selected
 341+ */
 342+ getTemplates: function() {
 343+ var _this = this;
 344+ var templates = [];
 345+ $j.each( _this.getValues(), function( key, data ) {
 346+ templates.push( data.template );
 347+ } );
 348+ return templates;
 349+ },
 350+
 351+ /**
 352+ * Returns true if any license is set
 353+ * @return boolean
 354+ */
 355+ isSet: function() {
 356+ var _this = this;
 357+ return ( _this.getValues().length !== 0 );
 358+ }
 359+
 360+};
 361+
 362+
 363+/**
 364+ * Represents the upload -- in its local and remote state. (Possibly those could be separate objects too...)
 365+ * This is our 'model' object if we are thinking MVC. Needs to be better factored, lots of feature envy with the UploadWizard
 366+ * states:
 367+ * 'new' 'transporting' 'transported' 'details' 'submitting-details' 'complete'
 368+ * should fork this into two -- local and remote, e.g. filename
 369+ */
 370+mw.UploadWizardUpload = function( filesDiv ) {
 371+ var _this = this;
 372+ _this.state = 'new';
 373+ _this.transportWeight = 1; // default
 374+ _this.detailsWeight = 1; // default
 375+ _this._thumbnails = {};
 376+ _this.imageinfo = {};
 377+ _this.title = undefined;
 378+ _this.filename = undefined;
 379+ _this.originalFilename = undefined;
 380+ _this.mimetype = undefined;
 381+ _this.extension = undefined;
 382+
 383+ // details
 384+ _this.ui = new mw.UploadWizardUploadInterface( _this, filesDiv );
 385+
 386+ // handler -- usually ApiUploadHandler
 387+ // _this.handler = new ( mw.getConfig( 'uploadHandlerClass' ) )( _this );
 388+ // _this.handler = new mw.MockUploadHandler( _this );
 389+ _this.handler = new mw.ApiUploadHandler( _this );
 390+};
 391+
 392+mw.UploadWizardUpload.prototype = {
 393+
 394+ acceptDeed: function( deed ) {
 395+ var _this = this;
 396+ _this.deed.applyDeed( _this );
 397+ },
 398+
 399+ /**
 400+ * start
 401+ */
 402+ start: function() {
 403+ var _this = this;
 404+ _this.setTransportProgress(0.0);
 405+ _this.handler.start();
 406+ _this.ui.start();
 407+ },
 408+
 409+
 410+ /**
 411+ * remove
 412+ */
 413+ remove: function() {
 414+ var _this = this;
 415+ $j( _this.ui.div ).remove();
 416+ $j( _this.details.div ).remove();
 417+ $j( _this ).trigger( 'removeUpload' );
 418+ },
 419+
 420+ /**
 421+ * Wear our current progress, for observing processes to see
 422+ * @param fraction
 423+ */
 424+ setTransportProgress: function ( fraction ) {
 425+ var _this = this;
 426+ _this.state = 'transporting';
 427+ _this.transportProgress = fraction;
 428+ $j( _this ).trigger( 'transportProgressEvent' );
 429+ },
 430+
 431+ /**
 432+ * To be executed when an individual upload finishes. Processes the result and updates step 2's details
 433+ * @param result the API result in parsed JSON form
 434+ */
 435+ setTransported: function( result ) {
 436+ var _this = this;
 437+ _this.state = 'transported';
 438+ _this.transportProgress = 1;
 439+ $j( _this ).trigger( 'transportedEvent' );
 440+
 441+ if ( result.upload && result.upload.imageinfo && result.upload.imageinfo.descriptionurl ) {
 442+ // success
 443+ _this.extractUploadInfo( result );
 444+ _this.deedPreview.setup();
 445+ _this.details.populate();
 446+
 447+ } else if ( result.upload && result.upload.sessionkey ) {
 448+ // there was a warning - type error which prevented it from adding the result to the db
 449+ if ( result.upload.warnings.duplicate ) {
 450+ var duplicates = result.upload.warnings.duplicate;
 451+ _this.details.errorDuplicate( result.upload.sessionkey, duplicates );
 452+ }
 453+
 454+ // and other errors that result in a stash
 455+ } else if ( 0 /* actual failure */ ) {
 456+ // we may want to tag or otherwise queue it as an upload to retry
 457+ }
 458+
 459+
 460+ },
 461+
 462+
 463+ /**
 464+ * call when the file is entered into the file input
 465+ * get as much data as possible -- maybe exif, even thumbnail maybe
 466+ */
 467+ extractLocalFileInfo: function( localFilename ) {
 468+ var _this = this;
 469+ if (false) { // FileAPI, one day
 470+ _this.transportWeight = getFileSize();
 471+ }
 472+ _this.extension = mw.UploadWizardUtil.getExtension( localFilename );
 473+ // XXX add filename, original filename, extension, whatever else is interesting.
 474+ },
 475+
 476+
 477+ /**
 478+ * Accept the result from a successful API upload transport, and fill our own info
 479+ *
 480+ * @param result The JSON object from a successful API upload result.
 481+ */
 482+ extractUploadInfo: function( result ) {
 483+ var _this = this;
 484+
 485+ _this.filename = result.upload.filename;
 486+ _this.title = mw.getConfig( 'fileNamespace' ) + ':' + _this.filename;
 487+
 488+ _this.extractImageInfo( result.upload.imageinfo );
 489+
 490+ },
 491+
 492+ /**
 493+ * Extract image info into our upload object
 494+ * Image info is obtained from various different API methods
 495+ * @param imageinfo JSON object obtained from API result.
 496+ */
 497+ extractImageInfo: function( imageinfo ) {
 498+ var _this = this;
 499+ for ( var key in imageinfo ) {
 500+ // we get metadata as list of key-val pairs; convert to object for easier lookup. Assuming that EXIF fields are unique.
 501+ if ( key == 'metadata' ) {
 502+ _this.imageinfo.metadata = {};
 503+ if ( imageinfo.metadata && imageinfo.metadata.length ) {
 504+ $j.each( imageinfo.metadata, function( i, pair ) {
 505+ if ( pair !== undefined ) {
 506+ _this.imageinfo.metadata[pair['name'].toLowerCase()] = pair['value'];
 507+ }
 508+ } );
 509+ }
 510+ } else {
 511+ _this.imageinfo[key] = imageinfo[key];
 512+ }
 513+ }
 514+
 515+ // we should already have an extension, but if we don't...
 516+ if ( _this.extension === undefined ) {
 517+ var extension = mw.UploadWizardUtil.getExtension( _this.imageinfo.url );
 518+ if ( !extension ) {
 519+ if ( _this.imageinfo.mimetype ) {
 520+ if ( mw.UploadWizardUtil.mimetypeToExtension[ _this.imageinfo.mimetype ] ) {
 521+ extension = mw.UploadWizardUtil.mimetypeToExtension[ _this.imageinfo.mimetype ];
 522+ }
 523+ }
 524+ }
 525+ }
 526+ },
 527+
 528+ /**
 529+ * Supply information to create a thumbnail for this Upload. Runs async, with a callback.
 530+ * It is assumed you don't call this until it's been transported.
 531+ *
 532+ * 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
 533+ *
 534+ * @param width - desired width of thumbnail (height will scale to match)
 535+ * @param callback - callback to execute once thumbnail has been obtained -- must accept object with properties of width, height, and url.
 536+ */
 537+ getThumbnail: function( width, callback ) {
 538+ var _this = this;
 539+ if ( _this._thumbnails[ "width" + width ] !== undefined ) {
 540+ callback( _this._thumbnails[ "width" + width ] );
 541+ return;
 542+ }
 543+
 544+ var apiUrl = mw.getLocalApiUrl();
 545+
 546+ var params = {
 547+ 'titles': _this.title,
 548+ 'prop': 'imageinfo',
 549+ 'iiurlwidth': width,
 550+ 'iiprop': 'url'
 551+ };
 552+
 553+ mw.getJSON( apiUrl, params, function( data ) {
 554+ if ( !data || !data.query || !data.query.pages ) {
 555+ mw.log(" No data? ");
 556+ // XXX do something about the thumbnail spinner, maybe call the callback with a broken image.
 557+ return;
 558+ }
 559+
 560+ if ( data.query.pages[-1] ) {
 561+ // XXX do something about the thumbnail spinner, maybe call the callback with a broken image.
 562+ return;
 563+ }
 564+ for ( var page_id in data.query.pages ) {
 565+ var page = data.query.pages[ page_id ];
 566+ if ( ! page.imageinfo ) {
 567+ // not found? error
 568+ } else {
 569+ var imageInfo = page.imageinfo[0];
 570+ var thumbnail = {
 571+ width: imageInfo.thumbwidth,
 572+ height: imageInfo.thumbheight,
 573+ url: imageInfo.thumburl
 574+ };
 575+ _this._thumbnails[ "width" + width ] = thumbnail;
 576+ callback( thumbnail );
 577+ }
 578+ }
 579+ } );
 580+
 581+ },
 582+
 583+
 584+ /**
 585+ * look up thumbnail info and set it in HTML, with loading spinner
 586+ * it might be interesting to make this more of a publish/subscribe thing, since we have to do this 3x
 587+ * the callbacks may pile up, getting unnecessary info
 588+ *
 589+ * @param selector
 590+ * @param width
 591+ */
 592+ setThumbnail: function( selector, width, imgClass ) {
 593+ var _this = this;
 594+ if ( typeof width === 'undefined' || width === null || width <= 0 ) {
 595+ width = mw.getConfig( 'thumbnailWidth' );
 596+ }
 597+ width = parseInt( width, 10 );
 598+
 599+ if ( typeof imgClass === 'undefined' || imgClass === null ) {
 600+ imgClass = 'mwe-upwiz-thumbnail';
 601+ }
 602+
 603+ var callback = function( thumbnail ) {
 604+ // side effect: will replace thumbnail's loadingSpinner
 605+ $j( selector ).html(
 606+ $j('<a/>')
 607+ .attr( { 'href': _this.imageinfo.descriptionurl,
 608+ 'target' : '_new' } )
 609+ .append(
 610+ $j( '<img/>' )
 611+ .addClass( imgClass )
 612+ .attr( 'width', thumbnail.width )
 613+ .attr( 'height', thumbnail.height )
 614+ .attr( 'src', thumbnail.url ) ) );
 615+ };
 616+
 617+ $j( selector ).loadingSpinner();
 618+ _this.getThumbnail( width, callback );
 619+ }
 620+
 621+
 622+
 623+};
 624+
 625+/**
 626+ * Create an interface fragment corresponding to a file input, suitable for Upload Wizard.
 627+ * @param upload
 628+ * @param div to insert file interface
 629+ * @param addInterface interface to add a new one (assumed that we start out there)
 630+ */
 631+mw.UploadWizardUploadInterface = function( upload, filesDiv ) {
 632+ var _this = this;
 633+
 634+ _this.upload = upload;
 635+
 636+ // may need to collaborate with the particular upload type sometimes
 637+ // for the interface, as well as the uploadwizard. OY.
 638+ _this.div = $j('<div class="mwe-upwiz-file"></div>').get(0);
 639+ _this.isFilled = false;
 640+
 641+ _this.fileInputCtrl = $j('<input size="1" class="mwe-upwiz-file-input" name="file" type="file"/>')
 642+ .change( function() { _this.fileChanged(); } )
 643+ .get(0);
 644+
 645+
 646+ // XXX better class for helper, we probably have a standard already
 647+ _this.visibleFilename = $j('<div class="mwe-upwiz-visible-file"></div>').hide();
 648+
 649+ // XXX not sure if we will have a filename here -- we may want to autogenerate a "stashed" filename,
 650+ // with this flow
 651+ _this.filenameCtrl = $j('<input type="hidden" name="filename" value=""/>').get(0);
 652+
 653+ // this file Ctrl container is placed over other interface elements, intercepts clicks and gives them to the file input control.
 654+ // however, we want to pass hover events to interface elements that we are over, hence the bindings.
 655+ // n.b. not using toggleClass because it often gets this event wrong -- relies on previous state to know what to do
 656+ _this.fileCtrlContainer = $j('<div class="mwe-upwiz-file-ctrl-container">')
 657+ .bind( 'mouseenter', function(e) { _this.addFileCtrlHover(e); } )
 658+ .bind( 'mouseleave', function(e) { _this.removeFileCtrlHover(e); } );
 659+
 660+
 661+ // the css trickery (along with css)
 662+ // here creates a giant size file input control which is contained within a div and then
 663+ // clipped for overflow. The effect is that we have a div (ctrl-container) we can position anywhere
 664+ // which works as a file input. It will be set to opacity:0 and then we can do whatever we want with
 665+ // interface "below".
 666+ // XXX caution -- if the add file input changes size we won't match, unless we add some sort of event to catch this.
 667+ _this.form = $j('<form class="mwe-upwiz-form"></form>')
 668+ .append( _this.visibleFilename )
 669+ .append( _this.fileCtrlContainer
 670+ .append( _this.fileInputCtrl )
 671+ )
 672+ .append( _this.filenameCtrl ).get( 0 );
 673+
 674+ _this.progressMessage = $j('<span class="mwe-upwiz-status-message mwe-upwiz-file-indicator" style="display: none"></span>').get(0);
 675+
 676+
 677+ _this.errorDiv = $j('<div class="mwe-upwiz-upload-error mwe-upwiz-file-indicator" style="display: none;"></div>').get(0);
 678+
 679+ _this.removeCtrl = $j( '<div class="mwe-upwiz-file-indicator"><a title="'
 680+ + gM( 'mwe-upwiz-remove-upload' )
 681+ + '" href="#" class="mwe-upwiz-remove">x</a></div>' )
 682+ .click( function() { _this.upload.remove(); } )
 683+ .hide()
 684+ .get( 0 );
 685+
 686+
 687+ $j( _this.div ).append( _this.form )
 688+ .append( _this.progressMessage )
 689+ .append( _this.errorDiv )
 690+ .append( _this.removeCtrl );
 691+
 692+ // XXX evil hardcoded
 693+ // we don't really need filesdiv if we do it this way?
 694+ $j( _this.div ).insertBefore( '#mwe-upwiz-upload-ctrls' ); // append( _this.div );
 695+
 696+ // _this.progressBar = ( no progress bar for individual uploads yet )
 697+ // add a details thing to details
 698+ // this should bind only to the FIRST transportProgress
 699+ $j( upload ).bind( 'transportProgressEvent', function(e) { _this.showTransportProgress(); e.stopPropagation(); } );
 700+ $j( upload ).bind( 'transportedEvent', function(e) { _this.showTransported(); e.stopPropagation(); } );
 701+
 702+};
 703+
 704+
 705+mw.UploadWizardUploadInterface.prototype = {
 706+ /**
 707+ * Things to do to this interface once we start uploading
 708+ */
 709+ start: function() {
 710+ var _this = this;
 711+ $j( _this.removeCtrl ).hide();
 712+ },
 713+
 714+ /**
 715+ * Make this interface look "busy" (i.e. spinner) without indicating a particular percentage of file uploaded.
 716+ * Will be useful for encoding phase of Firefogg, for example.
 717+ */
 718+ busy: function() {
 719+ var _this = this;
 720+ // for now we implement this as looking like "100% progress"
 721+ // e.g. an animated bar that takes up all the space
 722+ _this.showTransportProgress( 1.0 );
 723+ },
 724+
 725+ /**
 726+ * Show progress by a fraction
 727+ * @param fraction The fraction of progress. Float between 0 and 1
 728+ */
 729+ showTransportProgress: function() {
 730+ var _this = this;
 731+ $j( _this.progressMessage ).addClass('mwe-upwiz-status-progress')
 732+ .html(gM( 'mwe-upwiz-uploading' ))
 733+ .show();
 734+ // since, in this iteration of the interface, we never need to know
 735+ // about progress again, let's unbind
 736+/*
 737+ // unbind is broken in jquery 1.4.1 -- raises exception but it still works
 738+ try {
 739+ $j( _this.upload ).unbind( 'transportProgressEvent' );
 740+ } catch (ex) { }
 741+*/
 742+ // update individual progress bar with fraction?
 743+ },
 744+
 745+ /**
 746+ * Execute when this upload is transported; cleans up interface.
 747+ * @param result AJAx result object
 748+ */
 749+ showTransported: function() {
 750+ var _this = this;
 751+ $j( _this.progressMessage ).removeClass( 'mwe-upwiz-status-progress' )
 752+ .addClass( 'mwe-upwiz-status-completed' )
 753+ .html( gM( 'mwe-upwiz-transported' ) );
 754+ },
 755+
 756+ /**
 757+ * Run this when the value of the file input has changed. Check the file for various forms of goodness.
 758+ * If okay, then update the visible filename (due to CSS trickery the real file input is invisible)
 759+ */
 760+ fileChanged: function() {
 761+ var _this = this;
 762+ _this.clearErrors();
 763+ _this.upload.extractLocalFileInfo( $j( _this.fileInputCtrl ).val() );
 764+ if ( _this.isGoodExtension( _this.upload.extension ) ) {
 765+ _this.updateFilename();
 766+ } else {
 767+ //_this.error( 'bad-filename-extension', ext );
 768+ alert("bad extension");
 769+ }
 770+ },
 771+
 772+ /**
 773+ * Move the file input to cover a certain element on the page.
 774+ * We use invisible file inputs because this is the only way to style a file input
 775+ * or otherwise get it to do what you want.
 776+ * It is helpful to sometimes move them to cover certain elements on the page, and
 777+ * even to pass events like hover
 778+ * @param selector jquery-compatible selector, for a single element
 779+ */
 780+ moveFileInputToCover: function( selector ) {
 781+ //mw.log( "moving to cover " + selector );
 782+ var _this = this;
 783+ var $covered = $j( selector );
 784+
 785+ //mw.log( "position: " );
 786+ //mw.log( $covered.position() );
 787+
 788+ _this.fileCtrlContainer
 789+ .css( $covered.position() )
 790+ .width( $covered.outerWidth() )
 791+ .height( $covered.outerHeight() );
 792+
 793+ // shift the file input over with negative margins,
 794+ // internal to the overflow-containing div, so the div shows all button
 795+ // and none of the textfield-like input
 796+ $j( _this.fileInputCtrl ).css( {
 797+ 'margin-left': '-' + ~~( $j( _this.fileInputCtrl).width() - $covered.outerWidth() - 10 ) + 'px',
 798+ 'margin-top' : '-' + ~~( $j( _this.fileInputCtrl).height() - $covered.outerHeight() - 10 ) + 'px'
 799+ } );
 800+
 801+ // we may be passing the file ctrl's hover events to another covered interface element
 802+ // see toggleFileCtrlHover
 803+ if ( _this.fileCtrlCovered ) {
 804+ _this.fileCtrlCovered.removeClass( 'hover' );
 805+ }
 806+ _this.fileCtrlCovered = $covered;
 807+
 808+ },
 809+
 810+ /**
 811+ * add class to an interface element covered by the fileCtrlContainer
 812+ * we are not using jQuery.toggleClass because it seems to get this wrong too often -- dumbly activates when should deactivate &
 813+ * vice versa.
 814+ * @param jquery event
 815+ */
 816+ addFileCtrlHover: function(e) {
 817+ if ( this.fileCtrlCovered ) {
 818+ this.fileCtrlCovered.addClass( 'hover' );
 819+ }
 820+ },
 821+
 822+ /**
 823+ * remove class from an jquery-wrapped interface element covered by the fileCtrlContainer
 824+ * we are not using jQuery.toggleClass because it seems to get this wrong too often -- dumbly activates when should deactivate &
 825+ * vice versa.
 826+ * @param jquery event
 827+ */
 828+ removeFileCtrlHover: function(e) {
 829+ if ( this.fileCtrlCovered ) {
 830+ this.fileCtrlCovered.removeClass( 'hover' );
 831+ }
 832+ },
 833+
 834+
 835+ /**
 836+ * this does two things:
 837+ * 1 ) since the file input has been hidden with some clever CSS ( to avoid x-browser styling issues ),
 838+ * update the visible filename
 839+ *
 840+ * 2 ) update the filename desired when added to MediaWiki. This should be RELATED to the filename on the filesystem,
 841+ * 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.
 842+ * 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.
 843+ * Ideally we should ask the SERVER for a decently unique filename related to our own.
 844+ * So, at the moment, this is hacked with a guaranteed - unique filename instead.
 845+ */
 846+ updateFilename: function() {
 847+ var _this = this;
 848+ var path = $j(_this.fileInputCtrl).attr('value');
 849+
 850+
 851+ // visible filename
 852+ $j( _this.visibleFilename ).html(
 853+ '<span class="ui-icon ui-icon-document" style="display: inline-block;"></span> ' + path
 854+ );
 855+
 856+ // desired filename
 857+ var filename = _this.convertPathToFilename( path );
 858+ _this.upload.originalFilename = filename;
 859+ // this is a hack to get a filename guaranteed unique.
 860+ uniqueFilename = mw.getConfig( 'userName' ) + "_" + ( new Date() ).getTime() + "_" + filename;
 861+ $j( _this.filenameCtrl ).attr( 'value', uniqueFilename );
 862+
 863+ if ( ! _this.isFilled ) {
 864+ _this.isFilled = true;
 865+ $j( _this.div ).addClass( 'filled' );
 866+ $j( _this.visibleFilename ).show();
 867+ $j( _this.removeCtrl ).show();
 868+ $j(_this.div ).css( {
 869+ 'position': 'relative', // own our own file input; it will move with us now.
 870+ 'height': '24px'
 871+ } );
 872+ _this.moveFileInputToCover( _this.visibleFilename );
 873+ $j( _this.upload ).trigger( 'filled' );
 874+ } else {
 875+ $j( _this.upload ).trigger( 'filenameAccepted' );
 876+ }
 877+ },
 878+
 879+ /**
 880+ * Remove any complaints we had about errors and such
 881+ * XXX this should be changed to something Theme compatible
 882+ */
 883+ clearErrors: function() {
 884+ var _this = this;
 885+ $j( _this.div ).removeClass( 'mwe-upwiz-upload-error ');
 886+ $j( _this.errorDiv ).hide().empty();
 887+ },
 888+
 889+ /**
 890+ * Show an error with the upload
 891+ */
 892+ error: function() {
 893+ var _this = this;
 894+ var args = Array.prototype.slice.call( arguments ); // copies arguments into a real array
 895+ var msg = 'mwe-upwiz-upload-error-' + args[0];
 896+ $j( _this.errorDiv ).append( $j( '<p class="mwe-upwiz-upload-error">' + gM( msg, args.slice( 1 ) ) + '</p>') );
 897+ // apply a error style to entire did
 898+ $j( _this.div ).addClass( 'mwe-upwiz-upload-error' );
 899+ $j( _this.errorDiv ).show();
 900+ },
 901+
 902+ /**
 903+ * Get the extension of the path in fileInputCtrl
 904+ * @return extension as string
 905+ */
 906+ getExtension: function() {
 907+ var _this = this;
 908+ var path = $j(_this.fileInputCtrl).attr('value');
 909+ return mw.UploadWizardUtil.getExtension(path);
 910+ },
 911+
 912+ /**
 913+ * XXX this is common utility code
 914+ * used when converting contents of a file input and coming up with a suitable "filename" for mediawiki
 915+ * test: what if path is length 0
 916+ * what if path is all separators
 917+ * what if path ends with a separator character
 918+ * what if it ends with multiple separator characters
 919+ *
 920+ * @param path
 921+ * @return filename suitable for mediawiki as string
 922+ */
 923+ convertPathToFilename: function( path ) {
 924+ if (path === undefined || path == '') {
 925+ return '';
 926+ }
 927+
 928+ var lastFileSeparatorIdx = Math.max(path.lastIndexOf( '/' ), path.lastIndexOf( '\\' ));
 929+ // lastFileSeparatorIdx is now -1 if no separator found, or some index in the string.
 930+ // so, +1, that is either 0 ( beginning of string ) or the character after last separator.
 931+ // caution! could go past end of string... need to be more careful
 932+ var filename = path.substr( lastFileSeparatorIdx + 1 );
 933+ return mw.UploadWizardUtil.pathToTitle( filename );
 934+
 935+
 936+
 937+ },
 938+
 939+ /**
 940+ * XXX this is common utility code
 941+ * copied because we'll probably need it... stripped from old doDestinationFill
 942+ * this is used when checking for "bad" extensions in a filename.
 943+ * @param ext
 944+ * @return boolean if extension was acceptable
 945+ */
 946+ isGoodExtension: function( ext ) {
 947+ var _this = this;
 948+ var found = false;
 949+ var extensions = mw.getConfig('fileExtensions');
 950+ if ( extensions ) {
 951+ for ( var i = 0; i < extensions.length; i++ ) {
 952+ if ( extensions[i].toLowerCase() == ext ) {
 953+ found = true;
 954+ }
 955+ }
 956+ }
 957+ return found;
 958+ }
 959+
 960+};
 961+
 962+/**
 963+ * Object that represents an indvidual language description, in the details portion of Upload Wizard
 964+ * @param languageCode
 965+ */
 966+mw.UploadWizardDescription = function( languageCode ) {
 967+ var _this = this;
 968+
 969+ // Logic copied from MediaWiki:UploadForm.js
 970+ // Per request from Portuguese and Brazilian users, treat Brazilian Portuguese as Portuguese.
 971+ if (languageCode == 'pt-br') {
 972+ languageCode = 'pt';
 973+ // this was also in UploadForm.js, but without the heartwarming justification
 974+ } else if (languageCode == 'en-gb') {
 975+ languageCode = 'en';
 976+ }
 977+
 978+ _this.languageMenu = mw.LanguageUpWiz.getMenu("lang", languageCode);
 979+ $j(_this.languageMenu).addClass('mwe-upwiz-desc-lang-select');
 980+ _this.description = $j('<textarea name="desc" rows="2" cols="36" class="mwe-upwiz-desc-lang-text"></textarea>')
 981+ .attr( 'title', gM( 'mwe-upwiz-tooltip-description' ) )
 982+ .tipsy( { gravity: 'w', trigger: 'focus' } )
 983+ .growTextArea();
 984+ _this.div = $j('<div class="mwe-upwiz-desc-lang-container"></div>')
 985+ .append( _this.languageMenu )
 986+ .append( _this.description );
 987+
 988+};
 989+
 990+mw.UploadWizardDescription.prototype = {
 991+
 992+ /**
 993+ * Obtain text of this description, suitable for including into Information template
 994+ * @return wikitext as a string
 995+ */
 996+ getWikiText: function() {
 997+ var _this = this;
 998+ var language = $j( _this.languageMenu ).val().trim();
 999+ var fix = mw.getConfig("languageTemplateFixups");
 1000+ if (fix[language]) {
 1001+ language = fix[language];
 1002+ }
 1003+ return '{{' + language + '|1=' + $j( _this.description ).val().trim() + '}}';
 1004+ }
 1005+};
 1006+
 1007+/**
 1008+ * Object that represents the Details (step 2) portion of the UploadWizard
 1009+ * n.b. each upload gets its own details.
 1010+ *
 1011+ * XXX a lot of this construction is not really the jQuery way.
 1012+ * The correct thing would be to have some hidden static HTML
 1013+ * on the page which we clone and slice up with selectors. Inputs can still be members of the object
 1014+ * but they'll be found by selectors, not by creating them as members and then adding them to a DOM structure.
 1015+ *
 1016+ * XXX this should have styles for what mode we're in
 1017+ *
 1018+ * @param UploadWizardUpload
 1019+ * @param containerDiv The div to put the interface into
 1020+ */
 1021+mw.UploadWizardDetails = function( upload, containerDiv ) {
 1022+
 1023+ var _this = this;
 1024+ _this.upload = upload;
 1025+
 1026+ _this.descriptions = [];
 1027+
 1028+ _this.div = $j( '<div class="mwe-upwiz-info-file"></div>' );
 1029+
 1030+ _this.thumbnailDiv = $j( '<div class="mwe-upwiz-thumbnail"></div>' );
 1031+
 1032+ _this.errorDiv = $j( '<div class="mwe-upwiz-details-error"></div>' );
 1033+
 1034+ _this.dataDiv = $j( '<div class="mwe-upwiz-data"></div>' );
 1035+
 1036+ // descriptions
 1037+ _this.descriptionsDiv = $j( '<div class="mwe-upwiz-details-descriptions mwe-upwiz-details-input"></div>' );
 1038+
 1039+
 1040+ _this.descriptionAdder = $j( '<a class="mwe-upwiz-desc-add"/>' )
 1041+ .attr( 'href', '#' )
 1042+ .html( gM( 'mwe-upwiz-desc-add-0' ) )
 1043+ .click( function( ) { _this.addDescription(); } );
 1044+
 1045+ _this.descriptionsContainerDiv =
 1046+ $j( '<div class="mwe-upwiz-details-descriptions-container"></div>' )
 1047+ .append( $j( '<div class="mwe-upwiz-details-label">' + gM( 'mwe-upwiz-desc' ) + '</div>' ) )
 1048+ .append( _this.descriptionsDiv )
 1049+ .append( $j( '<div class="mwe-upwiz-details-descriptions-add"></div>' )
 1050+ .append( _this.descriptionAdder ) );
 1051+ // Commons specific help for titles
 1052+ // http://commons.wikimedia.org/wiki/Commons:File_naming
 1053+ // http://commons.wikimedia.org/wiki/MediaWiki:Filename-prefix-blacklist
 1054+ // XXX make sure they can't use ctrl characters or returns or any other bad stuff.
 1055+ _this.titleInput = $j( '<textarea type="text" rows="1" class="mwe-title mwe-long-textarea"></textarea>' )
 1056+ .attr( 'title', gM( 'mwe-upwiz-tooltip-title' ) )
 1057+ .tipsy( { gravity: 'w', trigger: 'focus' } )
 1058+ .keyup( function() {
 1059+ _this.setFilenameFromTitle();
 1060+ } )
 1061+ .growTextArea()
 1062+ .destinationChecked( {
 1063+ spinner: function(bool) { _this.toggleDestinationBusy(bool); },
 1064+ preprocess: function( name ) { return _this.getFilenameFromTitle(); }, // XXX this is no longer a pre-process
 1065+ processResult: function( result ) { _this.processDestinationCheck( result ); }
 1066+ } )
 1067+ ;
 1068+
 1069+ _this.titleErrorDiv = $j('<div></div>');
 1070+
 1071+ _this.titleContainerDiv = $j('<div class="mwe-upwiz-details-label-input></div>')
 1072+ .append( $j( '<div class="mwe-upwiz-details-label"></div>' ).append( gM( 'mwe-upwiz-title' ) ) )
 1073+ .append( $j( '<div class="mwe-upwiz-details-input"></div>' ).append( _this.titleInput ) )
 1074+ .append( _this.titleErrorDiv );
 1075+
 1076+ _this.deedDiv = $j( '<div class="mwe-upwiz-custom-deed" />' );
 1077+
 1078+ _this.copyrightInfoFieldset = $j('<fieldset class="mwe-fieldset mwe-upwiz-copyright-info"></fieldset>')
 1079+ .hide()
 1080+ .append(
 1081+ $j( '<legend class="mwe-legend">' ).append( gM( 'mwe-upwiz-copyright-info' ) ),
 1082+ _this.deedDiv
 1083+ );
 1084+
 1085+ _this.moreDetailsDiv = $j('<div class="mwe-more-details"></div>').maskSafeHide();
 1086+
 1087+ _this.moreDetailsCtrlDiv = $j( '<div class="mwe-upwiz-details-more-options"></div>' );
 1088+
 1089+
 1090+
 1091+ _this.dateInput = $j( '<input type="text" class="mwe-date" size="20"/>' );
 1092+ // XXX suddenly this isn't working. Seems to be a problem with monobook. If I datepicker-ify an input outside the
 1093+ // content area, it works. Vector is fine
 1094+ $j( _this.dateInput ).datepicker( {
 1095+ dateFormat: 'yy-mm-dd', // oddly, this means yyyy-mm-dd
 1096+ buttonImage: '/js/mwEmbed/skins/common/images/calendar.gif',
 1097+ buttonImageOnly: false // XXX determine what this does, docs are confusing
 1098+ } );
 1099+
 1100+ _this.locationInput = $j( '<input type="text" class="mwe-location" size="20"/>' );
 1101+
 1102+ var aboutThisWorkFieldset = $j('<fieldset class="mwe-fieldset"></fieldset>')
 1103+ .append( $j( '<legend class="mwe-legend">' ).append( gM( 'mwe-upwiz-about-this-work' ) ) )
 1104+ .append( $j( '<div class="mwe-upwiz-details-more-subdiv">' )
 1105+ .append( $j( '<div class="mwe-upwiz-details-label-input"></div>' )
 1106+ .append( $j( '<div class="mwe-upwiz-details-more-label"></div>' ).append( gM( 'mwe-upwiz-date-created' ) ) )
 1107+ .append( $j( '<div class="mwe-upwiz-details-more-input"></div>' ).append( _this.dateInput ) )
 1108+ )
 1109+ .append( $j ( '<div style="display: none;"></div>' ) // see prefillLocation
 1110+ .append( $j( '<div class="mwe-upwiz-details-more-label"></div>' ).append( gM( 'mwe-upwiz-location' ) ) )
 1111+ .append( $j( '<div class="mwe-upwiz-details-more-input"></div>' ).append( _this.locationInput ) )
 1112+ )
 1113+ );
 1114+
 1115+
 1116+
 1117+ var aboutFileFieldset = $j('<fieldset class="mwe-fieldset"></fieldset>')
 1118+ .append( $j( '<legend class="mwe-legend">' ).append( gM( 'mwe-upwiz-about-format' ) ) )
 1119+ .append( $j( '<div class="mwe-upwiz-details-more-subdiv">' )
 1120+ .append( $j( '<div></div>' )
 1121+ .append( $j( '<div class="mwe-upwiz-details-more-label"></div>' ).append( gM( 'mwe-upwiz-filename-tag' ) ) )
 1122+ .append( $j( '<div class="mwe-upwiz-details-filename mwe-upwiz-details-more-input"></div>' ) ) ) );
 1123+
 1124+ _this.otherInformationInput = $j( '<textarea class="mwe-upwiz-other-textarea"></textarea>' )
 1125+ .growTextArea()
 1126+ .attr( 'title', gM( 'mwe-upwiz-tooltip-other' ) )
 1127+ .tipsy( { gravity: 'w', trigger: 'focus' } );
 1128+
 1129+ var otherInformationDiv = $j('<div></div>')
 1130+ .append( $j( '<div class="mwe-upwiz-details-more-label">' ).append( gM( 'mwe-upwiz-other' ) ) )
 1131+ .append( _this.otherInformationInput );
 1132+
 1133+
 1134+ $j( _this.div )
 1135+ .addClass( 'ui-helper-clearfix' )
 1136+ .append( _this.thumbnailDiv )
 1137+ .append( _this.errorDiv )
 1138+ .append( $j( _this.dataDiv )
 1139+ .append( _this.descriptionsContainerDiv )
 1140+ .append( _this.titleContainerDiv )
 1141+ .append( _this.copyrightInfoFieldset )
 1142+ .append( _this.moreDetailsCtrlDiv )
 1143+ .append( $j( _this.moreDetailsDiv )
 1144+ .append( aboutThisWorkFieldset )
 1145+ //.append( aboutFileFieldset )
 1146+ .append( otherInformationDiv )
 1147+ )
 1148+ );
 1149+
 1150+ mw.UploadWizardUtil.makeMoreToggler( _this.moreDetailsCtrlDiv, _this.moreDetailsDiv );
 1151+
 1152+ _this.addDescription();
 1153+ $j( containerDiv ).append( _this.div );
 1154+
 1155+
 1156+};
 1157+
 1158+mw.UploadWizardDetails.prototype = {
 1159+
 1160+ /**
 1161+ * toggles whether we use the 'macro' deed or our own
 1162+ */
 1163+ useCustomDeedChooser: function() {
 1164+ var _this = this;
 1165+ _this.copyrightInfoFieldset.show();
 1166+ _this.upload.wizardDeedChooser = _this.upload.deedChooser;
 1167+ _this.upload.deedChooser = new mw.UploadWizardDeedChooser( _this.deedDiv );
 1168+ },
 1169+
 1170+ useDeedChooser: function( deedChooser ) {
 1171+ var _this = this;
 1172+ _this.upload.deedChooser = deedChooser;
 1173+ _this.deedDiv.empty();
 1174+ },
 1175+
 1176+
 1177+ /**
 1178+ * Sets the filename from the title plus this upload's extension.
 1179+ */
 1180+ setFilenameFromTitle: function() {
 1181+ var _this = this;
 1182+ _this.filename = mw.getConfig( 'fileNamespace' ) + ':' + _this.getFilenameFromTitle();
 1183+ $j( '#mwe-upwiz-details-filename' ).text( _this.filename );
 1184+
 1185+ },
 1186+
 1187+ /**
 1188+ * Gets a filename from the human readable title, using upload's extension.
 1189+ * @return Filename
 1190+ */
 1191+ getFilenameFromTitle: function() {
 1192+ var _this = this;
 1193+ var name = $j( _this.titleInput ).val();
 1194+ return mw.UploadWizardUtil.pathToTitle( name ) + '.' + _this.upload.extension;
 1195+ },
 1196+
 1197+
 1198+ /**
 1199+ * show file destination field as "busy" while checking
 1200+ * @param busy boolean true = show busy-ness, false = remove
 1201+ */
 1202+ toggleDestinationBusy: function ( busy ) {
 1203+ var _this = this;
 1204+ if (busy) {
 1205+ _this.titleInput.addClass( "busy" );
 1206+ } else {
 1207+ _this.titleInput.removeClass( "busy" );
 1208+ }
 1209+ },
 1210+
 1211+ /**
 1212+ * Process the result of a destination filename check.
 1213+ * See mw.DestinationChecker.js for documentation of result format
 1214+ * XXX would be simpler if we created all these divs in the DOM and had a more jquery-friendly way of selecting
 1215+ * attrs. Instead we create & destroy whole interface each time. Won't someone think of the DOM elements?
 1216+ * @param result
 1217+ */
 1218+ processDestinationCheck: function( result ) {
 1219+ var _this = this;
 1220+
 1221+ if ( result.isUnique ) {
 1222+ _this.titleErrorDiv.hide().empty();
 1223+ _this.ignoreWarningsInput = undefined;
 1224+ return;
 1225+ }
 1226+
 1227+ // result is NOT unique
 1228+ var title = result.title;
 1229+ var img = result.img;
 1230+ var href = result.href;
 1231+
 1232+ _this.ignoreWarningsInput = $j("<input />").attr( { type: 'checkbox', name: 'ignorewarnings' } );
 1233+
 1234+ var $fileAlreadyExists = $j('<div />')
 1235+ .append(
 1236+ gM( 'mwe-fileexists',
 1237+ $j('<a />')
 1238+ .attr( { target: '_new', href: href } )
 1239+ .text( title )
 1240+ ),
 1241+ $j('<br />'),
 1242+ _this.ignoreWarningsInput,
 1243+ gM('mwe-upwiz-overwrite')
 1244+ );
 1245+
 1246+ var $imageLink = $j('<a />')
 1247+ .addClass( 'image' )
 1248+ .attr( { target: '_new', href: href } )
 1249+ .append(
 1250+ $j( '<img />')
 1251+ .addClass( 'thumbimage' )
 1252+ .attr( {
 1253+ 'width' : img.thumbwidth,
 1254+ 'height' : img.thumbheight,
 1255+ 'border' : 0,
 1256+ 'src' : img.thumburl,
 1257+ 'alt' : title
 1258+ } )
 1259+ );
 1260+
 1261+ var $imageCaption = $j( '<div />' )
 1262+ .addClass( 'thumbcaption' )
 1263+ .append(
 1264+ $j('<div />')
 1265+ .addClass( "magnify" )
 1266+ .append(
 1267+ $j('<a />' )
 1268+ .addClass( 'internal' )
 1269+ .attr( {
 1270+ 'title' : gM('mwe-thumbnail-more'),
 1271+ 'href' : href
 1272+ } ),
 1273+
 1274+ $j( '<img />' )
 1275+ .attr( {
 1276+ 'border' : 0,
 1277+ 'width' : 15,
 1278+ 'height' : 11,
 1279+ 'src' : mw.getConfig( 'images_path' ) + 'magnify-clip.png'
 1280+ } ),
 1281+
 1282+ $j('<span />')
 1283+ .html( gM( 'mwe-fileexists-thumb' ) )
 1284+ )
 1285+ );
 1286+
 1287+ $j( _this.titleErrorDiv ).html(
 1288+ $j('<span />') // dummy argument since .html() only takes one arg
 1289+ .append(
 1290+ $fileAlreadyExists,
 1291+ $j( '<div />' )
 1292+ .addClass( 'thumb tright' )
 1293+ .append(
 1294+ $j( '<div />' )
 1295+ .addClass( 'thumbinner' )
 1296+ .css({
 1297+ 'width' : ( parseInt( img.thumbwidth ) + 2 ) + 'px;'
 1298+ })
 1299+ .append(
 1300+ $imageLink,
 1301+ $imageCaption
 1302+ )
 1303+ )
 1304+ )
 1305+ ).show();
 1306+
 1307+ },
 1308+
 1309+ /**
 1310+ * Do anything related to a change in the number of descriptions
 1311+ */
 1312+ recountDescriptions: function() {
 1313+ var _this = this;
 1314+ // if there is some maximum number of descriptions, deal with that here
 1315+ $j( _this.descriptionAdder ).html( gM( 'mwe-upwiz-desc-add-' + ( _this.descriptions.length == 0 ? '0' : 'n' ) ) );
 1316+ },
 1317+
 1318+
 1319+ /**
 1320+ * Add a new description
 1321+ */
 1322+ addDescription: function() {
 1323+ var _this = this;
 1324+ var languageCode = _this.descriptions.length ? mw.LanguageUpWiz.UNKNOWN : mw.getConfig('userLanguage' );
 1325+ var description = new mw.UploadWizardDescription( languageCode );
 1326+
 1327+ description.removeCtrl = $j('<a title="' + gM( 'mwe-upwiz-remove-description' ) + '" href="#">x</a>' )
 1328+ .addClass('mwe-upwiz-remove' )
 1329+ .addClass('mwe-upwiz-remove-desc' )
 1330+ .click( function() { _this.removeDescription( description ) } )
 1331+ .get( 0 );
 1332+ $j( description.div ).append( description.removeCtrl );
 1333+
 1334+ $j( _this.descriptionsDiv ).append( description.div );
 1335+ _this.descriptions.push( description );
 1336+ _this.recountDescriptions();
 1337+ },
 1338+
 1339+ /**
 1340+ * Remove a description
 1341+ * @param description
 1342+ */
 1343+ removeDescription: function( description ) {
 1344+ var _this = this;
 1345+ $j( description.div ).remove();
 1346+ mw.UploadWizardUtil.removeItem( _this.descriptions, description );
 1347+ _this.recountDescriptions();
 1348+ },
 1349+
 1350+ /**
 1351+ * Display an error with details
 1352+ * XXX this is a lot like upload ui's error -- should merge
 1353+ */
 1354+ error: function() {
 1355+ var _this = this;
 1356+ var args = Array.prototype.slice.call( arguments ); // copies arguments into a real array
 1357+ var msg = 'mwe-upwiz-upload-error-' + args[0];
 1358+ $j( _this.errorDiv ).append( $j( '<p class="mwe-upwiz-upload-error">' + gM( msg, args.slice( 1 ) ) + '</p>' ) );
 1359+ // apply a error style to entire did
 1360+ $j( _this.div ).addClass( 'mwe-upwiz-upload-error' );
 1361+ $j( _this.dataDiv ).hide();
 1362+ $j( _this.errorDiv ).show();
 1363+ },
 1364+
 1365+ /**
 1366+ * Given the API result pull some info into the form ( for instance, extracted from EXIF, desired filename )
 1367+ * @param result Upload API result object
 1368+ */
 1369+ populate: function() {
 1370+ var _this = this;
 1371+ mw.log( "populating details from upload" );
 1372+ _this.upload.setThumbnail( _this.thumbnailDiv );
 1373+ _this.prefillDate();
 1374+ _this.prefillSource();
 1375+ _this.prefillAuthor();
 1376+ _this.prefillTitle();
 1377+ _this.prefillFilename();
 1378+ _this.prefillLocation();
 1379+ },
 1380+
 1381+ /**
 1382+ * Check if we got an EXIF date back; otherwise use today's date; and enter it into the details
 1383+ * XXX We ought to be using date + time here...
 1384+ * EXIF examples tend to be in ISO 8601, but the separators are sometimes things like colons, and they have lots of trailing info
 1385+ * (which we should actually be using, such as time and timezone)
 1386+ */
 1387+ prefillDate: function() {
 1388+ var _this = this;
 1389+ var yyyyMmDdRegex = /^(\d\d\d\d)[:\/-](\d\d)[:\/-](\d\d)\D.*/;
 1390+ var dateStr;
 1391+ var metadata = _this.upload.imageinfo.metadata;
 1392+ $j.each([metadata.datetimeoriginal, metadata.datetimedigitized, metadata.datetime, metadata['date']],
 1393+ function( i, imageinfoDate ) {
 1394+ if ( imageinfoDate !== undefined ) {
 1395+ var d = imageinfoDate.trim();
 1396+ if ( d.match( yyyyMmDdRegex ) ) {
 1397+ dateStr = d.replace( yyyyMmDdRegex, "$1-$2-$3" );
 1398+ return false; // break from $j.each
 1399+ }
 1400+ }
 1401+ }
 1402+ );
 1403+ // if we don't have EXIF or other metadata, let's use "now"
 1404+ // XXX if we have FileAPI, it might be clever to look at file attrs, saved
 1405+ // in the upload object for use here later, perhaps
 1406+ function pad( n ) {
 1407+ return n < 10 ? "0" + n : n;
 1408+ }
 1409+
 1410+ if (dateStr === undefined) {
 1411+ d = new Date();
 1412+ dateStr = d.getUTCFullYear() + '-' + pad(d.getUTCMonth()) + '-' + pad(d.getUTCDate());
 1413+ }
 1414+
 1415+ // ok by now we should definitely have a date string formatted in YYYY-MM-DD
 1416+ $j( _this.dateInput ).val( dateStr );
 1417+ },
 1418+
 1419+ /**
 1420+ * Set the title of the thing we just uploaded, visibly
 1421+ * Note: the interface's notion of "filename" versus "title" is the opposite of MediaWiki
 1422+ */
 1423+ prefillTitle: function() {
 1424+ var _this = this;
 1425+ var titleExt = mw.UploadWizardUtil.titleToPath( _this.upload.originalFilename );
 1426+ var title = titleExt.replace( /\.\w+$/, '' );
 1427+ $j( _this.titleInput ).val( title );
 1428+ },
 1429+
 1430+ /**
 1431+ * Set the title of the thing we just uploaded, visibly
 1432+ * Note: the interface's notion of "filename" versus "title" is the opposite of MediaWiki
 1433+ */
 1434+ prefillFilename: function() {
 1435+ var _this = this;
 1436+ _this.setFilenameFromTitle();
 1437+ },
 1438+
 1439+ /**
 1440+ * Prefill location inputs (and/or scroll to position on map) from image info and metadata
 1441+ *
 1442+ * At least for my test images, the EXIF parser on MediaWiki is not giving back any data for
 1443+ * GPSLatitude, GPSLongitude, or GPSAltitudeRef. It is giving the lat/long Refs, the Altitude, and the MapDatum
 1444+ * So, this is broken until we fix MediaWiki's parser, OR, parse it ourselves somehow
 1445+ *
 1446+ * in Image namespace
 1447+ * GPSTag Long ??
 1448+ *
 1449+ * in GPSInfo namespace
 1450+ * GPSVersionID byte* 2000 = 2.0.0.0
 1451+ * GPSLatitude rational
 1452+ * GPSLatitudeRef ascii (N | S) or North | South
 1453+ * GPSLongitude rational
 1454+ * GPSLongitudeRef ascii (E | W) or East | West
 1455+ * GPSAltitude rational
 1456+ * GPSAltitudeRef byte (0 | 1) above or below sea level
 1457+ * GPSImgDirection rational
 1458+ * GPSImgDirectionRef ascii (M | T) magnetic or true north
 1459+ * GPSMapDatum ascii "WGS-84" is the standard
 1460+ *
 1461+ * A 'rational' is a string like this:
 1462+ * "53/1 0/1 201867/4096" --> 53 deg 0 min 49.284 seconds
 1463+ * "2/1 11/1 64639/4096" --> 2 deg 11 min 15.781 seconds
 1464+ * "122/1" -- 122 m (altitude)
 1465+ */
 1466+ prefillLocation: function() {
 1467+ var _this = this;
 1468+ var metadata = _this.upload.imageinfo.metadata;
 1469+ if (metadata === undefined) {
 1470+ return;
 1471+ }
 1472+
 1473+
 1474+ },
 1475+
 1476+ /**
 1477+ * Given a decimal latitude and longitude, return filled out {{Location}} template
 1478+ * @param latitude decimal latitude ( -90.0 >= n >= 90.0 ; south = negative )
 1479+ * @param longitude decimal longitude ( -180.0 >= n >= 180.0 ; west = negative )
 1480+ * @param scale (optional) how rough the geocoding is.
 1481+ * @param heading (optional) what direction the camera is pointing in. (decimal 0.0-360.0, 0 = north, 90 = E)
 1482+ * @return string with WikiText which will geotag this record
 1483+ */
 1484+ coordsToWikiText: function(latitude, longitude, scale, heading) {
 1485+ //Wikipedia
 1486+ //http://en.wikipedia.org/wiki/Wikipedia:WikiProject_Geographical_coordinates#Parameters
 1487+ // http://en.wikipedia.org/wiki/Template:Coord
 1488+ //{{coord|61.1631|-149.9721|type:landmark_globe:earth_region:US-AK_scale:150000_source:gnis|name=Kulis Air National Guard Base}}
 1489+
 1490+ //Wikimedia Commons
 1491+ //{{Coor dms|41|19|20.4|N|19|38|36.7|E}}
 1492+ //{{Location}}
 1493+
 1494+ },
 1495+
 1496+ /**
 1497+ * If there is a way to figure out source from image info, do so here
 1498+ * XXX user pref?
 1499+ */
 1500+ prefillSource: function() {
 1501+ // we have no way to do this AFAICT
 1502+ },
 1503+
 1504+ /**
 1505+ * Prefill author (such as can be determined) from image info and metadata
 1506+ * XXX user pref?
 1507+ */
 1508+ prefillAuthor: function() {
 1509+ var _this = this;
 1510+ if (_this.upload.imageinfo.metadata.author !== undefined) {
 1511+ $j( _this.authorInput ).val( _this.upload.imageinfo.metadata.author );
 1512+ }
 1513+
 1514+ },
 1515+
 1516+ /**
 1517+ * Prefill license (such as can be determined) from image info and metadata
 1518+ * XXX user pref?
 1519+ */
 1520+ prefillLicense: function() {
 1521+ var _this = this;
 1522+ var copyright = _this.upload.imageinfo.metadata.copyright;
 1523+ if (copyright !== undefined) {
 1524+ if (copyright.match(/\bcc-by-sa\b/i)) {
 1525+ // set license to be that CC-BY-SA
 1526+ } else if (copyright.match(/\bcc-by\b/i)) {
 1527+ // set license to be that
 1528+ } else if (copyright.match(/\bcc-zero\b/i)) {
 1529+ // set license to be that
 1530+ // XXX any other licenses we could guess from copyright statement
 1531+ } else {
 1532+ $j( _this.licenseInput ).val( copyright );
 1533+ }
 1534+ }
 1535+ },
 1536+
 1537+
 1538+ /**
 1539+ *
 1540+ showErrors: function() {
 1541+ var _this = this;
 1542+ $j.each( _this.errors, function() {
 1543+
 1544+ } );
 1545+ },
 1546+ */
 1547+
 1548+ /**
 1549+ * Convert entire details for this file into wikiText, which will then be posted to the file
 1550+ * XXX there is a WikiText sanitizer in use on UploadForm -- use that here, or port it
 1551+ * @return wikitext representing all details
 1552+ */
 1553+ getWikiText: function() {
 1554+ var _this = this;
 1555+
 1556+ // XXX validate!
 1557+ if ( ! _this.isReady() ) {
 1558+ alert( "ZOMG THIS DEED IS NOT READY" );
 1559+ return null;
 1560+ }
 1561+
 1562+
 1563+
 1564+ wikiText = '';
 1565+
 1566+
 1567+ // http://commons.wikimedia.org / wiki / Template:Information
 1568+
 1569+ // can we be more slick and do this with maps, applys, joins?
 1570+ var information = {
 1571+ 'description' : '', // {{lang|description in lang}}* required
 1572+ 'date' : '', // YYYY, YYYY-MM, or YYYY-MM-DD required - use jquery but allow editing, then double check for sane date.
 1573+ 'source' : '', // {{own}} or wikitext optional
 1574+ 'author' : '', // any wikitext, but particularly {{Creator:Name Surname}} required
 1575+ 'permission' : '', // leave blank unless OTRS pending; by default will be "see below" optional
 1576+ 'other_versions' : '', // pipe separated list, other versions optional
 1577+ 'other_fields' : '' // ??? additional table fields
 1578+ };
 1579+
 1580+ // sanity check the descriptions -- do not have two in the same lang
 1581+ // all should be a known lang
 1582+ if ( _this.descriptions.length === 0 ) {
 1583+ // ruh roh
 1584+ // we should not even allow them to press the button ( ? ) but then what about the queue...
 1585+ }
 1586+ $j.each( _this.descriptions, function( i, desc ) {
 1587+ information['description'] += desc.getWikiText();
 1588+ } );
 1589+
 1590+ // XXX add a sanity check here for good date
 1591+ information['date'] = $j( _this.dateInput ).val().trim();
 1592+
 1593+ var deed = _this.upload.deedChooser.deed;
 1594+
 1595+ information['source'] = deed.getSourceWikiText();
 1596+
 1597+ information['author'] = deed.getAuthorWikiText();
 1598+
 1599+ var info = '';
 1600+ for ( var key in information ) {
 1601+ info += '|' + key + '=' + information[key] + "\n";
 1602+ }
 1603+
 1604+ wikiText += "=={{int:filedesc}}==\n";
 1605+
 1606+ wikiText += '{{Information\n' + info + '}}\n';
 1607+
 1608+
 1609+ wikiText += "=={{int:license-header}}==\n";
 1610+
 1611+ wikiText += deed.getLicenseWikiText();
 1612+
 1613+ // add a location template
 1614+
 1615+ // add an "anything else" template if needed
 1616+ var otherInfoWikiText = $j( _this.otherInformationInput ).val().trim();
 1617+ if ( otherInfoWikiText != '' ) {
 1618+ wikiText += "=={{int:otherinfo}}==\n";
 1619+ wikiText += otherInfoWikiText;
 1620+ }
 1621+
 1622+ return wikiText;
 1623+ },
 1624+
 1625+ /**
 1626+ * Check if we are ready to post wikitext
 1627+ */
 1628+ isReady: function() {
 1629+ var _this = this;
 1630+ return _this.upload.deedChooser.deed.isReady();
 1631+
 1632+ // somehow, all the various issues discovered with this upload should be present in a single place
 1633+ // where we can then check on
 1634+ // perhaps as simple as _this.issues or _this.agenda
 1635+ },
 1636+
 1637+ /**
 1638+ * Post wikitext as edited here, to the file
 1639+ * XXX This should be split up -- one part should get wikitext from the interface here, and the ajax call
 1640+ * should be be part of upload
 1641+ */
 1642+ submit: function() {
 1643+ var _this = this;
 1644+
 1645+
 1646+ // are we okay to submit?
 1647+ // all necessary fields are ready
 1648+ // check descriptions
 1649+ // the filename is in a sane state
 1650+ var desiredFilename = _this.filename;
 1651+ shouldRename = ( desiredFilename != _this.upload.title );
 1652+
 1653+ // if ok to go
 1654+ // XXX lock down the interface, spinnerify
 1655+ // else
 1656+ // point out problems
 1657+
 1658+
 1659+ // XXX check state of details for okayness ( license selected, at least one desc, sane filename )
 1660+ var wikiText = _this.getWikiText();
 1661+ mw.log( wikiText );
 1662+
 1663+ var params = {
 1664+ action: 'edit',
 1665+ token: mw.getConfig( 'token' ),
 1666+ title: _this.upload.title,
 1667+ // section: 0, ?? causing issues?
 1668+ text: wikiText,
 1669+ summary: "User edited page with " + mw.UploadWizard.userAgent,
 1670+ // notminor: 1,
 1671+ // basetimestamp: _this.upload.imageinfo.timestamp, ( conflicts? )
 1672+ nocreate: 1
 1673+ };
 1674+
 1675+ var endCallback = function() { _this.completeDetailsSubmission(); }
 1676+
 1677+ mw.log( "editing!" );
 1678+ mw.log( params );
 1679+ var callback = function( result ) {
 1680+ mw.log( result );
 1681+ mw.log( "successful edit" );
 1682+ if ( shouldRename ) {
 1683+ _this.rename( desiredFilename, endCallback );
 1684+ } else {
 1685+ endCallback();
 1686+ }
 1687+ };
 1688+
 1689+ _this.upload.state = 'submitting-details';
 1690+ _this.showProgress();
 1691+ mw.getJSON( params, callback );
 1692+ },
 1693+
 1694+ /**
 1695+ * Rename the file
 1696+ *
 1697+ * 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
 1698+ * we need an "incomplete" upload status
 1699+ * we are presuming this File page is brand new, so let's not bother with the whole redirection deal. ('noredirect')
 1700+ *
 1701+ * use _this.ignoreWarningsInput (if it exists) to check if we can blithely move the file or if we have a problem if there
 1702+ * is a file by that name already there
 1703+ *
 1704+ * @param filename to rename this file to
 1705+ */
 1706+ rename: function( title, endCallback ) {
 1707+ var _this = this;
 1708+ mw.log("renaming!");
 1709+ params = {
 1710+ action: 'move',
 1711+ from: _this.upload.title,
 1712+ to: title,
 1713+ reason: "User edited page with " + mw.UploadWizard.userAgent,
 1714+ movetalk: '',
 1715+ noredirect: '', // presume it's too new
 1716+ token: mw.getConfig('token')
 1717+ };
 1718+ mw.log(params);
 1719+ // despite the name, getJSON magically changes this into a POST request (it has a list of methods and what they require).
 1720+ mw.getJSON( params, function( data ) {
 1721+ // handle errors later
 1722+ // possible error data: { code = 'missingtitle' } -- orig filename not there
 1723+ // and many more
 1724+
 1725+ // which should match our request.
 1726+ // we should update the current upload filename
 1727+ // then call the uploadwizard with our progress
 1728+
 1729+ // success is
 1730+ // { move = { from : ..., reason : ..., redirectcreated : ..., to : .... }
 1731+ if (data !== undefined && data.move !== undefined && data.move.to !== undefined) {
 1732+ _this.upload.title = data.move.to;
 1733+ _this.refreshImageInfo( _this.upload, _this.upload.title, endCallback );
 1734+ }
 1735+ } );
 1736+ },
 1737+
 1738+ /**
 1739+ * Get new image info, for instance, after we renamed an image
 1740+ *
 1741+ * @param upload an UploadWizardUpload object
 1742+ * @param title title to look up remotely
 1743+ * @param endCallback execute upon completion
 1744+ */
 1745+ refreshImageInfo: function( upload, title, endCallback ) {
 1746+ var params = {
 1747+ 'titles': title,
 1748+ 'prop': 'imageinfo',
 1749+ 'iiprop': 'timestamp|url|user|size|sha1|mime|metadata'
 1750+ };
 1751+ // XXX timeout callback?
 1752+ mw.getJSON( params, function( data ) {
 1753+ if ( data && data.query && data.query.pages ) {
 1754+ if ( ! data.query.pages[-1] ) {
 1755+ for ( var page_id in data.query.pages ) {
 1756+ var page = data.query.pages[ page_id ];
 1757+ if ( ! page.imageinfo ) {
 1758+ // not found? error
 1759+ } else {
 1760+ upload.extractImageInfo( page.imageinfo[0] );
 1761+ }
 1762+ }
 1763+ }
 1764+ }
 1765+ endCallback();
 1766+ } );
 1767+ },
 1768+
 1769+ showProgress: function() {
 1770+ var _this = this;
 1771+ _this.div.mask();
 1772+ // XXX spinnerize
 1773+ _this.upload.detailsProgress = 1.0;
 1774+ },
 1775+
 1776+ completeDetailsSubmission: function() {
 1777+ var _this = this;
 1778+ _this.upload.state = 'complete';
 1779+ // XXX de-spinnerize
 1780+ _this.div.unmask();
 1781+ }
 1782+
 1783+};
 1784+
 1785+
 1786+/**
 1787+ * Object that reperesents the entire multi-step Upload Wizard
 1788+ */
 1789+mw.UploadWizard = function() {
 1790+
 1791+ this.uploads = [];
 1792+
 1793+};
 1794+
 1795+
 1796+mw.UploadWizard.userAgent = "UploadWizard (alpha) on " + $j.browser.name + " " + $j.browser.version;
 1797+
 1798+
 1799+mw.UploadWizard.prototype = {
 1800+ maxUploads: 10, // XXX get this from config
 1801+ maxSimultaneousUploads: 2, // XXX get this from config
 1802+ stepNames: [ 'file', 'deeds', 'details', 'thanks' ],
 1803+ currentStepName: undefined,
 1804+
 1805+ /*
 1806+ // list possible upload handlers in order of preference
 1807+ // these should all be in the mw.* namespace
 1808+ // hardcoded for now. maybe some registry system might work later, like, all
 1809+ // things which subclass off of UploadHandler
 1810+ uploadHandlers: [
 1811+ 'FirefoggUploadHandler',
 1812+ 'XhrUploadHandler',
 1813+ 'ApiIframeUploadHandler',
 1814+ 'SimpleUploadHandler',
 1815+ 'NullUploadHandler'
 1816+ ],
 1817+
 1818+ * We can use various UploadHandlers based on the browser's capabilities. Let's pick one.
 1819+ * For example, the ApiUploadHandler should work just about everywhere, but XhrUploadHandler
 1820+ * allows for more fine-grained upload progress
 1821+ * @return valid JS upload handler class constructor function
 1822+ getUploadHandlerClass: function() {
 1823+ // return mw.MockUploadHandler;
 1824+ return mw.ApiUploadHandler;
 1825+ var _this = this;
 1826+ for ( var i = 0; i < uploadHandlers.length; i++ ) {
 1827+ var klass = mw[uploadHandlers[i]];
 1828+ if ( klass != undefined && klass.canRun( this.config )) {
 1829+ return klass;
 1830+ }
 1831+ }
 1832+ // this should never happen; NullUploadHandler should always work
 1833+ return null;
 1834+ },
 1835+ */
 1836+
 1837+ reset: function() {
 1838+ var _this = this;
 1839+ $j.each( _this.uploads, function( i, upload ) {
 1840+ _this.removeUpload( upload );
 1841+ } );
 1842+ _this.uploads = [];
 1843+ },
 1844+
 1845+
 1846+ /**
 1847+ * create the basic interface to make an upload in this div
 1848+ * @param div The div in the DOM to put all of this into.
 1849+ */
 1850+ createInterface: function( selector ) {
 1851+ var _this = this;
 1852+ var div = $j( selector ).get(0);
 1853+ div.innerHTML =
 1854+
 1855+ '<div id="mwe-upwiz-steparrows" class="ui-helper-clearfix">'
 1856+ + '<ul>'
 1857+ + '<li id="mwe-upwiz-step-file"><span class="mwe-arrow-text">' + gM('mwe-upwiz-step-file') + '<span class="mwe-arrow"/></span></span></li>'
 1858+ + '<li id="mwe-upwiz-step-deeds"><span class="mwe-arrow-text">' + gM('mwe-upwiz-step-deeds') + '<span class="mwe-arrow"/></span></span></li>'
 1859+ + '<li id="mwe-upwiz-step-details"><span class="mwe-arrow-text">' + gM('mwe-upwiz-step-details') + '<span class="mwe-arrow"/></span></span></li>'
 1860+ + '<li id="mwe-upwiz-step-thanks"><span class="mwe-arrow-text">' + gM('mwe-upwiz-step-thanks') + '<span class="mwe-arrow"/></span></span></li>'
 1861+ + '</ul>'
 1862+ + '</div>'
 1863+
 1864+ + '<div id="mwe-upwiz-content">'
 1865+
 1866+ + '<div class="mwe-upwiz-stepdiv ui-helper-clearfix" id="mwe-upwiz-stepdiv-file">'
 1867+ + '<div id="mwe-upwiz-intro">' + gM('mwe-upwiz-intro') + '</div>'
 1868+ + '<div id="mwe-upwiz-files">'
 1869+ + '<div class="shim" style="height: 120px"></div>'
 1870+ + '<div id="mwe-upwiz-upload-ctrls" class="mwe-upwiz-file">'
 1871+ + '<div id="mwe-upwiz-add-file-container" class="mwe-upwiz-add-files-0">'
 1872+ + '<a id="mwe-upwiz-add-file">' + gM("mwe-upwiz-add-file-0") + '</a>'
 1873+ + '</div>'
 1874+ + '<div id="proceed" class="mwe-upwiz-file-indicator" style="display: none;">'
 1875+ + '<button id="mwe-upwiz-upload-ctrl" disabled="disabled">' + gM("mwe-upwiz-upload") + '</button>'
 1876+ + '</div>'
 1877+ + '</div>'
 1878+ + '<div id="mwe-upwiz-progress" class="ui-helper-clearfix"></div>'
 1879+ + '</div>'
 1880+ + '<div class="mwe-upwiz-buttons"/>'
 1881+ + '<button class="mwe-upwiz-button-next" />'
 1882+ + '</div>'
 1883+ + '</div>'
 1884+
 1885+ + '<div class="mwe-upwiz-stepdiv" id="mwe-upwiz-stepdiv-deeds">'
 1886+ + '<div id="mwe-upwiz-deeds-intro"></div>'
 1887+ + '<div id="mwe-upwiz-deeds-thumbnails" class="ui-helper-clearfix"></div>'
 1888+ + '<div id="mwe-upwiz-deeds" class="ui-helper-clearfix"></div>'
 1889+ + '<div id="mwe-upwiz-deeds-custom" class="ui-helper-clearfix"></div>'
 1890+ + '<div class="mwe-upwiz-buttons"/>'
 1891+ + '<button class="mwe-upwiz-button-next" />'
 1892+ + '</div>'
 1893+ + '</div>'
 1894+
 1895+ + '<div class="mwe-upwiz-stepdiv" id="mwe-upwiz-stepdiv-details">'
 1896+ + '<div id="mwe-upwiz-macro">'
 1897+ + '<div id="mwe-upwiz-macro-progress" class="ui-helper-clearfix"></div>'
 1898+ + '<div id="mwe-upwiz-macro-choice">'
 1899+ + '<div>' + gM( 'mwe-upwiz-details-intro' ) + '</div>'
 1900+ + '</div>'
 1901+ + '<div id="mwe-upwiz-macro-files"></div>'
 1902+ + '</div>'
 1903+ + '<div class="mwe-upwiz-buttons"/>'
 1904+ + '<button class="mwe-upwiz-button-next" />'
 1905+ + '</div>'
 1906+ + '</div>'
 1907+
 1908+ + '<div class="mwe-upwiz-stepdiv" id="mwe-upwiz-stepdiv-thanks">'
 1909+ + '<div id="mwe-upwiz-thanks"></div>'
 1910+ + '<div class="mwe-upwiz-buttons"/>'
 1911+ + '<button class="mwe-upwiz-button-begin"></button>'
 1912+ + '<br/><button class="mwe-upwiz-button-home"></button>'
 1913+ + '</div>'
 1914+ + '</div>'
 1915+
 1916+ + '</div>'
 1917+
 1918+ + '<div class="mwe-upwiz-clearing"></div>';
 1919+
 1920+
 1921+ $j( '.mwe-upwiz-button-home' )
 1922+ .append( gM( 'mwe-upwiz-home' ) )
 1923+ .click( function() { window.location.href = '/' } );
 1924+
 1925+ $j( '.mwe-upwiz-button-begin' )
 1926+ .append( gM( 'mwe-upwiz-upload-another' ) )
 1927+ .click( _this.reset() );
 1928+
 1929+ $j( '.mwe-upwiz-button-next' )
 1930+ .append( gM( 'mwe-upwiz-next' ) )
 1931+
 1932+ // within FILE step div
 1933+ $j('#mwe-upwiz-upload-ctrl').click( function() {
 1934+ _this.removeEmptyUploads();
 1935+ _this.startUploads();
 1936+ } );
 1937+
 1938+ $j( '#mwe-upwiz-stepdiv-file .mwe-upwiz-button-next').click( function() {
 1939+ _this.moveToStep( 'deeds' );
 1940+ } );
 1941+
 1942+ // DEEDS div
 1943+ _this.deedChooser = new mw.UploadWizardDeedChooser( '#mwe-upwiz-deeds' );
 1944+
 1945+ var customDeed = $j.extend( new mw.UploadWizardDeed(), {
 1946+ isReady: function() { return true; },
 1947+ setQuantity: function( n ) { return; }
 1948+ } );
 1949+
 1950+ $j( '#mwe-upwiz-deeds-custom' ).append(
 1951+ $j( '<div id="mwe-upwiz-deeds-later" />' )
 1952+ .hide()
 1953+ .append( $j( '<label>' )
 1954+ .append(
 1955+ $j( '<input />')
 1956+ .attr( { type: 'checkbox', value: 'deeds-later' } )
 1957+ .addClass( 'mwe-accept-deed' )
 1958+ .click( function() {
 1959+ if ( $j( this ).is( ':checked' ) ) {
 1960+ _this.deedChooser.showDeedChoice();
 1961+ _this.deedChooser.choose( customDeed );
 1962+ //_this.deedChooser.trigger( 'isReady' );
 1963+ } else {
 1964+ _this.deedChooser.choose( mw.UploadWizardNullDeed );
 1965+ }
 1966+ } ),
 1967+ $j( '<span />').append( gM( 'mwe-upwiz-deeds-later' ) )
 1968+ )
 1969+ )
 1970+ );
 1971+
 1972+ $j( _this.deedChooser ).bind( 'setQuantityEvent', function() {
 1973+ mw.log( "checking this deedchooser count " + _this.deedChooser.count );
 1974+ if ( _this.deedChooser.count > 1 ) {
 1975+ $j( '#mwe-upwiz-deeds-later' ).show();
 1976+ } else {
 1977+ $j( '#mwe-upwiz-deeds-later' ).hide();
 1978+ }
 1979+ } );
 1980+
 1981+ $j( '#mwe-upwiz-deeds-intro' ).html( gM( 'mwe-upwiz-deeds-intro' ) );
 1982+
 1983+ $j( '#mwe-upwiz-stepdiv-deeds .mwe-upwiz-button-next').click( function() {
 1984+ _this.moveToStep('details');
 1985+ } );
 1986+
 1987+ // XXX perhaps we should defer this until we click next
 1988+ $j( _this.deedChooser ).bind( 'chooseDeed', function() {
 1989+ var isCustom = ( _this.deedChooser.deed === customDeed );
 1990+ $j.each( _this.uploads, function( i, upload ) {
 1991+ if (isCustom) {
 1992+ upload.details.useCustomDeedChooser();
 1993+ } else {
 1994+ upload.details.useDeedChooser( _this.deedChooser );
 1995+ }
 1996+ } );
 1997+ } );
 1998+
 1999+ // XXX perhaps we should defer this until we click next
 2000+ $j( _this.deedChooser ).bind( 'chooseNullDeed', function() {
 2001+ $j.each( _this.uploads, function( i, upload ) {
 2002+ upload.details.deedChooser = _this.deedChooser;
 2003+ } );
 2004+ // $j( '#mwe-upwiz-stepdiv-deeds' ).disableNextButton();
 2005+ } );
 2006+
 2007+ /*
 2008+ // XXX figure out some way to make them ready / unready
 2009+ // timeout after input, check for readiness, then trigger event?
 2010+ $j( _this.deedChooser ).bind( 'isReady', function() {
 2011+ $j( '#mwe-upwiz-stepdiv-deeds' ).enableNextButton();
 2012+ } );
 2013+
 2014+ $j( _this.deedChooser ).bind( 'isNotReady', function() {
 2015+ $j( '#mwe-upwiz-stepdiv-deeds' ).disableNextButton();
 2016+ } );
 2017+*/
 2018+
 2019+
 2020+ // DETAILS div
 2021+ $j( '#mwe-upwiz-stepdiv-details .mwe-upwiz-button-next' ).click( function() {
 2022+ _this.detailsSubmit( function() {
 2023+ _this.prefillThanksPage();
 2024+ _this.moveToStep('thanks');
 2025+ } );
 2026+ } );
 2027+
 2028+ // add one to start
 2029+ var upload = _this.newUpload( '#mwe-upwiz-add-file' );
 2030+
 2031+ // "select" the first step - highlight, make it visible, hide all others
 2032+ _this.moveToStep( 'file', function() {
 2033+ // XXX moving the file input doesn't seem to work at this point; we get its old position before
 2034+ // CSS is applied. Hence, using a timeout.
 2035+ // XXX using a timeout is lame, are there other options?
 2036+ // XXX Trevor suggests that using addClass() may queue stuff unnecessarily; use 'concrete' HTML
 2037+ setTimeout( function() {
 2038+ upload.ui.moveFileInputToCover( '#mwe-upwiz-add-file' );
 2039+ }, 300 );
 2040+ } );
 2041+
 2042+ },
 2043+
 2044+ /**
 2045+ * Advance one "step" in the wizard interface.
 2046+ * It is assumed that the previous step to the current one was selected.
 2047+ * We do not hide the tabs because this messes up certain calculations we'd like to make about dimensions, while elements are not
 2048+ * on screen. So instead we make the tabs zero height and, in CSS, they are already overflow hidden
 2049+ * @param selectedStepName
 2050+ * @param callback to do after layout is ready?
 2051+ */
 2052+ moveToStep: function( selectedStepName, callback ) {
 2053+ var _this = this;
 2054+ $j.each( _this.stepNames, function(i, stepName) {
 2055+
 2056+ // the step indicator
 2057+ var step = $j( '#mwe-upwiz-step-' + stepName );
 2058+
 2059+ // the step's contents
 2060+ var stepDiv = $j( '#mwe-upwiz-stepdiv-' + stepName );
 2061+
 2062+ if ( _this.currentStepName == stepName ) {
 2063+ // we hide the old stepDivs because we are afraid of some z-index elements that may interfere with later tabs
 2064+ // this will break if we ever allow people to page back and forth.
 2065+ step.hide( 1000 );
 2066+ stepDiv.hide();
 2067+ } else if ( selectedStepName == stepName ) {
 2068+ stepDiv.maskSafeShow();
 2069+ step.addClass( 'mwe-upwiz-step-highlight' );
 2070+ } else {
 2071+ // it's neither the formerly active nor the newly active one, so don't show it
 2072+ // we don't use hide() because we want to manipulate elements within future tabs, and calculate their dimensions.
 2073+ // stepDiv.maskSafeHide();
 2074+ }
 2075+ } );
 2076+
 2077+ _this.currentStepName = selectedStepName;
 2078+
 2079+ $j.each( _this.uploads, function(i, upload) {
 2080+ upload.state = selectedStepName;
 2081+ } );
 2082+
 2083+ if ( callback ) {
 2084+ callback();
 2085+ }
 2086+ },
 2087+
 2088+ /**
 2089+ * add an Upload
 2090+ * we create the upload interface, a handler to transport it to the server,
 2091+ * and UI for the upload itself and the "details" at the second step of the wizard.
 2092+ * we don't yet add it to the list of uploads; that only happens when it gets a real file.
 2093+ * @return boolean success
 2094+ */
 2095+ newUpload: function() {
 2096+ var _this = this;
 2097+ if ( _this.uploads.length == _this.maxUploads ) {
 2098+ return false;
 2099+ }
 2100+
 2101+ var upload = new mw.UploadWizardUpload( _this, '#mwe-upwiz-files' );
 2102+ _this.uploadToAdd = upload;
 2103+
 2104+ upload.ui.moveFileInputToCover( '#mwe-upwiz-add-file' );
 2105+ $j( upload ).bind( 'filenameAccepted', function(e) { _this.updateFileCounts(); e.stopPropagation(); } );
 2106+ $j( upload ).bind( 'removeUpload', function(e) { _this.removeUpload( upload ); e.stopPropagation(); } );
 2107+ $j( upload ).bind( 'filled', function(e) {
 2108+ _this.newUpload();
 2109+ _this.setUploadFilled(upload);
 2110+ e.stopPropagation();
 2111+ } );
 2112+ // XXX bind to some error state
 2113+
 2114+
 2115+ return upload;
 2116+ },
 2117+
 2118+ /**
 2119+ * When an upload is filled with a real file, accept it in the wizard's list of uploads
 2120+ * and set up some other interfaces
 2121+ * @param UploadWizardUpload
 2122+ */
 2123+ setUploadFilled: function( upload ) {
 2124+ var _this = this;
 2125+
 2126+ // XXX check if it has a file?
 2127+
 2128+ _this.uploads.push( upload );
 2129+ _this.updateFileCounts();
 2130+
 2131+ upload.deedPreview = new mw.UploadWizardDeedPreview( upload );
 2132+
 2133+ // this will modify upload, so it has a .deedChooser property.
 2134+ // We use a method to so we notify deedChooser that it has a new upload -- interface
 2135+ // will change based on quantity etc. Maybe we could be clever with bind here.
 2136+ _this.deedChooser.attach( upload );
 2137+
 2138+ // set up details
 2139+ upload.details = new mw.UploadWizardDetails( upload, $j( '#mwe-upwiz-macro-files' ));
 2140+ },
 2141+
 2142+
 2143+ /**
 2144+ * Remove an upload from our array of uploads, and the HTML UI
 2145+ * We can remove the HTML UI directly, as jquery will just get the parent.
 2146+ * We need to grep through the array of uploads, since we don't know the current index.
 2147+ * We need to update file counts for obvious reasons.
 2148+ *
 2149+ * @param upload
 2150+ */
 2151+ removeUpload: function( upload ) {
 2152+ var _this = this;
 2153+ $j( upload ).unbind(); // everything
 2154+ if ( upload.deedChooser ) {
 2155+ upload.deedChooser.detach( upload );
 2156+ }
 2157+ upload.details.div.remove();
 2158+ upload.thanksDiv.remove();
 2159+
 2160+ mw.UploadWizardUtil.removeItem( _this.uploads, upload );
 2161+ _this.updateFileCounts();
 2162+ },
 2163+
 2164+ /**
 2165+ * This is useful to clean out unused upload file inputs if the user hits GO.
 2166+ * We are using a second array to iterate, because we will be splicing the main one, _this.uploads
 2167+ */
 2168+ removeEmptyUploads: function() {
 2169+ var _this = this;
 2170+ var toRemove = [];
 2171+
 2172+ for ( var i = 0; i < _this.uploads.length; i++ ) {
 2173+ if ( _this.uploads[i].ui.fileInputCtrl.value == "" ) {
 2174+ toRemove.push( _this.uploads[i] );
 2175+ }
 2176+ }
 2177+
 2178+ for ( var i = 0; i < toRemove.length; i++ ) {
 2179+ toRemove[i].remove();
 2180+ }
 2181+ },
 2182+
 2183+ /**
 2184+ * Manage transitioning all of our uploads from one state to another -- like from "new" to "uploaded".
 2185+ * Shows progress bar with estimated time remaining.
 2186+ *
 2187+ * There are too many args here. How to fix?
 2188+ * This is starting to feel like an object.
 2189+ *
 2190+ * @param beginState what state the upload should be in before starting.
 2191+ * @param progressState the state to set the upload to while it's doing whatever
 2192+ * @param endState the state to set the upload to after it's done whatever
 2193+ * @param progressProperty the property on the upload showing current progress of whatever
 2194+ * @param weightProperty the property on the upload giving how heavy to weight this item in total progress calculation
 2195+ * @param starter function, taking single argument (upload) which starts the process we're interested in
 2196+ * @param progressBarSelector where to put the progress bar
 2197+ * @param endCallback function to call when all uploads are in the end state.
 2198+ */
 2199+ makeTransitioner: function( beginState,
 2200+ progressState,
 2201+ endState,
 2202+ progressProperty,
 2203+ weightProperty,
 2204+ progressBarSelector,
 2205+ progressBarText,
 2206+ starter,
 2207+ endCallback ) {
 2208+
 2209+ var wizard = this;
 2210+
 2211+ var totalWeight = 0.0;
 2212+ $j.each( wizard.uploads, function( i, upload ) {
 2213+ totalWeight += upload[weightProperty];
 2214+ } );
 2215+ var totalCount = wizard.uploads.length;
 2216+
 2217+ var progressBar = new mw.ProgressBar( progressBarSelector, progressBarText );
 2218+ progressBar.setTotal( totalCount );
 2219+
 2220+ transitioner = function() {
 2221+ var fraction = 0.0;
 2222+ var uploadsToStart = wizard.maxSimultaneousUploads;
 2223+ var endStateCount = 0;
 2224+ $j.each( wizard.uploads, function(i, upload) {
 2225+ if ( upload.state == endState ) {
 2226+ endStateCount++;
 2227+ } else if ( upload.state == progressState ) {
 2228+ uploadsToStart--;
 2229+ } else if ( ( upload.state == beginState ) && ( uploadsToStart > 0 ) ) {
 2230+ starter( upload );
 2231+ uploadsToStart--;
 2232+ }
 2233+ if (upload[progressProperty] !== undefined) {
 2234+ fraction += upload[progressProperty] * ( upload[weightProperty] / totalWeight );
 2235+ }
 2236+ } );
 2237+
 2238+ // perhaps this could be collected into a single progressbar obj
 2239+ progressBar.showProgress( fraction );
 2240+ progressBar.showCount( endStateCount );
 2241+
 2242+ // build in a little delay even for the end state, so user can see progress bar in a complete state.
 2243+ var nextAction = (endStateCount == totalCount) ? endCallback : transitioner;
 2244+ setTimeout( nextAction, wizard.transitionerDelay );
 2245+ };
 2246+
 2247+ progressBar.setBeginTime();
 2248+ transitioner();
 2249+ },
 2250+
 2251+ transitionerDelay: 300, // milliseconds
 2252+
 2253+
 2254+
 2255+ /**
 2256+ * Kick off the upload processes.
 2257+ * Does some precalculations, changes the interface to be less mutable, moves the uploads to a queue,
 2258+ * and kicks off a thread which will take from the queue.
 2259+ */
 2260+ startUploads: function( finishedCallback ) {
 2261+ var _this = this;
 2262+ // remove the upload button, and the add file button
 2263+ $j( '#mwe-upwiz-upload-ctrl' ).hide();
 2264+ $j( '#mwe-upwiz-add-file' ).hide();
 2265+
 2266+ var allowCloseWindow = $j().preventCloseWindow( {
 2267+ message: gM( 'mwe-prevent-close')
 2268+ } );
 2269+
 2270+
 2271+ // remove ability to change files
 2272+ // ideally also hide the "button"... but then we require styleable file input CSS trickery
 2273+ // although, we COULD do this just for files already in progress...
 2274+
 2275+ // it might be interesting to just make this creational -- attach it to the dom element representing
 2276+ // the progress bar and elapsed time
 2277+ _this.makeTransitioner(
 2278+ 'new',
 2279+ 'transporting',
 2280+ 'transported',
 2281+ 'transportProgress',
 2282+ 'transportWeight',
 2283+ '#mwe-upwiz-progress',
 2284+ gM( 'mwe-upwiz-uploading' ),
 2285+ function( upload ) {
 2286+ upload.start();
 2287+ },
 2288+ function() {
 2289+ allowCloseWindow();
 2290+ $j().notify( gM( 'mwe-upwiz-files-complete' ) );
 2291+ $j( '#mwe-upwiz-stepdiv-file' ).enableNextButton();
 2292+ }
 2293+ );
 2294+ },
 2295+
 2296+
 2297+
 2298+ /**
 2299+ * Occurs whenever we need to update the interface based on how many files there are
 2300+ * Thhere is an uncounted upload, waiting to be used, which has a fileInput which covers the
 2301+ * "add an upload" button. This is absolutely positioned, so it needs to be moved if another upload was removed.
 2302+ * The uncounted upload is also styled differently between the zero and n files cases
 2303+ */
 2304+ updateFileCounts: function() {
 2305+ var _this = this;
 2306+
 2307+ if ( _this.uploads.length ) {
 2308+ $j( '#mwe-upwiz-upload-ctrl' ).removeAttr( 'disabled' );
 2309+ $j( '#proceed' ).show();
 2310+ $j( '#mwe-upwiz-add-file' ).html( gM( 'mwe-upwiz-add-file-n' ) );
 2311+ $j( '#mwe-upwiz-add-file-container' ).removeClass('mwe-upwiz-add-files-0');
 2312+ $j( '#mwe-upwiz-add-file-container' ).addClass('mwe-upwiz-add-files-n');
 2313+ } else {
 2314+ $j( '#mwe-upwiz-upload-ctrl' ).attr( 'disabled', 'disabled' );
 2315+ $j( '#proceed' ).hide();
 2316+ $j( '#mwe-upwiz-add-file' ).html( gM( 'mwe-upwiz-add-file-0' ) );
 2317+ $j( '#mwe-upwiz-add-file-container' ).addClass('mwe-upwiz-add-files-0');
 2318+ $j( '#mwe-upwiz-add-file-container' ).removeClass('mwe-upwiz-add-files-n');
 2319+ }
 2320+
 2321+ if ( _this.uploads.length < _this.maxUploads ) {
 2322+ $j( '#mwe-upwiz-add-file' ).removeAttr( 'disabled' );
 2323+ $j( _this.uploadToAdd.ui.div ).show();
 2324+ _this.uploadToAdd.ui.moveFileInputToCover( '#mwe-upwiz-add-file' );
 2325+ } else {
 2326+ $j( '#mwe-upwiz-add-file' ).attr( 'disabled', true );
 2327+ $j( _this.uploadToAdd.ui.div ).hide();
 2328+ }
 2329+
 2330+
 2331+ },
 2332+
 2333+ /**
 2334+ * Submit all edited details and other metadata
 2335+ * Works just like startUploads -- parallel simultaneous submits with progress bar.
 2336+ */
 2337+ detailsSubmit: function( endCallback ) {
 2338+ var _this = this;
 2339+ // some details blocks cannot be submitted (for instance, identical file hash)
 2340+ _this.removeBlockedDetails();
 2341+
 2342+ // check that it's even possible to submit all
 2343+
 2344+ // remove all controls
 2345+ //$j( '#mwe-upwiz-upload-ctrl' ).hide();
 2346+ //$j( '#mwe-upwiz-add-file' ).hide();
 2347+
 2348+ // remove ability to edit details
 2349+ // maybe add some sort of greyish semi-opaque thing
 2350+
 2351+ // add the upload progress bar, with ETA
 2352+ // add in the upload count
 2353+ _this.makeTransitioner(
 2354+ 'details',
 2355+ 'submitting-details',
 2356+ 'complete',
 2357+ 'detailsProgress',
 2358+ 'detailsWeight',
 2359+ '#mwe-upwiz-macro-progress',
 2360+ gM( 'mwe-upwiz-editing' ),
 2361+ function( upload ) {
 2362+ upload.details.submit();
 2363+ },
 2364+ endCallback
 2365+ );
 2366+ },
 2367+
 2368+ /**
 2369+ * Removes(?) details that we can't edit for whatever reason -- might just advance them to a different state?
 2370+ */
 2371+ removeBlockedDetails: function() {
 2372+
 2373+ },
 2374+
 2375+
 2376+ prefillThanksPage: function() {
 2377+ var _this = this;
 2378+
 2379+ $j( '#mwe-upwiz-thanks' ).append( $j( '<p>' ).append( gM( 'mwe-upwiz-thanks-intro' ) ) );
 2380+ var width = mw.getConfig( 'thumbnailWidth' );
 2381+
 2382+ $j.each( _this.uploads, function(i, upload) {
 2383+ var thanksDiv = $j( '<div class="mwe-upwiz-thanks ui-helper-clearfix" />' );
 2384+ _this.thanksDiv = thanksDiv;
 2385+
 2386+ var thumbnailDiv = $j( '<div></div>' ).addClass( 'mwe-upwiz-thumbnail' );
 2387+ thanksDiv.append( thumbnailDiv );
 2388+ upload.setThumbnail( thumbnailDiv );
 2389+
 2390+ //var thumbTitle = upload.title.replace(/^File/, 'Image'); // XXX is this really necessary?
 2391+ var thumbWikiText = "[[" + upload.title + "|thumb]]";
 2392+
 2393+ thanksDiv.append(
 2394+ $j( '<div class="mwe-upwiz-data"></div>' )
 2395+ .append(
 2396+ $j('<p/>').append(
 2397+ gM( 'mwe-upwiz-thanks-link',
 2398+ $j( '<a />' )
 2399+ .attr( { target: '_new',
 2400+ href: upload.imageinfo.descriptionurl } )
 2401+ .text( upload.title )
 2402+ )
 2403+ ),
 2404+ $j('<p/>').append(
 2405+ gM( 'mwe-upwiz-thanks-wikitext' ),
 2406+ $j( '<br />' ),
 2407+ $j( '<textarea class="mwe-long-textarea" rows="1"/>' )
 2408+ .growTextArea()
 2409+ .append( thumbWikiText )
 2410+ .resizeIfNeeded()
 2411+ ),
 2412+ $j('<p/>').append(
 2413+ gM( 'mwe-upwiz-thanks-url' ),
 2414+ $j( '<br />' ),
 2415+ $j( '<textarea class="mwe-long-textarea" rows="1"/>' )
 2416+ .growTextArea()
 2417+ .append( upload.imageinfo.descriptionurl )
 2418+ .resizeIfNeeded()
 2419+ )
 2420+ )
 2421+ );
 2422+
 2423+ $j( '#mwe-upwiz-thanks' ).append( thanksDiv );
 2424+ } );
 2425+ },
 2426+
 2427+ /**
 2428+ *
 2429+ */
 2430+ pause: function() {
 2431+
 2432+ },
 2433+
 2434+ /**
 2435+ *
 2436+ */
 2437+ stop: function() {
 2438+
 2439+ }
 2440+};
 2441+
 2442+
 2443+mw.UploadWizardDeedPreview = function(upload) {
 2444+ this.upload = upload;
 2445+};
 2446+
 2447+mw.UploadWizardDeedPreview.prototype = {
 2448+ setup: function() {
 2449+ var _this = this;
 2450+ // add a preview on the deeds page
 2451+ var thumbnailDiv = $j( '<div></div>' ).addClass( 'mwe-upwiz-thumbnail' );
 2452+ $j( '#mwe-upwiz-deeds-thumbnails' ).append( thumbnailDiv );
 2453+ _this.upload.setThumbnail( thumbnailDiv, mw.getConfig( 'smallThumbnailWidth' ), 'mwe-upwiz-smallthumbnail' );
 2454+ }
 2455+};
 2456+
 2457+mw.UploadWizardNullDeed = $j.extend( new mw.UploadWizardDeed(), {
 2458+ isReady: function() { return false; },
 2459+
 2460+ setQuantity: function( n ) { return; }
 2461+
 2462+} );
 2463+
 2464+
 2465+
 2466+
 2467+
 2468+/**
 2469+ * @param selector where to put this deed chooser
 2470+ * @param isPlural whether this chooser applies to multiple files (changes messaging mostly)
 2471+ */
 2472+mw.UploadWizardDeedChooser = function( selector ) {
 2473+ var _this = this;
 2474+ _this.selector = selector;
 2475+ _this.deed = mw.UploadWizardNullDeed;
 2476+
 2477+ items = [];
 2478+ $j.each( [ 'ownwork', 'thirdparty' ], function (i, key) {
 2479+ var item =
 2480+ '<div class="mwe-upwiz-macro-deed-' + key + ' mwe-upwiz-deed">'
 2481+ + '<div class="mwe-upwiz-deed-option-title">'
 2482+ + '<span class="mwe-upwiz-deed-header mwe-closed">'
 2483+ + '<a class="mwe-upwiz-deed-header-link mwe-upwiz-deed-name"></a>'
 2484+ + '</span>'
 2485+ + '<span class="mwe-upwiz-deed-header mwe-open" style="display: none;">'
 2486+ + '<span class="mwe-upwiz-deed-name"></span>'
 2487+ + ' <a class="mwe-upwiz-macro-deeds-return">' + gM( 'mwe-upwiz-change' ) + '</a>'
 2488+ + '</span>'
 2489+ + '</div>'
 2490+ + '<div class="mwe-upwiz-deed-form"></div>'
 2491+ + '</div>'
 2492+ items.push(item);
 2493+ } );
 2494+
 2495+ $j( selector ).html( items.join('') );
 2496+
 2497+ $j( '.mwe-upwiz-macro-deeds-return' ).click( function() {
 2498+ _this.choose( mw.UploadWizardNullDeed );
 2499+ _this.showDeedChoice();
 2500+ } );
 2501+
 2502+ _this.choose( mw.UploadWizardNullDeed );
 2503+ _this.showDeedChoice();
 2504+
 2505+ // set up the deed interfaces
 2506+ _this.deeds = {
 2507+ 'ownwork' : _this.setupDeedOwnWork(),
 2508+ 'thirdparty' : _this.setupDeedThirdParty()
 2509+ };
 2510+
 2511+ _this.setQuantity();
 2512+};
 2513+
 2514+
 2515+mw.UploadWizardDeedChooser.prototype = {
 2516+
 2517+ count: 0,
 2518+
 2519+ attach: function( upload ) {
 2520+ var _this = this;
 2521+ upload.deedChooser = _this;
 2522+ _this.count++;
 2523+ _this.setQuantity();
 2524+ },
 2525+
 2526+ detach: function( upload ) {
 2527+ _this.count--;
 2528+ _this.setQuantity();
 2529+ },
 2530+
 2531+ // modify various interface strings depending on singular, multiple deeds
 2532+ setQuantity: function() {
 2533+ var _this = this;
 2534+ mw.log( "setting quantity of deed to " + _this.count );
 2535+ var isPlural = _this.count > 1;
 2536+ $j.each( [ 'ownwork', 'thirdparty' ], function (i, key) {
 2537+
 2538+ // fix the deed title that opens and closes
 2539+ gMkey = isPlural ? key + '-plural' : key;
 2540+ $j( _this.selector )
 2541+ .find( '.mwe-upwiz-macro-deed-' + key)
 2542+ .find( '.mwe-upwiz-deed-name' )
 2543+ .html( gM( 'mwe-upwiz-source-' + gMkey ) );
 2544+
 2545+ // any other internal strings in the deed
 2546+ if ( _this.deeds[key] ) {
 2547+ _this.deeds[key].setQuantity( _this.count );
 2548+ }
 2549+
 2550+ } );
 2551+ $j( _this ).trigger( 'setQuantityEvent' );
 2552+
 2553+ },
 2554+
 2555+ choose: function( deed ) {
 2556+ var _this = this;
 2557+ _this.deed = deed;
 2558+ if ( deed === mw.UploadWizardNullDeed ) {
 2559+ $j( _this ).trigger( 'chooseNullDeed' );
 2560+ //_this.trigger( 'isNotReady' );
 2561+ $j( _this.selector )
 2562+ .find( 'input.mwe-accept-deed' )
 2563+ .attr( 'checked', false )
 2564+ } else {
 2565+ $j( _this ).trigger( 'chooseDeed' );
 2566+ }
 2567+ },
 2568+
 2569+ /**
 2570+ * Go back to original source choice.
 2571+ * Assumed that we are in details mode.
 2572+ */
 2573+ showDeedChoice: function() {
 2574+ var _this = this;
 2575+ $j( _this.selector ).find( '.mwe-upwiz-deed-header.mwe-open' ).hide();
 2576+ $j( _this.selector ).find( '.mwe-upwiz-deed-header.mwe-closed' ).show();
 2577+ $j( _this.selector ).find( '.mwe-upwiz-deed' ).maskSafeShow();
 2578+ $j( _this.selector ).find( '.mwe-upwiz-deed-form' ).maskSafeHide();
 2579+ },
 2580+
 2581+ /**
 2582+ * From the deed choice page, show a particular deed
 2583+ */
 2584+ showDeed: function( deedSelector ) {
 2585+ $j( deedSelector ).find( '.mwe-upwiz-deed-header.mwe-open' ).show();
 2586+ $j( deedSelector ).find( '.mwe-upwiz-deed-header.mwe-closed' ).hide();
 2587+ $j( deedSelector ).siblings().maskSafeHide();
 2588+ $j( deedSelector ).find( '.mwe-upwiz-deed-form' ).maskSafeShow();
 2589+ },
 2590+
 2591+ /**
 2592+ * Set up the form and deed object for the deed option that says these uploads are all the user's own work.
 2593+ */
 2594+ setupDeedOwnWork: function() {
 2595+ mw.log("setupdeed own work");
 2596+ var _this = this;
 2597+
 2598+ var authorInput = $j( '<input />')
 2599+ .attr( { name: "author" } )
 2600+ .addClass( 'mwe-upwiz-sign' );
 2601+
 2602+ var licenseInputDiv = $j( '<div class="mwe-upwiz-deed-license"></div>' );
 2603+ var licenseInput = new mw.UploadWizardLicenseInput( licenseInputDiv );
 2604+ licenseInput.setDefaultValues();
 2605+
 2606+ var ownWorkDeed = $j.extend( new mw.UploadWizardDeed(), {
 2607+
 2608+ licenseInput: licenseInput,
 2609+
 2610+ isReady: function() {
 2611+ return (
 2612+ ( $j( _this.selector ).find( '.mwe-accept-deed' ).is( ':checked' ) !== 0 )
 2613+ &&
 2614+ licenseInput.isSet()
 2615+ );
 2616+ },
 2617+
 2618+ getSourceWikiText: function() {
 2619+ return '{{own}}';
 2620+ },
 2621+
 2622+ getAuthorWikiText: function() {
 2623+ return "[[User:" + mw.getConfig('userName') + '|' + $j( authorInput ).val() + ']]';
 2624+ },
 2625+
 2626+
 2627+ getLicenseWikiText: function() {
 2628+ var wikiText = '{{self';
 2629+ $j.each ( licenseInput.getTemplates(), function( i, template ) {
 2630+ wikiText += '|' + template;
 2631+ } );
 2632+ wikiText += '}}';
 2633+ return wikiText;
 2634+ },
 2635+
 2636+ createInterface: function( deedChooser ) {
 2637+ var _this = this;
 2638+ _this.deedChooser = deedChooser;
 2639+
 2640+ var standardDiv = $j( '<div />' )
 2641+ .append(
 2642+ $j( '<input />')
 2643+ .attr( { type: 'checkbox' } )
 2644+ .click( function() {
 2645+ if ( $j( this ).is( ':checked' ) ) {
 2646+ $j( deedChooser.selector )
 2647+ .find( '.mwe-upwiz-deed-accept-ownwork-custom' )
 2648+ .attr( 'checked', false );
 2649+ _this.licenseInput.setDefaultValues();
 2650+ deedChooser.choose( ownWorkDeed );
 2651+ } else {
 2652+ deedChooser.choose( mw.UploadWizardNullDeed );
 2653+ }
 2654+ } )
 2655+ .addClass( 'mwe-upwiz-deed-accept-ownwork-default mwe-accept-deed mwe-checkbox-hang-indent' ),
 2656+
 2657+ // text added in setQuantit
 2658+ $j( '<p class="mwe-upwiz-source-ownwork-assert mwe-checkbox-hang-indent-text"/>' ),
 2659+
 2660+ $j( '<p />' )
 2661+ .addClass( 'mwe-checkbox-hang-indent-text' )
 2662+ .addClass( 'mwe-small-print' )
 2663+ .html( gM ( 'mwe-upwiz-source-ownwork-assert-note' ) )
 2664+ );
 2665+
 2666+ var toggleDiv = $j('<div />');
 2667+
 2668+ var customDiv = $j('<div/>')
 2669+ .maskSafeHide()
 2670+ .append(
 2671+ $j( '<input />')
 2672+ .attr( { type: 'checkbox' } )
 2673+ .click( function() {
 2674+ if ( $j( this ).is( ':checked' ) ) {
 2675+ $j( deedChooser.selector )
 2676+ .find( '.mwe-upwiz-deed-accept-ownwork-default' )
 2677+ .attr( 'checked', false )
 2678+ deedChooser.choose( ownWorkDeed );
 2679+ } else {
 2680+ deedChooser.choose( mw.UploadWizardNullDeed );
 2681+ }
 2682+ } )
 2683+ .addClass( 'mwe-upwiz-deed-accept-ownwork-custom mwe-accept-deed mwe-checkbox-hang-indent' ),
 2684+ $j( '<p class="mwe-upwiz-source-ownwork-assert-custom mwe-checkbox-hang-indent-text" />' ),
 2685+ licenseInputDiv
 2686+ );
 2687+
 2688+ $j( deedChooser.selector ).find( '.mwe-upwiz-macro-deed-ownwork .mwe-upwiz-deed-form' )
 2689+ .append( $j( '<div class="mwe-upwiz-deed-form-internal" />' )
 2690+ .append( standardDiv,
 2691+ toggleDiv,
 2692+ customDiv )
 2693+ );
 2694+
 2695+
 2696+
 2697+ mw.UploadWizardUtil.makeFadingToggler( standardDiv, toggleDiv, customDiv );
 2698+
 2699+ // if they select 'fewer options', and they have selected the deed in there, we should unselect it
 2700+ $j( toggleDiv ).bind( 'close', function(e) {
 2701+ e.stopPropagation();
 2702+ if ( $j( deedChooser.selector ).find( '.mwe-upwiz-deed-accept-ownwork-custom' ).is( ':checked' ) ) {
 2703+ $j( deedChooser.selector )
 2704+ .find( '.mwe-upwiz-deed-accept-ownwork-custom' )
 2705+ .attr( 'checked', false );
 2706+ deedChooser.choose( mw.UploadWizardNullDeed );
 2707+ }
 2708+ } );
 2709+
 2710+ _this.setQuantity( 1 );
 2711+
 2712+
 2713+ },
 2714+
 2715+
 2716+ setQuantity: function( n ) {
 2717+ var _this = this;
 2718+ var plural = n > 1 ? '-plural' : '';
 2719+ $j( _this.deedChooser.selector )
 2720+ .find( 'p.mwe-upwiz-source-ownwork-assert' )
 2721+ .html( gM( 'mwe-upwiz-source-ownwork-assert' + plural,
 2722+ $j( '<input />' )
 2723+ .attr( { name: 'author' } )
 2724+ .addClass( 'mwe-upwiz-sign' ) ) );
 2725+
 2726+ $j( _this.deedChooser.selector )
 2727+ .find( 'p.mwe-upwiz-source-ownwork-assert-custom' )
 2728+ .html( gM( 'mwe-upwiz-source-ownwork-assert-custom' + plural,
 2729+ '<span class="mwe-custom-author-input"></span>' ) );
 2730+
 2731+ // have to add the author input this way -- gM() will flatten it to a string and we'll lose it as a dom object
 2732+ $j( _this.deedChooser.selector ).find( '.mwe-custom-author-input' ).append( authorInput );
 2733+
 2734+ // synchronize both username signatures
 2735+ // set initial value to configured username
 2736+ // if one changes all the others change (keyup event)
 2737+ //
 2738+ // also set tooltips ( the title, tipsy() )
 2739+ $j( _this.deedChooser.selector ).find( '.mwe-upwiz-sign' )
 2740+ .attr( {
 2741+ title: gM( 'mwe-upwiz-tooltip-sign' ),
 2742+ value: mw.getConfig( 'userName' )
 2743+ } )
 2744+ .tipsy( { trigger: 'focus', gravity: 'w' } )
 2745+ .keyup( function() {
 2746+ var thisInput = this;
 2747+ var thisVal = $j( thisInput ).val();
 2748+ $j.each( $j( '.mwe-upwiz-sign' ), function( i, input ) {
 2749+ if (thisInput !== input) {
 2750+ $j( input ).val( thisVal );
 2751+ }
 2752+ } );
 2753+ } );
 2754+
 2755+
 2756+ }
 2757+ } );
 2758+
 2759+ ownWorkDeed.createInterface( _this );
 2760+
 2761+ $j( _this.selector ).find( '.mwe-upwiz-macro-deed-ownwork .mwe-upwiz-deed-header-link').click(
 2762+ function() {
 2763+ _this.showDeed( $j( _this.selector ).find( '.mwe-upwiz-macro-deed-ownwork' ) );
 2764+ }
 2765+ );
 2766+
 2767+ return ownWorkDeed;
 2768+
 2769+ },
 2770+
 2771+ setupDeedThirdParty: function() {
 2772+ var _this = this;
 2773+ var sourceInput = $j('<textarea class="mwe-source mwe-long-textarea" name="source" rows="1" cols="40"></textarea>' )
 2774+ .growTextArea()
 2775+ .attr( 'title', gM( 'mwe-upwiz-tooltip-source' ) )
 2776+ .tipsy( { trigger: 'focus', gravity: 'w' } );
 2777+ var authorInput = $j('<textarea class="mwe-author mwe-long-textarea" name="author" rows="1" cols="40"></textarea>' )
 2778+ .growTextArea()
 2779+ .attr( 'title', gM( 'mwe-upwiz-tooltip-author' ) )
 2780+ .tipsy( { trigger: 'focus', gravity: 'w' } );
 2781+ licenseInputDiv = $j( '<div class="mwe-upwiz-deed-license"></div>' );
 2782+ licenseInput = new mw.UploadWizardLicenseInput( licenseInputDiv );
 2783+ licenseInput.setDefaultValues();
 2784+
 2785+ var thirdPartyDeed = $j.extend( new mw.UploadWizardDeed(), {
 2786+ sourceInput: sourceInput,
 2787+ authorInput: authorInput,
 2788+ licenseInput: licenseInput,
 2789+
 2790+ isReady: function() {
 2791+ return (! mw.isEmpty( $j( this.sourceInput ).val() ) )
 2792+ && (! mw.isEmpty( $j( this.authorInput ).val() ) )
 2793+ && this.licenseInput.isSet()
 2794+ },
 2795+
 2796+ createInterface: function( deedChooser ) {
 2797+ var _this = this;
 2798+ _this.deedChooser = deedChooser;
 2799+
 2800+ $j( deedChooser.selector ).find( '.mwe-upwiz-macro-deed-thirdparty .mwe-upwiz-deed-form' ).append(
 2801+ $j( '<div class="mwe-upwiz-deed-form-internal"/>' )
 2802+ .append(
 2803+ $j( '<div class="mwe-upwiz-source-thirdparty-custom-plural-intro" />' ),
 2804+ $j( '<div />' )
 2805+ .addClass( "mwe-upwiz-thirdparty-fields" )
 2806+ .append( $j( '<label />' )
 2807+ .attr( { 'for' : 'source' } )
 2808+ .text( gM( 'mwe-upwiz-source' ) ) )
 2809+ .append( sourceInput ),
 2810+ $j( '<div />' )
 2811+ .addClass( "mwe-upwiz-thirdparty-fields" )
 2812+ .append( $j( '<label />' )
 2813+ .attr( { 'for' : 'author' } )
 2814+ .text( gM( 'mwe-upwiz-author' ) ) )
 2815+ .append( authorInput ),
 2816+ $j( '<div />' ).addClass( 'mwe-upwiz-thirdparty-license' )
 2817+ .text( gM( 'mwe-upwiz-source-thirdparty-license' ) ),
 2818+ licenseInputDiv
 2819+ )
 2820+ );
 2821+
 2822+ thirdPartyDeed.setQuantity( 1 );
 2823+ },
 2824+
 2825+
 2826+ setQuantity: function( n ) {
 2827+ var _this = this;
 2828+ $j( _this.deedChooser.selector )
 2829+ .find( 'div.mwe-upwiz-source-thirdparty-custom-plural-intro' )
 2830+ .html( n > 1 ? gM( 'mwe-upwiz-source-thirdparty-custom-plural-intro' ) : '' );
 2831+ }
 2832+
 2833+
 2834+ } );
 2835+
 2836+ thirdPartyDeed.createInterface( _this );
 2837+
 2838+ // set up the header
 2839+ $j( _this.selector ).find( '.mwe-upwiz-macro-deed-thirdparty .mwe-upwiz-deed-header-link').click(
 2840+ function() {
 2841+ _this.showDeed( $j( _this.selector ).find( '.mwe-upwiz-macro-deed-thirdparty' ) );
 2842+ _this.choose( thirdPartyDeed );
 2843+ }
 2844+ );
 2845+
 2846+ return thirdPartyDeed;
 2847+ }
 2848+};
 2849+
 2850+
 2851+
 2852+/**
 2853+ * Miscellaneous utilities
 2854+ */
 2855+mw.UploadWizardUtil = {
 2856+
 2857+ /**
 2858+ * make a "more" options toggle which makes the standard div fade when 'more' is open
 2859+ * @param standardDiv the div representing the standard options
 2860+ * @param toggleDiv the div which has the control to open and shut custom options
 2861+ * @param customDiv the div containing the custom options
 2862+ */
 2863+ makeFadingToggler: function( standardDiv, toggleDiv, moreDiv ) {
 2864+ return mw.UploadWizardUtil._makeToggler( standardDiv, toggleDiv, moreDiv, true );
 2865+ },
 2866+
 2867+
 2868+ /**
 2869+ * make a standard toggler that just opens up a new panel
 2870+ * @param toggle div -- where to put the 'more/fewer options' button
 2871+ * @param moreDiv -- the div to hide or show
 2872+ */
 2873+ makeMoreToggler: function( toggleDiv, moreDiv ) {
 2874+ return mw.UploadWizardUtil._makeToggler( null, toggleDiv, moreDiv, false );
 2875+ },
 2876+
 2877+
 2878+ /**
 2879+ * There is a common pattern of having standard options, and then a "more options" panel which
 2880+ * disables the standard options panel.
 2881+ *
 2882+ * We do not do anything to disable the inputs on either panel. So far, we've implemented these
 2883+ * in a way that the 'custom' panel is always controlling everything, but the standard panel just
 2884+ * enters values into the custom one behind the scenes.
 2885+ *
 2886+ * @param standardDiv the div representing the standard options
 2887+ * @param toggleDiv the div which has the control to open and shut custom options
 2888+ * @param moreDiv the div containing the custom options
 2889+ * @param fade whether to fade standardDiv when moreDiv is open
 2890+ */
 2891+ _makeToggler: function ( standardDiv, toggleDiv, moreDiv, fade ) {
 2892+ if (fade === undefined) {
 2893+ fade = false;
 2894+ }
 2895+
 2896+ var icon = $j( '<div class="ui-icon ui-icon-triangle-1-e" style="display: inline-block; margin-top: 3px;">' );
 2897+ var text = $j( '<span>' ).append( gM( 'mwe-upwiz-more-options' ) );
 2898+ var toggle = function() {
 2899+ var open = ! ( $j( this ).data( 'open' ) ) ;
 2900+ $j( this ).data( 'open', open );
 2901+ // on toggle:
 2902+ if ( open ) {
 2903+ // set out class to show the "close" message
 2904+ text.text( gM( 'mwe-upwiz-fewer-options' ) );
 2905+ icon.removeClass( "ui-icon-triangle-1-e" )
 2906+ .addClass( "ui-icon-triangle-1-s" );
 2907+ moreDiv.maskSafeShow();
 2908+ if (fade) {
 2909+ standardDiv.mask();
 2910+ }
 2911+ } else {
 2912+ text.text( gM( 'mwe-upwiz-more-options' ) );
 2913+ icon.removeClass( "ui-icon-triangle-1-s" )
 2914+ .addClass( "ui-icon-triangle-1-e" )
 2915+ moreDiv.maskSafeHide();
 2916+ if (fade) {
 2917+ standardDiv.unmask();
 2918+ }
 2919+ $j( this ).trigger( 'close' );
 2920+ }
 2921+ };
 2922+
 2923+ $j( toggleDiv )
 2924+ .addClass( 'mwe-more-options' )
 2925+ .append( $j( '<a />' )
 2926+ .click( toggle )
 2927+ .data( 'open', false )
 2928+ .append( icon, text ) );
 2929+
 2930+ },
 2931+
 2932+ /**
 2933+ * remove an item from an array. Tests for === identity to remove the item
 2934+ * XXX the entire rationale for this file may be wrong.
 2935+ * XXX The jQuery way would be to query the DOM for objects, not to keep a separate array hanging around
 2936+ * @param items the array where we want to remove an item
 2937+ * @param item the item to remove
 2938+ */
 2939+ removeItem: function( items, item ) {
 2940+ for ( var i = 0; i < items.length; i++ ) {
 2941+ if ( items[i] === item ) {
 2942+ items.splice( i, 1 );
 2943+ break;
 2944+ }
 2945+ }
 2946+ },
 2947+
 2948+ /**
 2949+ * Capitalise first letter and replace spaces by underscores
 2950+ * @param filename (basename, without directories)
 2951+ * @return typical title as would appear on MediaWiki
 2952+ */
 2953+ pathToTitle: function ( filename ) {
 2954+ return mw.ucfirst( filename.replace(/ /g, '_' ) );
 2955+ },
 2956+
 2957+ /**
 2958+ * Capitalise first letter and replace underscores by spaces
 2959+ * @param title typical title as would appear on MediaWiki
 2960+ * @return plausible local filename, with spaces changed to underscores.
 2961+ */
 2962+ titleToPath: function ( title ) {
 2963+ return mw.ucfirst( title.replace(/_/g, ' ' ) );
 2964+ },
 2965+
 2966+ /**
 2967+ * Slice extension off a path
 2968+ * We assume that extensions are 1-4 characters in length
 2969+ * @param path to file, like "foo/bar/baz.jpg"
 2970+ * @return extension, like ".jpg" or undefined if it doesn't look lke an extension.
 2971+ */
 2972+ getExtension: function( path ) {
 2973+ var extension = undefined;
 2974+ var idx = path.lastIndexOf( '.' );
 2975+ if (idx > 0 && ( idx > ( path.length - 5 ) ) && ( idx < ( path.length - 1 ) ) ) {
 2976+ extension = path.substr( idx + 1 ).toLowerCase();
 2977+ }
 2978+ return extension;
 2979+ },
 2980+
 2981+ /**
 2982+ * Last resort to guess a proper extension
 2983+ */
 2984+ mimetypeToExtension: {
 2985+ 'image/jpeg': 'jpg',
 2986+ 'image/gif': 'gif'
 2987+ // fill as needed
 2988+ }
 2989+
 2990+
 2991+};
 2992+
 2993+/**
 2994+ * Upper-case the first letter of a string. XXX move to common library
 2995+ * @param string
 2996+ * @return string with first letter uppercased.
 2997+ */
 2998+mw.ucfirst = function( s ) {
 2999+ return s.substring(0,1).toUpperCase() + s.substr(1);
 3000+};
 3001+
 3002+
 3003+/**
 3004+ * jQuery extension. Makes a textarea automatically grow if you enter overflow
 3005+ * (This feature was in the old Commons interface with a confusing arrow icon; it's nicer to make it automatic.)
 3006+ */
 3007+jQuery.fn.growTextArea = function( options ) {
 3008+
 3009+ // this is a jquery-style object
 3010+
 3011+ // in MSIE, this makes it possible to know what scrollheight is
 3012+ // Technically this means text could now dangle over the edge,
 3013+ // but it shouldn't because it will always grow to accomodate very quickly.
 3014+
 3015+ if ($j.msie) {
 3016+ this.each( function(i, textArea) {
 3017+ textArea.style.overflow = 'visible';
 3018+ } );
 3019+ }
 3020+
 3021+ this.resizeIfNeeded = function() {
 3022+ // this is the dom element
 3023+ while (this.scrollHeight > this.offsetHeight) {
 3024+ this.rows++;
 3025+ }
 3026+ return this;
 3027+ };
 3028+
 3029+ this.addClass( 'mwe-grow-textarea' );
 3030+
 3031+ this.change(this.resizeIfNeeded);
 3032+ this.keyup(this.resizeIfNeeded);
 3033+
 3034+
 3035+ return this;
 3036+};
 3037+
 3038+jQuery.fn.mask = function( options ) {
 3039+
 3040+ // intercept clicks...
 3041+ // Note: the size of the div must be obtainable. Hence, this cannot be a div without layout (e.g. display:none).
 3042+ // some of this is borrowed from http://code.google.com/p/jquery-loadmask/ , but simplified
 3043+ $j.each( this, function( i, el ) {
 3044+
 3045+ if ( ! $j( el ).data( 'mask' ) ) {
 3046+
 3047+
 3048+ //fix for z-index bug with selects in IE6
 3049+ if ( $j.browser.msie && $j.browser.version.substring(0,1) === '6' ){
 3050+ el.find( "select" ).addClass( "masked-hidden" );
 3051+ }
 3052+
 3053+ var mask = $j( '<div />' )
 3054+ .css( { 'position' : 'absolute',
 3055+ 'top' : '0px',
 3056+ 'left' : '0px',
 3057+ 'width' : el.offsetWidth + 'px',
 3058+ 'height' : el.offsetHeight + 'px',
 3059+ 'z-index' : 100 } )
 3060+ .click( function( e ) { e.stopPropagation(); } );
 3061+
 3062+ $j( el ).css( { 'position' : 'relative' } )
 3063+ .fadeTo( 'fast', 0.5 )
 3064+ .append( mask )
 3065+ .data( 'mask', mask );
 3066+
 3067+ //auto height fix for IE -- not sure about this, i think offsetWidth + Height is a better solution. Test!
 3068+ /*
 3069+ if( $j.browser.msie ) {
 3070+ mask.height(el.height() + parseInt(el.css("padding-top")) + parseInt(el.css("padding-bottom")));
 3071+ mask.width(el.width() + parseInt(el.css("padding-left")) + parseInt(el.css("padding-right")));
 3072+ }
 3073+ */
 3074+
 3075+ }
 3076+
 3077+ // XXX bind to a custom event in case the div size changes : ?
 3078+
 3079+ } );
 3080+
 3081+ return this;
 3082+
 3083+};
 3084+
 3085+jQuery.fn.unmask = function( options ) {
 3086+
 3087+ $j.each( this, function( i, el ) {
 3088+ if ( $j( el ).data( 'mask' ) ) {
 3089+ var mask = $j( el ).data( 'mask' );
 3090+ $j( el ).removeData( 'mask' ); // from the data
 3091+ mask.remove(); // from the DOM
 3092+ $j( el ).fadeTo( 'fast', 1.0 );
 3093+ }
 3094+ } );
 3095+
 3096+
 3097+ return this;
 3098+};
 3099+
 3100+
 3101+/**
 3102+ * Safe hide and show
 3103+ * Rather than use display: none, this collapses the divs to zero height
 3104+ * This is good because then the elements in the divs still have layout and we can do things like mask and unmask (above)
 3105+ * XXX may be obsolete as we are not really doing this any more
 3106+ */
 3107+
 3108+jQuery.fn.maskSafeHide = function( options ) {
 3109+ return this.css( { 'height' : '0px', 'overflow' : 'hidden' } );
 3110+};
 3111+
 3112+// XXX check overflow properties, is auto/auto not the right thing?
 3113+// may be causing scrollbar to appear when div changes size
 3114+jQuery.fn.maskSafeShow = function( options ) {
 3115+ return this.css( { 'height' : 'auto', 'overflow' : 'visible' } );
 3116+};
 3117+
 3118+
 3119+( function( $j ) {
 3120+
 3121+ /**
 3122+ * Prevent the closing of a window with a confirm message (the onbeforeunload event seems to
 3123+ * work in most browsers
 3124+ * e.g.
 3125+ * var allowCloseWindow = jQuery().preventCloseWindow( { message: "Don't go away!" } );
 3126+ * // ... do stuff that can't be interrupted ...
 3127+ * allowCloseWindow();
 3128+ *
 3129+ * @param options object which should have a message string, already internationalized
 3130+ * @return closure execute this when you want to allow the user to close the window
 3131+ */
 3132+ $j.fn.preventCloseWindow = function( options ) {
 3133+ if ( typeof options === 'undefined' ) {
 3134+ options = {};
 3135+ }
 3136+
 3137+ if ( typeof options.message === 'undefined' ) {
 3138+ options.message = 'Are you sure you want to close this window?';
 3139+ }
 3140+
 3141+ $j( window ).unload( function() {
 3142+ return options.message;
 3143+ } );
 3144+
 3145+ return function() {
 3146+ $j( window ).removeAttr( 'unload' );
 3147+ };
 3148+
 3149+ };
 3150+
 3151+
 3152+ $j.fn.notify = function ( message ) {
 3153+ // could do something here with Chrome's in-browser growl-like notifications.
 3154+ // play a sound?
 3155+ // if the current tab does not have focus, use an alert?
 3156+ // alert( message );
 3157+ };
 3158+
 3159+ $j.fn.enableNextButton = function() {
 3160+ this.find( '.mwe-upwiz-button-next' )
 3161+ .removeAttr( 'disabled' );
 3162+ // .effect( 'pulsate', { times: 3 }, 1000 );
 3163+ };
 3164+
 3165+ $j.fn.disableNextButton = function() {
 3166+ this.find( '.mwe-upwiz-button-next' )
 3167+ .attr( 'disabled', true );
 3168+ }
 3169+
 3170+
 3171+} )( jQuery );
Property changes on: trunk/extensions/UploadWizard/UploadWizardJsModule/mw.UploadWizard.js
___________________________________________________________________
Name: svn:eol-style
13172 + native
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/mw.DestinationChecker.js
@@ -0,0 +1,207 @@
 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.getLocalApiUrl();
 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+ if ( data.query.normalized ) {
 170+ var ntitle = data.query.normalized[0].to;
 171+ } else {
 172+ var ntitle = data.query.pages[ page_id ].title
 173+ }
 174+
 175+ var img = data.query.pages[ page_id ].imageinfo[0];
 176+
 177+ result = {
 178+ isUnique: false,
 179+ img: img,
 180+ title: ntitle,
 181+ href : img.descriptionurl
 182+ };
 183+
 184+ break;
 185+ }
 186+ }
 187+
 188+ if ( result !== undefined ) {
 189+ _this.cachedResult[name] = result;
 190+ _this.processResult( result );
 191+ }
 192+ } );
 193+ }
 194+
 195+};
 196+
 197+
 198+/**
 199+ * jQuery extension to make a field upload-checkable
 200+ */
 201+( function ( $ ) {
 202+ $.fn.destinationChecked = function( options ) {
 203+ var _this = this;
 204+ options.selector = _this;
 205+ new mw.DestinationChecker( options );
 206+ return _this;
 207+ };
 208+} )( jQuery );
Property changes on: trunk/extensions/UploadWizard/UploadWizardJsModule/mw.DestinationChecker.js
___________________________________________________________________
Name: svn:eol-style
1209 + native
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/images/black-angle.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/UploadWizard/UploadWizardJsModule/images/black-angle.gif
___________________________________________________________________
Name: svn:mime-type
2210 + application/octet-stream
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/images/grey-angle.gif
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/UploadWizard/UploadWizardJsModule/images/grey-angle.gif
___________________________________________________________________
Name: svn:mime-type
3211 + application/octet-stream
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/css/uploadWizard.css
@@ -0,0 +1,498 @@
 2+form.mwe-upwiz-form {
 3+ display: inline;
 4+}
 5+
 6+#upload-wizard {
 7+ margin-top: 18px;
 8+}
 9+
 10+/*
 11+.upload-section {
 12+ padding: 1em;
 13+ margin-bottom: 0.5em;
 14+ margin-top: 0.5em;
 15+ border: 1px solid #e0e0e0;
 16+}
 17+*/
 18+
 19+#mwe-upwiz-steparrows {
 20+}
 21+
 22+#mwe-upwiz-content {
 23+}
 24+
 25+.mwe-upwiz-clearing {
 26+ clear: left;
 27+ width: 100%;
 28+}
 29+
 30+#mwe-upwiz-steparrows ul {
 31+ list-style-type: none;
 32+ list-style-image: none;
 33+ padding: 0px;
 34+ margin: 0px;
 35+ position: relative;
 36+}
 37+
 38+#mwe-upwiz-steparrows ul li {
 39+ color: #999999;
 40+ float: left;
 41+ padding: 0px;
 42+ margin: 0px;
 43+}
 44+
 45+#mwe-upwiz-steparrows ul li span {
 46+ padding-top: 2px;
 47+ padding-bottom: 4px;
 48+}
 49+
 50+#mwe-upwiz-steparrows ul li span.mwe-arrow-text {
 51+ border-color: #e0e0e0;
 52+ border-style: solid;
 53+ border-width: 1px 0px 1px 0px;
 54+ padding-right: 10px;
 55+ padding-left: 24px;
 56+ z-index: 1;
 57+}
 58+
 59+#mwe-upwiz-steparrows ul li span.mwe-arrow {
 60+ position: relative;
 61+ left: 21px;
 62+ padding-right: 12px;
 63+ z-index: 2;
 64+ background: url('../images/grey-angle.gif') no-repeat right center;
 65+}
 66+
 67+#mwe-upwiz-steparrows ul li.mwe-upwiz-step-highlight span.mwe-arrow {
 68+ background: url('../images/black-angle.gif') no-repeat right center;
 69+}
 70+
 71+#mwe-upwiz-steparrows ul li.mwe-upwiz-step-highlight span.mwe-arrow-text {
 72+ border-color: #000000;
 73+}
 74+
 75+#mwe-upwiz-steparrows ul li.mwe-upwiz-step-highlight span.mwe-arrow-text {
 76+ padding-left: 12px;
 77+ font-weight: bold;
 78+ color: black;
 79+ border-left: 1px solid;
 80+}
 81+
 82+#mwe-upwiz-content {
 83+ padding: 1em;
 84+}
 85+
 86+.mwe-upwiz-add-files-0 {
 87+ margin-top: 20px;
 88+ text-align: center;
 89+ font-size: 16px;
 90+}
 91+
 92+#mwe-upwiz-add-file {
 93+}
 94+
 95+/* NOT a pseudoclass */
 96+#mwe-upwiz-add-file.hover {
 97+ text-decoration: underline;
 98+}
 99+
 100+/* perhaps a general class for links that are actually "buttons" */
 101+#mwe-upwiz-add-file, .mwe-upwiz-remove, .mwe-upwiz-more-options {
 102+ cursor: pointer;
 103+}
 104+
 105+/* CSS styling hack for file inputs - http://www.quirksmode.org/dom/inputfile.html */
 106+.mwe-upwiz-file-ctrl-container {
 107+ position: absolute;
 108+ overflow: hidden;
 109+}
 110+
 111+.mwe-upwiz-file-input, .mwe-upwiz-visible-file {
 112+ cursor: pointer;
 113+}
 114+
 115+/* file inputs are freakishly large to overflow the containing div -- we get a div
 116+ that can act as a more styleable single-click file input */
 117+.mwe-upwiz-file-input, .disabler {
 118+ font-size: 100px;
 119+ -moz-opacity: 0.3;
 120+ filter:alpha(opacity: 0);
 121+ opacity: 0;
 122+ z-index: 2;
 123+}
 124+
 125+.mwe-upwiz-file.filled, #mwe-upwiz-upload-ctrls {
 126+ padding: 4px;
 127+}
 128+
 129+.mwe-upwiz-file.filled {
 130+ height: 24px;
 131+ border-bottom: 1px solid #e0e0e0;
 132+}
 133+
 134+.mwe-upwiz-file-indicator, .mwe-upwiz-count {
 135+ float: left;
 136+ width: 92px;
 137+ margin-left: 8px;
 138+ text-align: center;
 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-visible-file, .mwe-upwiz-progress-bar-etr {
 148+ width: 300px;
 149+ height: 18px;
 150+ padding-left: 5px;
 151+}
 152+
 153+#mwe-upwiz-add-file-container.mwe-upwiz-add-files-n, .mwe-upwiz-visible-file {
 154+ float: left;
 155+}
 156+
 157+.mwe-upwiz-visible-file {
 158+ white-space: nowrap;
 159+ overflow: hidden;
 160+
 161+ background: white;
 162+
 163+ border-style: solid;
 164+ border-top-width: 2px;
 165+ border-left-width: 2px;
 166+ border-right-width: 1px;
 167+ border-bottom-width: 1px;
 168+ border-color: white;
 169+ /* border: 1px solid blue; XXX just to make it visible for now */
 170+}
 171+
 172+.mwe-upwiz-visible-file.hover {
 173+ background: white;
 174+ border-color: #0645ad;
 175+}
 176+
 177+/* XXX we probably have a standard for this */
 178+.helper {
 179+ color: #cccccc; /* or whatever we do for greyed out text */
 180+ font-variant: italic, oblique;
 181+ text-align: center;
 182+}
 183+
 184+
 185+#mwe-upwiz-files {
 186+ margin-left: 50px;
 187+ margin-top: 20px;
 188+}
 189+
 190+.mwe-upwiz-file {
 191+ width: 410px;
 192+}
 193+
 194+#mwe-upwiz-upload-ctrls {
 195+ margin-top: 15px;
 196+
 197+}
 198+
 199+#mwe-upwiz-add-file-container {
 200+ /* empty; this changes a lot */
 201+}
 202+
 203+.mwe-upwiz-remove {
 204+ padding: 5px;
 205+ cursor: pointer;
 206+ text-align: center;
 207+}
 208+
 209+a.mwe-upwiz-remove:link,
 210+a.mwe-upwiz-remove:active,
 211+a.mwe-upwiz-remove:visited {
 212+ color: #999999;
 213+ text-decoration: none;
 214+}
 215+
 216+a.mwe-upwiz-remove:hover {
 217+ color: #ff0000;
 218+ text-decoration: none;
 219+ cursor: pointer;
 220+}
 221+
 222+.mwe-upwiz-remove-desc {
 223+ vertical-align: top;
 224+}
 225+
 226+a[disabled=true] {
 227+ color: #999999;
 228+ text-decoration: none;
 229+ cursor: default;
 230+}
 231+
 232+a[disabled=true]:hover {
 233+ text-decoration: none;
 234+}
 235+
 236+.mwe-upwiz-status-progress {
 237+ font-weight: bold;
 238+ color: #ff9900;
 239+}
 240+
 241+/* XXX add background of checkmark, with left-padding offset */
 242+.mwe-upwiz-status-completed {
 243+ font-weight: bold;
 244+ color: #009900;
 245+}
 246+
 247+.mwe-upwiz-progress-bar-etr {
 248+ float: left;
 249+}
 250+
 251+.mwe-upwiz-etr {
 252+ text-align: center;
 253+}
 254+
 255+
 256+.mwe-upwiz-upload-warning {
 257+ background: #ffffe0;
 258+}
 259+
 260+.mwe-upwiz-details-error {
 261+ display: none;
 262+ background: #ffffe0;
 263+}
 264+
 265+.mwe-upwiz-thumbnail {
 266+ width: 120px; /* XXX should be same as configured thumbnail width in uploadWizard.js */
 267+ float: left;
 268+ margin-bottom: 12px;
 269+ margin-right: 12px;
 270+}
 271+
 272+#mwe-upwiz-deeds-thumbnails {
 273+ text-align: center;
 274+ vertical-align: middle;
 275+}
 276+
 277+.mwe-upwiz-smallthumbnail {
 278+ width: 60px; /* XXX should be same as configured small thumbnail width in uploadWizard.js */
 279+ margin: 6px;
 280+}
 281+
 282+.mwe-upwiz-data {
 283+ float: left;
 284+}
 285+
 286+
 287+/* XXX add spinner gif instead, maybe */
 288+.busy {
 289+ background: yellow;
 290+}
 291+
 292+.mwe-upwiz-stepdiv {
 293+ height: 0px;
 294+ overflow: hidden;
 295+}
 296+
 297+#mwe-upwiz-stepdiv-file {
 298+ width: 550px;
 299+}
 300+
 301+
 302+/* XXX this has to be 'centered' beneath the 'x's -- this is about right for english but we need a more
 303+ robust layout strategy for i18n */
 304+.proceed {
 305+ text-align: right;
 306+ width: 410px;
 307+}
 308+
 309+.shim {
 310+ float:right;
 311+ width: 1px;
 312+}
 313+
 314+.clearShim {
 315+ clear: both;
 316+ height: 1px;
 317+ overflow: hidden;
 318+}
 319+
 320+.mwe-checkbox-hang-indent {
 321+ float: left;
 322+ width: 24px;
 323+ margin-top: 6px;
 324+}
 325+
 326+.mwe-checkbox-hang-indent-text {
 327+ margin-left: 24px;
 328+}
 329+
 330+.mwe-small-print {
 331+ font-size: xx-small;
 332+}
 333+
 334+.mwe-upwiz-deed {
 335+ margin-left: 24px;
 336+}
 337+
 338+.mwe-upwiz-deed-name {
 339+ font-weight: bold;
 340+}
 341+
 342+.mwe-more-options, .mwe-fewer-options, .mwe-upwiz-macro-deeds-return, .mwe-upwiz-deed-header-link {
 343+ cursor: pointer;
 344+}
 345+
 346+.mwe-more-options, .mwe-fewer-options {
 347+ padding-bottom: 4px;
 348+}
 349+
 350+.mwe-more-options a:hover, .mwe-fewer-options a:hover {
 351+ text-decoration: none;
 352+}
 353+
 354+
 355+.mwe-upwiz-deed-license {
 356+ margin-left: 24px;
 357+}
 358+
 359+#mwe-upwiz-macro-deeds {
 360+ margin-top: 12px;
 361+ margin-bottom: 24px;
 362+}
 363+
 364+#mwe-upwiz-macro-files {
 365+ margin-top: 12px;
 366+}
 367+.mwe-upwiz-info-file {
 368+ border-bottom: 1px solid #e0e0e0;
 369+ margin-bottom: 12px;
 370+ padding-bottom: 12px;
 371+ width: 660px;
 372+}
 373+
 374+
 375+.mwe-upwiz-details-input {
 376+ width: 400px;
 377+ float: left;
 378+}
 379+
 380+.mwe-upwiz-desc-lang-select {
 381+ width: 130px;
 382+ font-family: sans-serif;
 383+ font-size: x-small;
 384+}
 385+
 386+.mwe-upwiz-desc-lang-container {
 387+ float: left;
 388+}
 389+
 390+.mwe-upwiz-desc-lang-text {
 391+ width: 240px;
 392+ overflow: hidden;
 393+ font-family: sans-serif; /* XXX is this right? */
 394+ font-size: x-small;
 395+}
 396+
 397+.mwe-upwiz-details-descriptions-add {
 398+ margin-bottom: 12px;
 399+ margin-left: 100px;
 400+}
 401+
 402+.mwe-grow-textarea, .mwe-long-textarea {
 403+ overflow: hidden;
 404+ font-family: sans-serif; /* XXX is this right? */
 405+ font-size: x-small;
 406+}
 407+
 408+.mwe-long-textarea {
 409+ width: 370px;
 410+}
 411+
 412+fieldset .mwe-long-textarea {
 413+ width: 280px;
 414+}
 415+
 416+
 417+
 418+.mwe-upwiz-details-label {
 419+ width: 100px;
 420+ float: left;
 421+}
 422+
 423+.mwe-upwiz-details-label-input {
 424+
 425+}
 426+
 427+.mwe-upwiz-details-filename. {
 428+ overflow: hidden;
 429+ width: 350px;
 430+}
 431+
 432+.mwe-upwiz-other-textarea {
 433+ width: 468px;
 434+}
 435+
 436+fieldset.mwe-fieldset {
 437+ width: 450px;
 438+ border: 1px solid #cccccc;
 439+ padding-left: 12px;
 440+ padding-right: 12px;
 441+}
 442+
 443+legend.mwe-legend {
 444+ padding: 0.5em 0.5em 0.5em 0.33em;
 445+ color: #666666;
 446+}
 447+
 448+.masked-hidden {
 449+ visibility: hidden !important;
 450+}
 451+
 452+.mwe-upwiz-thirdparty-fields label {
 453+ width: 100px;
 454+ display: inline-block;
 455+}
 456+
 457+.mwe-upwiz-thirdparty-fields textarea {
 458+ margin: 5px 0px -3px 5px;
 459+}
 460+
 461+.mwe-upwiz-thirdparty-license {
 462+ margin-top: 8px;
 463+}
 464+
 465+.mwe-upwiz-deed-form-internal {
 466+ padding: 5px 0px 20px 20px;
 467+}
 468+
 469+
 470+.mwe-upwiz-copyright-info label {
 471+ display: inline-block;
 472+ padding-left: 15px;
 473+ text-indent: -15px;
 474+ margin-right: 15px;
 475+}
 476+
 477+/* the hanging indent checkboxes */
 478+.mwe-upwiz-copyright-info input.mwe-accept-deed {
 479+ width: 13px;
 480+ height: 13px;
 481+ padding: 0;
 482+ margin: 0px 3px 0px 0px;
 483+ vertical-align: bottom;
 484+ position: relative;
 485+ top: -1px;
 486+}
 487+
 488+
 489+.mwe-upwiz-custom-deed {
 490+ margin-top: 5px;
 491+}
 492+
 493+.mwe-upwiz-buttons {
 494+ margin-top: 10px;
 495+ padding-top: 10px;
 496+ border-top: 1px solid #e0e0e0;
 497+ text-align: right; /* works for now, only one 'next' button */
 498+}
 499+
Property changes on: trunk/extensions/UploadWizard/UploadWizardJsModule/css/uploadWizard.css
___________________________________________________________________
Name: svn:eol-style
1500 + native
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/UploadWizard.i18n.php
@@ -0,0 +1,155 @@
 2+<?php
 3+/*
 4+ * Internationalisation for UploadWizard
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ */
 9+
 10+$messages = array();
 11+$messages['en'] = array(
 12+ 'mwe-code-unknown' => 'Unknown language',
 13+ 'mwe-loading-upwiz' => 'Loading upload wizard',
 14+ 'mwe-upwiz-tab-file' => '1. Upload your files',
 15+ 'mwe-upwiz-tab-details' => '2. Add licenses and descriptions',
 16+ 'mwe-upwiz-tab-thanks' => '3. Use your files',
 17+ 'mwe-upwiz-intro' => 'Welcome to Wikimedia Commons, a repository of images, sounds, and movies that anyone can freely download and use. Add to humanity\'s knowledge by uploading files that could be used for an educational purpose.',
 18+ 'mwe-upwiz-add-file-n' => 'Add another file',
 19+ 'mwe-upwiz-add-file-0' => 'Click here to add a file for upload',
 20+ 'mwe-upwiz-browse' => 'Browse...',
 21+ 'mwe-upwiz-transported' => 'OK',
 22+ 'mwe-upwiz-click-here' => 'Click here to select a file',
 23+ 'mwe-upwiz-uploading' => 'uploading...',
 24+ 'mwe-upwiz-editing' => 'editing...',
 25+ 'mwe-upwiz-remove-upload' => 'Remove this file from the list of files to upload',
 26+ 'mwe-upwiz-remove-description' => 'Remove this description',
 27+ 'mwe-upwiz-upload' => 'Upload',
 28+ 'mwe-upwiz-upload-count' => '$1 of $2 files uploaded',
 29+ 'mwe-upwiz-progressbar-uploading' => 'uploading',
 30+ 'mwe-upwiz-remaining' => '$1 remaining',
 31+ 'mwe-upwiz-intro-details' => 'Thank you! Now we need some basic information about the files you just uploaded.',
 32+ 'mwe-upwiz-source-ownwork' => 'These files are my own work.',
 33+ 'mwe-upwiz-source-ownwork-assert' => 'I, $1, the copyright holder of this work, hereby grant anyone the right to use these works for any purpose, as long as they credit me and share derivative work under the same terms.',
 34+ 'mwe-upwiz-source-ownwork-assert-custom' => 'I, $1, the copyright holder of this work, hereby publish these works under the following license(s):',
 35+ 'mwe-upwiz-source-ownwork-assert-note' => 'This means you release your work under a double Creative Commons Attribution ShareAlike and GFDL license.',
 36+ 'mwe-upwiz-source-permission' => 'Their author gave you explicit permission to upload them',
 37+ 'mwe-upwiz-source-thirdparty' => 'These files are not my own work.',
 38+ 'mwe-upwiz-source-thirdparty-intro' => 'Please enter the address where you found each file.',
 39+ 'mwe-upwiz-source-thirdparty-custom-intro' => 'If all files have the same source, author, and copyright status, you may enter them only once for all of them.',
 40+ 'mwe-upwiz-source-thirdparty-license' => 'The copyright holder of these works published them under the following license(s):',
 41+ 'mwe-upwiz-source-thirdparty-accept' => 'OK',
 42+ 'mwe-upwiz-source-custom' => 'Did you know? You can <a href="$1">customize</a> the default options you see here.',
 43+ 'mwe-upwiz-more-options' => 'more options...',
 44+ 'mwe-upwiz-fewer-options' => 'fewer options...',
 45+ 'mwe-upwiz-desc' => 'Description in',
 46+ 'mwe-upwiz-desc-add-n' => 'add a description in another language',
 47+ 'mwe-upwiz-desc-add-0' => 'add a description',
 48+ 'mwe-upwiz-title' => 'Title',
 49+ 'mwe-upwiz-categories-intro' => 'Help people find your works by adding categories',
 50+ 'mwe-upwiz-categories-another' => 'Add other categories',
 51+ 'mwe-upwiz-previously-uploaded' => 'This file was previously uploaded to $1 and is already available <a href="$2">here</a>.',
 52+ 'mwe-upwiz-about-this-work' => 'About this work',
 53+ 'mwe-upwiz-media-type' => 'Media type',
 54+ 'mwe-upwiz-date-created' => 'Date created',
 55+ 'mwe-upwiz-location' => 'Location',
 56+ 'mwe-upwiz-copyright-info' => 'Copyright information',
 57+ 'mwe-upwiz-author' => 'Author(s)',
 58+ 'mwe-upwiz-license' => 'License',
 59+ 'mwe-upwiz-about-format' => 'About the file',
 60+ 'mwe-upwiz-autoconverted' => 'This file was automatically converted to the $1 format',
 61+ 'mwe-upwiz-filename-tag' => 'File name:',
 62+ 'mwe-upwiz-other' => 'Other information',
 63+ 'mwe-upwiz-other-prefill' => 'Free wikitext field',
 64+ 'mwe-upwiz-showall' => 'show all',
 65+ 'mwe-upwiz-source' => 'Source',
 66+ 'mwe-upwiz-macro-edit-intro' => 'Please add some descriptions and other information to your uploads.',
 67+ 'mwe-upwiz-macro-edit' => 'Update descriptions',
 68+ 'mwe-upwiz-thanks-intro' => 'Thanks for uploading your works! You can now use your files on a Wikipedia article or link to them from elsewhere on the web.',
 69+ 'mwe-upwiz-thanks-link' => 'This file is now available at <b><tt>$1</tt></b>.',
 70+ 'mwe-upwiz-thanks-wikitext' => 'To use it in a Wikipedia article, copy this text into an article: ',
 71+ 'mwe-upwiz-thanks-url' => 'To link to it in HTML, copy this HTML code: ',
 72+ 'mwe-upwiz-upload-error-bad-filename-extension' => 'This wiki does not accept filenames with the extension "$1".',
 73+ 'mwe-upwiz-upload-error-duplicate' => 'This file was previously uploaded to this wiki.',
 74+ 'mwe-upwiz-upload-error-stashed-anyway' => 'Post anyway?',
 75+ 'mwe-upwiz-ok' => 'OK',
 76+ 'mwe-upwiz-cancel' => 'Cancel',
 77+ 'mwe-upwiz-change' => '(change)',
 78+ 'mwe-fileexists' => 'A file with this name exists already. Please check <b><tt>$1</tt></b> if you are not sure if you want to replace it.',
 79+ 'mwe-thumbnail-more' => 'Enlarge',
 80+ 'mwe-upwiz-overwrite' => 'Replace the file',
 81+);
 82+$messages['be-tarask'] = array(
 83+ 'mwe-fileexists' => 'Файл з такой назвай ужо існуе. Калі ласка, праверце <b><tt>$1</tt></b>, калі Вы ня ўпэўненыя, што жадаеце яго замяніць.',
 84+);
 85+$messages['cs'] = array(
 86+ 'mwe-fileexists' => 'Soubor s tímto jménem již existuje, prosím podívejte se na <b><tt>$1</tt></b>, pokud nevíte jistě, zda chcete tento soubor nahradit.',
 87+);
 88+$messages['de'] = array(
 89+ 'mwe-fileexists' => 'Eine Datei mit diesem Namen existiert bereits. Bitte prüfe <b><tt>$1</tt></b>, wenn du dir bei der Änderung nicht sicher bist.',
 90+);
 91+$messages['diq'] = array(
 92+ 'mwe-fileexists' => 'no name de ca ra yew dosya esta. eke şıma emin niê bıvurni, kerem kerê <b><tt>$1</tt></b> kontrol bıkerê.',
 93+);
 94+$messages['dsb'] = array(
 95+ 'mwe-fileexists' => 'Dataja z toś tym mjenim južo eksistěrujo. Pšosym skontrolěruj <b><tt>$1</tt></b>, jolic njejsy wěsty, lěc coš ju změniś.',
 96+);
 97+$messages['es'] = array(
 98+ 'mwe-fileexists' => 'Un archivo con este nombre ya existe. Por favor verifica <b><tt>$1</tt></b> si no est.ás seguro si deseas cambiarlo.',
 99+);
 100+$messages['fr'] = array(
 101+ 'mwe-fileexists' => 'Un fichier existe déjà sous ce nom. Veuillez vérifier <b><tt>$1</tt></b> si vous n\'êtes pas sûr de vouloir le changer.',
 102+);
 103+$messages['gl'] = array(
 104+ 'mwe-fileexists' => 'Xa existe un ficheiro con ese nome. Por favor, verifique <b><tt>$1</tt></b> se non está seguro de que quere cambialo.',
 105+);
 106+$messages['gsw'] = array(
 107+ 'mwe-fileexists' => 'S het scho ne Datei mit däm Name. Bitte prief <b><tt>$1</tt></b>, wänn du nit sicher bisch, eb Du dr Name witt ändere.',
 108+);
 109+$messages['hsb'] = array(
 110+ 'mwe-fileexists' => 'Dataja z tutym mjenom hižo eksistuje. Prošu skontroluj <b><tt>$1</tt></b>, jeli njesy sej wěsty, hač chceš ju změnić.',
 111+);
 112+$messages['hu'] = array(
 113+ 'mwe-fileexists' => 'Már létezik ilyen nevű fájl. Ellenőrizd a(z) <b><tt>$1</tt></b> fájlt, ha nem vagy biztos benne, hogy le szeretnéd cserélni.',
 114+);
 115+$messages['ia'] = array(
 116+ 'mwe-fileexists' => 'Un file con iste nomine ja existe. Per favor verifica <b><tt>$1</tt></b> si tu non es secur de voler cambiar lo.',
 117+);
 118+$messages['id'] = array(
 119+ 'mwe-fileexists' => 'Suatu berkas dengan nama tersebut telah ada. Tolong cek <b><tt>$1</tt></b> jika Anda tidak yakin untuk mengubahnya.',
 120+);
 121+$messages['ja'] = array(
 122+ 'mwe-fileexists' => '同名のファイルが既に存在しています。上書きしてよいかわからない場合は <b><tt>$1</tt></b> を確認してください。',
 123+);
 124+$messages['ksh'] = array(
 125+ 'mwe-fileexists' => 'En Dattei met dämm Name jidd_et ald. Beß esu joot un donn <b><tt>$1</tt></b> prööfe, wann De Der nit sescher beß, of De jät ändere wells.',
 126+);
 127+$messages['lb'] = array(
 128+ 'mwe-fileexists' => 'E Fichier mat dësem Numm gëtt et schonn. Kuckt w.e.g. op <b><tt>$1</tt></b> no wann Dir net sécher sidd ob Dir en ännere wëllt.',
 129+);
 130+$messages['ml'] = array(
 131+ 'mwe-fileexists' => 'ഇതേ പേരിൽ ഒരു പ്രമാണം നിലവിലുണ്ട്. അതിൽ മാറ്റം വരുത്തണോ എന്നു താങ്കൾക്ക് ഉറപ്പില്ലങ്കിൽ ദയവായി <b><tt>$1</tt></b> കാണുക.',
 132+);
 133+$messages['nl'] = array(
 134+ 'mwe-fileexists' => 'Er bestaat al een bestand met deze naam. Controleer <b><tt>$1</tt></b> als u niet zeker weet of u het huidige bestand wilt overschrijven.',
 135+);
 136+$messages['oc'] = array(
 137+ 'mwe-fileexists' => 'Un fichièr amb aqueste nom existís ja. Mercé de verificar <b><tt>$1</tt></b> se sètz pas segur que lo volètz cambiar.',
 138+);
 139+$messages['pl'] = array(
 140+ 'mwe-fileexists' => 'Plik o tej nazwie już istnieje. Sprawdź <b><tt>$1</tt></b> jeśli nie jesteś pewien czy chcesz go zastąpić.',
 141+);
 142+$messages['pt'] = array(
 143+ 'mwe-fileexists' => 'Já existe um ficheiro com este nome. Por favor, verifique <b><tt>$1</tt></b> se não tem a certeza de que deseja alterá-lo.',
 144+);
 145+$messages['ru'] = array(
 146+ 'mwe-fileexists' => 'Файл с этим именем уже существует. Пожалуйста, проверьте <b><tt>$1</tt></b>, если вы не уверены, что хотите заменить его.',
 147+);
 148+$messages['sk'] = array(
 149+ 'mwe-fileexists' => 'Súbor s týmto názvom už existuje. Prosím, skontrolujte <b><tt>$1</tt></b> ak si nie ste istý, či ho chcete zmeniť.',
 150+);
 151+$messages['tr'] = array(
 152+ 'mwe-fileexists' => 'Bu isimde bir dosya zaten mevcut. Değiştirmek istediğinize emin değilseniz lütfen <b><tt>$1</tt></b> kontrol edin.',
 153+);
 154+$messages['vi'] = array(
 155+ 'mwe-fileexists' => 'Một tập tin với tên này đã tồn tại, xin hãy kiểm tra lại <b><tt>$1</tt></b> nếu bạn không chắc bạn có muốn thay đổi nó hay không.',
 156+);
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/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.getLocalApiUrl(); // XXX or? throw new Error( "configuration", "no API url" );
 38+ if ( ! ( mw.getConfig( '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.getConfig( '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.getConfig( '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/UploadWizardJsModule/mw.ApiUploadHandler.js
___________________________________________________________________
Name: svn:eol-style
1102 + native
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/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/UploadWizardJsModule/mw.MockUploadHandler.js
___________________________________________________________________
Name: svn:eol-style
139 + native
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/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' ).html( mw.loading_spinner() );
 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/UploadWizardJsModule/mw.UploadApiProcessor.js
___________________________________________________________________
Name: svn:eol-style
1298 + native
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/README
@@ -0,0 +1,66 @@
 2+What this is
 3+============
 4+
 5+It's the first stab at a more usable multimedia uploader, particularly for Wikimedia Commons.
 6+
 7+Main features:
 8+ - Multiple simultaneous file uploads
 9+ - Batch assertion of license ("all these files were CC-BY-SA")
 10+ - "Incomplete" uploads, where the data can be uploaded and metadata edited without really adding the file to the collection.
 11+ - (Eventually) Gee-whiz technology like HTML5 drag & drop from the desktop
 12+
 13+http://usability.wikimedia.org/wiki/Multimedia:NewUpload
 14+
 15+
 16+General disclaimer
 17+===================
 18+
 19+For the moment, (March 2010) UploadWizard is all alpha quality code.
 20+It often does not conform to the MediaWiki coding standards. This will have to be fixed before it gets merged into trunk.
 21+
 22+It uses MwEmbed because it was found to be very difficult to separate it from the framework.
 23+Furthermore, it is somewhat redundant with the AddMedia module. AddMedia has a lot of useful code but
 24+not factored in the way that would be most convenient for this module. Michael Dale (mdale) and myself (NeilK)
 25+are attempting to refactor things and meet in the middle.
 26+
 27+Presently (March 2010) we are sprinting to get a hacky interface up to conduct a usability test on our
 28+basic ideas about updating media uploads for commons. Nothing here is expected to stay here permanently.
 29+
 30+Stay tuned, or, talk to NeilK about this, if anything here bothers you.
 31+
 32+
 33+How to use it
 34+=============
 35+
 36+This is *very* complicated at present. Obviously we want to make this simpler in the future.
 37+
 38+Currently mdale tests with the live Commons site by loading in code from a JS2-enabled prototype.
 39+This is accomplished with a gadget on Commons (or other additions to the skin).
 40+
 41+Here's how you might do such a thing:
 42+
 43+- Set up an instance of MediaWiki from trunk; or, if feeling brave[1], use a real site like Commons itself.
 44+- Set up an instance of MediaWiki from js-work branch, which your browser can access.
 45+- On this trunk-based Mediawiki, install a gadget which is invoked on all pages to load some code from the JS2-enabled site. Since
 46+ this is all accomplished in your browser, you could use a local hostname. (See below).
 47+- Point your browser to the upload page on the trunk-based mediawiki, with a special parameter: Special:Upload?uploadWizard=1 .
 48+
 49+
 50+[1] Currently I (NeilK) use a local MediaWiki, since we are uploading stuff *without* the normal checks
 51+ that the Commons people have carefully added.
 52+
 53+[2] The gadget might look like this:
 54+
 55+----- cut here ----
 56+// Once mwEmbed is "on" we can use mw.setConfig calls:
 57+if( typeof mwAddMediaConfig == 'undefined'){
 58+mwAddMediaConfig = {};
 59+}
 60+mwAddMediaConfig['enabled_providers'] = [ 'wiki_commons', 'upload' ]
 61+
 62+importScriptURI('http://js2-work.yoursite.local/js/mwEmbed/remotes/mediaWiki.js?&uselang=en&debug=true' );
 63+----- cut here ----
 64+
 65+
 66+
 67+
Index: trunk/extensions/UploadWizard/UploadWizardJsModule/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+ _this.progressCb( 1.0 );
 56+ return true;
 57+ } );
 58+
 59+ // Set up the completion callback
 60+ $j( '#' + _this.iframeId ).load( function() {
 61+ mw.log( "received result in iframe" );
 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/UploadWizardJsModule/mw.IframeTransport.js
___________________________________________________________________
Name: svn:eol-style
1110 + native
Property changes on: trunk/extensions/UploadWizard/UploadWizardJsModule
___________________________________________________________________
Name: svn:mergeinfo
2111 + /branches/REL1_15/phase3/js2/mwEmbed/modules/UploadWizard:51646
/branches/sqlite/js2/mwEmbed/modules/UploadWizard:58211-58321

Status & tagging log