Index: branches/uploadwizard-firefogg/UploadWizard.config.php |
— | — | @@ -4,44 +4,180 @@ |
5 | 5 | * Do not modify this file, instead use localsettings.php and set: |
6 | 6 | * $wgUploadWizardConfig[ 'name'] = 'value'; |
7 | 7 | */ |
8 | | -global $wgFileExtensions, $wgServer, $wgScriptPath, $wgAPIModules, |
9 | | -$wgTimedMediaHandlerFileExtensions, $wgAutoloadClasses; |
| 8 | +global $wgFileExtensions, $wgServer, $wgScriptPath, $wgAPIModules; |
10 | 9 | return array( |
11 | 10 | // Upload wizard has an internal debug flag |
12 | 11 | 'debug' => false, |
13 | 12 | |
14 | | - // If the uploaded file should be auto categorized |
15 | | - 'autoCategory' => true, |
16 | | - |
17 | 13 | // File extensions acceptable in this wiki |
18 | 14 | 'fileExtensions' => $wgFileExtensions, |
19 | 15 | |
20 | | - // Check if we want to enable firefogg ( for transcoding ) |
21 | | - 'enableFirefogg' => true, |
| 16 | + // The default api url is for the current wiki ( can override at run time ) |
| 17 | + 'apiUrl' => $wgServer . $wgScriptPath . '/api.php', |
| 18 | + |
| 19 | + // If the uploaded file should be auto categorized |
| 20 | + 'autoCategory' => false, |
22 | 21 | |
23 | | - // Check if we have the firefogg upload api module enabled: |
24 | | - 'enableFirefoggChunkUpload' => isset( $wgAPIModules['firefoggupload'] )? true : false, |
| 22 | + // 'licenses' is a list of licenses you could possibly use elsewhere, for instance in |
| 23 | + // licensesOwnWork or licensesThirdParty. |
| 24 | + // It just describes what licenses go with what wikitext, and how to display them in |
| 25 | + // a menu of license choices. There probably isn't any reason to delete any entry here. |
| 26 | + // Under normal circumstances, the license name is the name of the wikitext template to insert. |
| 27 | + // For those that aren't, there is a "templates" property. |
| 28 | + 'licenses' => array( |
| 29 | + 'cc-by-sa-3.0' => array( |
| 30 | + 'msg' => 'mwe-upwiz-license-cc-by-sa-3.0', |
| 31 | + 'icons' => array( 'cc-by', 'cc-sa' ) |
| 32 | + ), |
| 33 | + 'cc-by-sa-3.0-gfdl' => array( |
| 34 | + 'msg' => 'mwe-upwiz-license-cc-by-sa-3.0-gfdl', |
| 35 | + 'templates' => array( 'gfdl', 'cc-by-sa-3.0' ), |
| 36 | + 'icons' => array( 'cc-by', 'cc-sa' ) |
| 37 | + ), |
| 38 | + 'cc-by-3.0-gfdl' => array( |
| 39 | + 'msg' => 'mwe-upwiz-license-cc-by-3.0-gfdl', |
| 40 | + 'templates' => array( 'gfdl', 'cc-by-3.0' ), |
| 41 | + 'icons' => array( 'cc-by' ) |
| 42 | + ), |
| 43 | + 'cc-by-3.0' => array( |
| 44 | + 'msg' => 'mwe-upwiz-license-cc-by-3.0', |
| 45 | + 'icons' => array( 'cc-by' ) |
| 46 | + ), |
| 47 | + 'cc-zero' => array( |
| 48 | + 'msg' => 'mwe-upwiz-license-cc-zero', |
| 49 | + 'icons' => array( 'cc-zero' ) |
| 50 | + ), |
| 51 | + 'own-pd' => array( |
| 52 | + 'msg' => 'mwe-upwiz-license-own-pd', |
| 53 | + 'icons' => array( 'cc-zero' ), |
| 54 | + 'templates' => array( 'cc-zero' ) |
| 55 | + ), |
| 56 | + 'cc-by-sa-2.5' => array( |
| 57 | + 'msg' => 'mwe-upwiz-license-cc-by-sa-2.5', |
| 58 | + 'icons' => array( 'cc-by', 'cc-sa' ) |
| 59 | + ), |
| 60 | + 'cc-by-2.5' => array( |
| 61 | + 'msg' => 'mwe-upwiz-license-cc-by-2.5', |
| 62 | + 'icons' => array( 'cc-by' ) |
| 63 | + ), |
| 64 | + 'cc-by-sa-2.0' => array( |
| 65 | + 'msg' => 'mwe-upwiz-license-cc-by-sa-2.0', |
| 66 | + 'icons' => array( 'cc-by', 'cc-sa' ) |
| 67 | + ), |
| 68 | + 'cc-by-2.0' => array( |
| 69 | + 'msg' => 'mwe-upwiz-license-cc-by-2.0', |
| 70 | + 'icons' => array( 'cc-by' ) |
| 71 | + ), |
| 72 | + 'fal' => array( |
| 73 | + 'msg' => 'mwe-upwiz-license-fal' |
| 74 | + ), |
| 75 | + 'pd-old-100' => array( |
| 76 | + 'msg' => 'mwe-upwiz-license-pd-old-100' |
| 77 | + ), |
| 78 | + 'pd-old' => array( |
| 79 | + 'msg' => 'mwe-upwiz-license-pd-old' |
| 80 | + ), |
| 81 | + 'pd-art' => array( |
| 82 | + 'msg' => 'mwe-upwiz-license-pd-art' |
| 83 | + ), |
| 84 | + 'pd-us' => array( |
| 85 | + 'msg' => 'mwe-upwiz-license-pd-us' |
| 86 | + ), |
| 87 | + 'pd-usgov' => array( |
| 88 | + 'msg' => 'mwe-upwiz-license-pd-usgov' |
| 89 | + ), |
| 90 | + 'pd-usgov-nasa' => array( |
| 91 | + 'msg' => 'mwe-upwiz-license-pd-usgov-nasa' |
| 92 | + ), |
| 93 | + 'pd-usgov-military-navy' => array( |
| 94 | + 'msg' => 'mwe-upwiz-license-pd-usgov-military-navy' |
| 95 | + ), |
| 96 | + 'pd-ineligible' => array( |
| 97 | + 'msg' => 'mwe-upwiz-license-pd-ineligible' |
| 98 | + ), |
| 99 | + 'pd-textlogo' => array( |
| 100 | + 'msg' => 'mwe-upwiz-license-pd-textlogo', |
| 101 | + 'templates' => array( 'trademarked', 'pd-textlogo' ) |
| 102 | + ), |
| 103 | + 'copyrighted-free-use' => array( |
| 104 | + 'msg' => 'mwe-upwiz-license-copyrighted-free-use' |
| 105 | + ), |
| 106 | + 'attribution' => array( |
| 107 | + 'msg' => 'mwe-upwiz-license-attribution' |
| 108 | + ), |
| 109 | + 'gfdl' => array( |
| 110 | + 'msg' => 'mwe-upwiz-license-gfdl' |
| 111 | + ) |
| 112 | + ), |
25 | 113 | |
26 | | - // Firefogg encode settings ( if timed media handler extension is installed use HD webm, else mid-rage ogg ) |
27 | | - 'firefoggEncodeSettings' => ( class_exists( 'WebVideoTranscode' ) )? |
28 | | - WebVideoTranscode::$derivativeSettings[ WebVideoTranscode::ENC_WEBM_HQ_VBR ] : |
29 | | - array( |
30 | | - 'maxSize' => '480', |
31 | | - 'videoBitrate' => '512', |
32 | | - 'audioBitrate' => '96', |
33 | | - 'noUpscaling' => 'true', |
34 | | - 'twopass' => 'true', |
35 | | - 'keyframeInterval' => '128', |
36 | | - 'bufDelay' => '256', |
37 | | - 'videoCodec' => 'theora', |
38 | | - ), |
| 114 | + // radio button selection of some licenses |
| 115 | + 'licensesOwnWork' => array( |
| 116 | + 'type' => 'or', |
| 117 | + 'filterTemplate' => 'self', |
| 118 | + 'licenses' => array( |
| 119 | + 'cc-by-sa-3.0', |
| 120 | + 'cc-by-3.0', |
| 121 | + 'own-pd' |
| 122 | + ), |
| 123 | + 'defaults' => array( 'cc-by-sa-3.0' ) |
| 124 | + ), |
39 | 125 | |
40 | | - // The default api url is for the current wiki ( can override at run time ) |
41 | | - 'apiUrl' => $wgServer . $wgScriptPath . '/api.php', |
42 | | - |
43 | | - // The progress update interval for uploads |
44 | | - 'uploadProgressInterval' => 250, |
45 | | - |
| 126 | + // checkbox selection of all licenses |
| 127 | + 'licensesThirdParty' => array( |
| 128 | + 'type' => 'and', |
| 129 | + 'licenseGroups' => array( |
| 130 | + array( |
| 131 | + // This should be a list of all CC licenses we can reasonably expect to find around the web |
| 132 | + 'head' => 'mwe-upwiz-license-cc-head', |
| 133 | + 'subhead' => 'mwe-upwiz-license-cc-subhead', |
| 134 | + 'licenses' => array( |
| 135 | + 'cc-by-sa-3.0', |
| 136 | + 'cc-by-sa-2.5', |
| 137 | + 'cc-by-3.0', |
| 138 | + 'cc-by-2.5', |
| 139 | + 'cc-zero' |
| 140 | + ) |
| 141 | + ), |
| 142 | + array( |
| 143 | + // n.b. as of April 2011, Flickr still uses CC 2.0 licenses. |
| 144 | + // The White House also has an account there, hence the Public Domain US Government license |
| 145 | + 'head' => 'mwe-upwiz-license-flickr-head', |
| 146 | + 'subhead' => 'mwe-upwiz-license-flickr-subhead', |
| 147 | + 'prependTemplates' => array( 'flickrreview' ), |
| 148 | + 'licenses' => array( |
| 149 | + 'cc-by-sa-2.0', |
| 150 | + 'cc-by-2.0', |
| 151 | + 'pd-usgov', |
| 152 | + ) |
| 153 | + ), |
| 154 | + array( |
| 155 | + 'head' => 'mwe-upwiz-license-public-domain-head', |
| 156 | + 'licenses' => array( |
| 157 | + 'pd-old-100', |
| 158 | + 'pd-old', |
| 159 | + 'pd-art', |
| 160 | + 'pd-us', |
| 161 | + ) |
| 162 | + ), |
| 163 | + array( |
| 164 | + // omitted navy because it is believed only MultiChil uses it heavily. Could add it back |
| 165 | + 'head' => 'mwe-upwiz-license-usgov-head', |
| 166 | + 'licenses' => array( |
| 167 | + 'pd-usgov', |
| 168 | + 'pd-usgov-nasa' |
| 169 | + ) |
| 170 | + ), |
| 171 | + array( |
| 172 | + 'head' => 'mwe-upwiz-license-misc', |
| 173 | + 'licenses' => array( |
| 174 | + 'fal' |
| 175 | + ) |
| 176 | + ) |
| 177 | + ), |
| 178 | + 'defaults' => array(), |
| 179 | + ), |
| 180 | + |
| 181 | + |
46 | 182 | // Default thumbnail width |
47 | 183 | 'thumbnailWidth' => 120, |
48 | 184 | |
— | — | @@ -53,7 +189,13 @@ |
54 | 190 | |
55 | 191 | // Small thumbnail max height |
56 | 192 | 'smallThumbnailMaxHeight' => 100, |
| 193 | + |
| 194 | + // Large thumbnail width |
| 195 | + 'largeThumbnailWidth' => 500, |
57 | 196 | |
| 197 | + // Large thumbnail max height |
| 198 | + 'largeThumbnailMaxHeight' => 500, |
| 199 | + |
58 | 200 | // Icon thumbnail width: |
59 | 201 | 'iconThumbnailWidth' => 32, |
60 | 202 | |
— | — | @@ -100,17 +242,6 @@ |
101 | 243 | // so, this workaround will cause tagalog descriptions to be saved with this template instead. |
102 | 244 | 'languageTemplateFixups' => array( 'tl' => 'tgl' ), |
103 | 245 | |
104 | | - // names of all license templates, in order. Case sensitive! |
105 | | - // n.b. in the future, the licenses for a wiki will probably be defined in PHP or even LocalSettings. |
106 | | - 'licenses' => array( |
107 | | - array( 'template' => 'Cc-by-sa-3.0','messageKey' => 'mwe-upwiz-license-cc-by-sa-3.0', 'default' => true ), |
108 | | - array( 'template' => 'Cc-by-3.0', 'messageKey' => 'mwe-upwiz-license-cc-by-3.0', 'default' => false ), |
109 | | - array( 'template' => 'Cc-zero', 'messageKey' => 'mwe-upwiz-license-cc-zero', 'default' => false ), |
110 | | - // n.b. the PD-US is only for testing purposes, obviously we need some geographical discrimination here... |
111 | | - array( 'template' => 'PD-US', 'messageKey' => 'mwe-upwiz-license-pd-us', 'default' => false ), |
112 | | - array( 'template' => 'GFDL', 'messageKey' => 'mwe-upwiz-license-gfdl', 'default' => false ) |
113 | | - ) |
114 | | - |
115 | 246 | // XXX this is horribly confusing -- some file restrictions are client side, others are server side |
116 | 247 | // the filename prefix blacklist is at least server side -- all this should be replaced with PHP regex config |
117 | 248 | // or actually, in an ideal world, we'd have some way to reliably detect gibberish, rather than trying to |
— | — | @@ -123,4 +254,17 @@ |
124 | 255 | // /^(test|image|img|bild|example?[\s_-]*)$/, // test stuff |
125 | 256 | // /^(\d{10}[\s_-][0-9a-f]{10}[\s_-][a-z])$/ // flickr |
126 | 257 | // ] |
| 258 | + |
| 259 | + // Check if we want to enable firefogg when installed on the client ( for transcoding video ) |
| 260 | + 'enableFirefogg' => true, |
| 261 | + |
| 262 | + // Check if we have the firefogg upload api module enabled: |
| 263 | + 'enableFirefoggChunkUpload' => isset( $wgAPIModules['firefoggupload'] )? true : false, |
| 264 | + |
| 265 | + // Set skipTutorial to true to always skip tutorial step |
| 266 | + 'skipTutorial' => false, |
| 267 | + |
| 268 | + // Wiki page for leaving Upload Wizard feedback, for example 'Commons:Upload wizard feedback' |
| 269 | + 'feedbackPage' => '', |
| 270 | + |
127 | 271 | ); |
Index: branches/uploadwizard-firefogg/resources/mw.js |
— | — | @@ -1,7 +0,0 @@ |
2 | | -// dependencies: [] |
3 | | - |
4 | | -if ( typeof window.mediaWiki === 'undefined' ) { |
5 | | - window.mediaWiki = {}; |
6 | | - window.mw = window.mediaWiki; |
7 | | - mw = window.mediaWiki; |
8 | | -} |
Index: branches/uploadwizard-firefogg/resources/jquery/jquery.mwCoolCats.css |
— | — | @@ -45,7 +45,17 @@ |
46 | 46 | display: inline-block; |
47 | 47 | } |
48 | 48 | |
| 49 | +/* Fix for IE6 */ |
| 50 | +.cat-widget .ui-button { |
| 51 | + position: static; |
| 52 | +} |
49 | 53 | |
| 54 | +/* Make category add button smaller */ |
| 55 | +.cat-widget .ui-button-text { |
| 56 | + padding: 0; |
| 57 | +} |
| 58 | + |
| 59 | + |
50 | 60 | /* Utilities */ |
51 | 61 | .pkg:after, #content-inner:after { |
52 | 62 | content: " "; |
Index: branches/uploadwizard-firefogg/resources/jquery/jquery.tipsy.js |
— | — | @@ -19,9 +19,9 @@ |
20 | 20 | if (title && this.enabled) { |
21 | 21 | var $tip = this.tip(); |
22 | 22 | |
23 | | - $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title); |
24 | 23 | $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity |
25 | 24 | $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body); |
| 25 | + $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title); |
26 | 26 | |
27 | 27 | var pos = $.extend({}, this.$element.offset(), { |
28 | 28 | width: this.$element[0].offsetWidth, |
— | — | @@ -82,6 +82,7 @@ |
83 | 83 | if ( this.displayed ) { |
84 | 84 | this.hide(); |
85 | 85 | } else { |
| 86 | + $('.mwe-upwiz-hint').each( function(i) { $(this).tipsy('hide'); } ); |
86 | 87 | this.show(); |
87 | 88 | } |
88 | 89 | }, |
— | — | @@ -102,7 +103,9 @@ |
103 | 104 | } else if (typeof o.title == 'function') { |
104 | 105 | title = o.title.call($e[0]); |
105 | 106 | } |
106 | | - title = ('' + title).replace(/(^\s*|\s*$)/, ""); |
| 107 | + if ( typeof title === 'string' ) { |
| 108 | + title = ('' + title).replace(/(^\s*|\s*$)/, ""); |
| 109 | + } |
107 | 110 | return title || o.fallback; |
108 | 111 | }, |
109 | 112 | |
Index: branches/uploadwizard-firefogg/resources/jquery/jquery.mwCoolCats.js |
— | — | @@ -35,6 +35,7 @@ |
36 | 36 | |
37 | 37 | _this.wrap('<div class="cat-widget"></div>'); |
38 | 38 | $container = _this.parent(); // set to the cat-widget class we just wrapped |
| 39 | + $container.prepend('<ul class="cat-list pkg"></ul>'); |
39 | 40 | $container.append( $j( '<button type="button" name="catbutton">'+settings.buttontext+'</button>' ) |
40 | 41 | .button() |
41 | 42 | .click( function(e) { |
— | — | @@ -45,8 +46,6 @@ |
46 | 47 | }) |
47 | 48 | ); |
48 | 49 | |
49 | | - $container.prepend('<ul class="cat-list pkg"></ul>'); |
50 | | - |
51 | 50 | //XXX ensure this isn't blocking other stuff needed. |
52 | 51 | _this.parents('form').submit( function() { |
53 | 52 | _processInput(); |
— | — | @@ -80,6 +79,8 @@ |
81 | 80 | } |
82 | 81 | |
83 | 82 | function _insertCat( cat, isHidden ) { |
| 83 | + // strip out bad characters |
| 84 | + cat = cat.replace( /[\x00-\x1f\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f]+/g, '' ); |
84 | 85 | if ( mw.isEmpty( cat ) || _containsCat( cat ) ) { |
85 | 86 | return; |
86 | 87 | } |
— | — | @@ -120,13 +121,15 @@ |
121 | 122 | |
122 | 123 | function _fetchSuggestions( query ) { |
123 | 124 | var _this = this; |
| 125 | + // ignore bad characters, they will be stripped out |
| 126 | + var catName = $j( this ).val().replace( /[\x00-\x1f\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f]+/g, '' ); |
124 | 127 | var request = $j.ajax( { |
125 | 128 | url: wgScriptPath + '/api.php', |
126 | 129 | data: { |
127 | 130 | 'action': 'query', |
128 | 131 | 'list': 'allpages', |
129 | 132 | 'apnamespace': wgNamespaceIds['category'], |
130 | | - 'apprefix': $j( this ).val(), |
| 133 | + 'apprefix': catName, |
131 | 134 | 'format': 'json' |
132 | 135 | }, |
133 | 136 | dataType: 'json', |
Index: branches/uploadwizard-firefogg/resources/jquery/jquery.tipsy.css |
— | — | @@ -1,5 +1,5 @@ |
2 | 2 | .tipsy { padding: 5px; font-size: small; position: absolute; z-index: 100000; } |
3 | | - .tipsy-inner { padding: 5px 8px 4px 8px; background-color:#96d8d9; color: black; max-width: 200px; text-align: center; } |
| 3 | + .tipsy-inner { padding: 5px 8px 4px 8px; background-color:#e8f5ff; color: black; max-width: 200px; text-align: center; } |
4 | 4 | .tipsy-inner { border-radius: 3px; -moz-border-radius:3px; -webkit-border-radius:3px; } |
5 | 5 | .tipsy-arrow { position: absolute; background: url('jquery.tipsy.help.gif') no-repeat top left; width: 9px; height: 5px; } |
6 | 6 | .tipsy-n .tipsy-arrow { top: 0; left: 50%; margin-left: -4px; } |
Index: branches/uploadwizard-firefogg/resources/mw.UploadWizardDeed.js |
— | — | @@ -29,13 +29,7 @@ |
30 | 30 | * @return wikitext of all applicable license templates. |
31 | 31 | */ |
32 | 32 | getLicenseWikiText: function() { |
33 | | - var _this = this; |
34 | | - var wikiText = ''; |
35 | | - $j.each ( _this.licenseInput.getTemplates(), function( i, template ) { |
36 | | - wikiText += "{{" + template + "}}\n"; |
37 | | - } ); |
38 | | - |
39 | | - return wikiText; |
| 33 | + return this.licenseInput.getWikiText(); |
40 | 34 | } |
41 | 35 | |
42 | 36 | }; |
— | — | @@ -62,8 +56,10 @@ |
63 | 57 | .addClass( 'mwe-upwiz-sign' ); |
64 | 58 | |
65 | 59 | var licenseInputDiv = $j( '<div class="mwe-upwiz-deed-license"></div>' ); |
66 | | - _this.licenseInput = new mw.UploadWizardLicenseInput( licenseInputDiv ); |
67 | | - _this.licenseInput.setDefaultValues(); |
| 60 | + _this.licenseInput = new mw.UploadWizardLicenseInput( licenseInputDiv, |
| 61 | + undefined, |
| 62 | + mw.UploadWizard.config.licensesOwnWork, |
| 63 | + _this.uploadCount ); |
68 | 64 | |
69 | 65 | return $j.extend( _this, { |
70 | 66 | |
— | — | @@ -92,56 +88,41 @@ |
93 | 89 | }, |
94 | 90 | |
95 | 91 | |
96 | | - getLicenseWikiText: function() { |
97 | | - var wikiText = '{{self'; |
98 | | - $j.each( _this.licenseInput.getTemplates(), function( i, template ) { |
99 | | - wikiText += '|' + template; |
100 | | - } ); |
101 | | - wikiText += '}}'; |
102 | | - return wikiText; |
103 | | - }, |
104 | | - |
105 | 92 | setFormFields: function( $selector ) { |
106 | 93 | _this.$selector = $selector; |
107 | 94 | |
108 | | - _this.$form = $j( '<form/>' ); |
| 95 | + _this.$form = $j( '<form />' ); |
109 | 96 | |
| 97 | + var $authorInput2 = $j( '<input />' ).attr( { name: "author2", type: "text" } ).addClass( 'mwe-upwiz-sign' ); |
110 | 98 | var $standardDiv = $j( '<div />' ).append( |
111 | | - $j( '<label for="author2" generated="true" class="mwe-validator-error" style="display:block;"/>' ), |
112 | | - $j( '<p>' ) |
113 | | - .html( gM( 'mwe-upwiz-source-ownwork-assert', |
114 | | - uploadCount, |
115 | | - '<span class="mwe-standard-author-input"></span>' ) |
116 | | - ), |
117 | | - $j( '<p class="mwe-small-print" />' ).append( gM( 'mwe-upwiz-source-ownwork-assert-note' ) ) |
| 99 | + $j( '<label for="author2" generated="true" class="mwe-validator-error" style="display:block;" />' ), |
| 100 | + $j( '<p></p>' ).msg( 'mwe-upwiz-source-ownwork-assert', |
| 101 | + uploadCount, |
| 102 | + $authorInput2 ), |
| 103 | + $j( '<p class="mwe-small-print"></p>' ).msg( 'mwe-upwiz-source-ownwork-assert-note' ) |
118 | 104 | ); |
119 | | - $standardDiv.find( '.mwe-standard-author-input' ).append( $j( '<input name="author2" type="text" class="mwe-upwiz-sign" />' ) ); |
120 | 105 | |
121 | | - var $customDiv = $j('<div/>').append( |
122 | | - $j( '<label for="author" generated="true" class="mwe-validator-error" style="display:block;"/>' ), |
123 | | - $j( '<p>' ) |
124 | | - .html( gM( 'mwe-upwiz-source-ownwork-assert-custom', |
125 | | - uploadCount, |
126 | | - '<span class="mwe-custom-author-input"></span>' ) ), |
| 106 | + var $customDiv = $j('<div />').append( |
| 107 | + $j( '<label for="author" generated="true" class="mwe-validator-error" style="display:block;" />' ), |
| 108 | + $j( '<p></p>' ).msg( 'mwe-upwiz-source-ownwork-assert-custom', |
| 109 | + uploadCount, |
| 110 | + _this.authorInput ), |
127 | 111 | licenseInputDiv |
128 | 112 | ); |
129 | | - // have to add the author input this way -- gM() will flatten it to a string and we'll lose it as a dom object |
130 | | - $customDiv.find( '.mwe-custom-author-input' ).append( _this.authorInput ); |
131 | 113 | |
132 | | - |
133 | | - var $crossfader = $j( '<div>' ).append( $standardDiv, $customDiv ); |
134 | | - var $toggler = $j( '<p class="mwe-more-options" style="text-align: right" />' ) |
| 114 | + var $crossfader = $j( '<div />' ).append( $standardDiv, $customDiv ); |
| 115 | + var $toggler = $j( '<p class="mwe-more-options" style="text-align: right"></p>' ) |
135 | 116 | .append( $j( '<a />' ) |
136 | | - .append( gM( 'mwe-upwiz-license-show-all' ) ) |
| 117 | + .msg( 'mwe-upwiz-license-show-all' ) |
137 | 118 | .click( function() { |
138 | 119 | _this.formValidator.resetForm(); |
139 | 120 | if ( $crossfader.data( 'crossfadeDisplay' ) === $customDiv ) { |
140 | 121 | _this.licenseInput.setDefaultValues(); |
141 | 122 | $crossfader.morphCrossfade( $standardDiv ); |
142 | | - $j( this ).html( gM( 'mwe-upwiz-license-show-all' ) ); |
| 123 | + $j( this ).msg( 'mwe-upwiz-license-show-all' ); |
143 | 124 | } else { |
144 | 125 | $crossfader.morphCrossfade( $customDiv ); |
145 | | - $j( this ).html( gM( 'mwe-upwiz-license-show-recommended' ) ); |
| 126 | + $j( this ).msg( 'mwe-upwiz-license-show-recommended' ); |
146 | 127 | } |
147 | 128 | } ) ); |
148 | 129 | |
— | — | @@ -172,8 +153,10 @@ |
173 | 154 | |
174 | 155 | // done after added to the DOM, so there are true heights |
175 | 156 | $crossfader.morphCrossfader(); |
| 157 | + |
| 158 | + // choose default licenses |
| 159 | + _this.licenseInput.setDefaultValues(); |
176 | 160 | |
177 | | - |
178 | 161 | // and finally, make it validatable |
179 | 162 | _this.formValidator = _this.$form.validate( { |
180 | 163 | rules: { |
— | — | @@ -224,7 +207,10 @@ |
225 | 208 | .growTextArea() |
226 | 209 | .attr( 'title', gM( 'mwe-upwiz-tooltip-author' ) ); |
227 | 210 | licenseInputDiv = $j( '<div class="mwe-upwiz-deed-license"></div>' ); |
228 | | - _this.licenseInput = new mw.UploadWizardLicenseInput( licenseInputDiv ); |
| 211 | + _this.licenseInput = new mw.UploadWizardLicenseInput( licenseInputDiv, |
| 212 | + undefined, |
| 213 | + mw.UploadWizard.config.licensesThirdParty, |
| 214 | + _this.uploadCount ); |
229 | 215 | |
230 | 216 | |
231 | 217 | return $j.extend( _this, mw.UploadWizardDeed.prototype, { |
— | — | @@ -232,26 +218,26 @@ |
233 | 219 | |
234 | 220 | setFormFields: function( $selector ) { |
235 | 221 | var _this = this; |
236 | | - _this.$form = $j( '<form/>' ); |
| 222 | + _this.$form = $j( '<form />' ); |
237 | 223 | |
238 | | - var $formFields = $j( '<div class="mwe-upwiz-deed-form-internal"/>' ); |
| 224 | + var $formFields = $j( '<div class="mwe-upwiz-deed-form-internal" />' ); |
239 | 225 | |
240 | | - if ( uploadCount > 1 ) { |
241 | | - $formFields.append( $j( '<div />' ).append( gM( 'mwe-upwiz-source-thirdparty-custom-multiple-intro' ) ) ); |
| 226 | + if ( _this.uploadCount > 1 ) { |
| 227 | + $formFields.append( $j( '<div />' ).msg( 'mwe-upwiz-source-thirdparty-custom-multiple-intro' ) ); |
242 | 228 | } |
243 | 229 | |
244 | 230 | $formFields.append ( |
245 | 231 | $j( '<div class="mwe-upwiz-source-thirdparty-custom-multiple-intro" />' ), |
246 | | - $j( '<label for="source" generated="true" class="mwe-validator-error" style="display:block;"/>' ), |
| 232 | + $j( '<label for="source" generated="true" class="mwe-validator-error" style="display:block;" />' ), |
247 | 233 | $j( '<div class="mwe-upwiz-thirdparty-fields" />' ) |
248 | | - .append( $j( '<label for="source"/>' ).text( gM( 'mwe-upwiz-source' ) ).addHint( 'source' ), |
| 234 | + .append( $j( '<label for="source" />' ).text( gM( 'mwe-upwiz-source' ) ).addHint( 'source' ), |
249 | 235 | _this.sourceInput ), |
250 | | - $j( '<label for="author" generated="true" class="mwe-validator-error" style="display:block;"/>' ), |
| 236 | + $j( '<label for="author" generated="true" class="mwe-validator-error" style="display:block;" />' ), |
251 | 237 | $j( '<div class="mwe-upwiz-thirdparty-fields" />' ) |
252 | | - .append( $j( '<label for="author"/>' ).text( gM( 'mwe-upwiz-author' ) ).addHint( 'author' ), |
| 238 | + .append( $j( '<label for="author" />' ).text( gM( 'mwe-upwiz-author' ) ).addHint( 'author' ), |
253 | 239 | _this.authorInput ), |
254 | 240 | $j( '<div class="mwe-upwiz-thirdparty-license" />' ) |
255 | | - .append( gM( 'mwe-upwiz-source-thirdparty-license', uploadCount ) ), |
| 241 | + .msg( 'mwe-upwiz-source-thirdparty-cases', _this.uploadCount ), |
256 | 242 | licenseInputDiv |
257 | 243 | ); |
258 | 244 | |
— | — | @@ -325,16 +311,16 @@ |
326 | 312 | var id = _this.name + '-' + deed.name; |
327 | 313 | var $deedInterface = $j( |
328 | 314 | '<div class="mwe-upwiz-deed mwe-upwiz-deed-' + deed.name + '">' |
329 | | - + '<div class="mwe-upwiz-deed-option-title">' |
330 | | - + '<span class="mwe-upwiz-deed-header">' |
331 | | - + '<input id="' + id +'" name="' + _this.name + '" type="radio" value="' + deed.name + ' /">' |
332 | | - + '<label for="' + id + '" class="mwe-upwiz-deed-name">' |
333 | | - + gM( 'mwe-upwiz-source-' + deed.name, _this.uploadCount ) |
334 | | - + '</label>' |
335 | | - + '</span>' |
336 | | - + '</div>' |
337 | | - + '<div class="mwe-upwiz-deed-form">' |
338 | | - + '</div>' |
| 315 | + + '<div class="mwe-upwiz-deed-option-title">' |
| 316 | + + '<span class="mwe-upwiz-deed-header">' |
| 317 | + + '<input id="' + id +'" name="' + _this.name + '" type="radio" value="' + deed.name + ' /">' |
| 318 | + + '<label for="' + id + '" class="mwe-upwiz-deed-name">' |
| 319 | + + gM( 'mwe-upwiz-source-' + deed.name, _this.uploadCount ) |
| 320 | + + '</label>' |
| 321 | + + '</span>' |
| 322 | + + '</div>' |
| 323 | + + '<div class="mwe-upwiz-deed-form"></div>' |
| 324 | + +'</div>' |
339 | 325 | ); |
340 | 326 | |
341 | 327 | var $deedSelector = _this.$selector.append( $deedInterface ); |
Index: branches/uploadwizard-firefogg/resources/mw.UploadWizardLicenseInput.js |
— | — | @@ -1,39 +1,124 @@ |
2 | 2 | /** |
3 | | - * Create a group of checkboxes for licenses. N.b. the licenses are named after the templates they invoke. |
4 | | - * @param div |
5 | | - * @param values (optional) array of license key names to activate by default |
| 3 | + * Create a group of radio buttons for licenses. N.b. the licenses are named after the templates they invoke. |
| 4 | + * @param {String|jQuery} selector to place license input |
| 5 | + * @param {Array} license key name(s) to activate by default |
| 6 | + * @param {Array} configuration of licenseInput. Must have following properties |
| 7 | + * 'type' = ("and"|"or") -- whether inclusive or exclusive license allowed |
| 8 | + * 'defaults' => array of template string names (can be empty array), |
| 9 | + * 'licenses' => array of template string names (matching keys in mw.UploadWizard.config.licenses) |
| 10 | + * optional: 'licenseGroups' => groups of licenses, with more explanation |
| 11 | + * @param {Numbe} count of the things we are licensing (it matters to some texts) |
6 | 12 | */ |
7 | 13 | |
8 | 14 | ( function( $j ) { |
9 | | -mw.UploadWizardLicenseInput = function( selector, values ) { |
| 15 | +mw.UploadWizardLicenseInput = function( selector, values, config, count ) { |
10 | 16 | var _this = this; |
| 17 | + _this.count = count; |
11 | 18 | |
12 | | - var widgetCount = mw.UploadWizardLicenseInput.prototype.count++; |
| 19 | + if ( ! ( mw.isDefined(config.type) |
| 20 | + && mw.isDefined( config.defaults ) |
| 21 | + && ( mw.isDefined( config.licenses ) || mw.isDefined( config.licenseGroups ) ) ) ) { |
| 22 | + throw new Error( 'improper initialization' ); |
| 23 | + } |
| 24 | + |
| 25 | + _this.$selector = $j( selector ); |
| 26 | + _this.$selector.append( $j( '<div class="mwe-error"></div>' ) ); |
| 27 | + |
| 28 | + _this.type = config.type === 'or' ? 'radio' : 'checkbox'; |
| 29 | + |
| 30 | + _this.defaults = config.defaults; |
| 31 | + |
| 32 | + mw.UploadWizardLicenseInput.prototype.count++; |
| 33 | + _this.name = 'license' + mw.UploadWizardLicenseInput.prototype.count; |
| 34 | + |
13 | 35 | |
| 36 | + /** |
| 37 | + * Define the licenses this input will show: |
| 38 | + */ |
| 39 | + _this.licenses = []; |
14 | 40 | _this.inputs = []; |
| 41 | + /** |
| 42 | + * append defined license inputs to element; also records licenses and inputs in _this |
| 43 | + * Abstracts out simple lists of licenses, more complex groups with layout |
| 44 | + * @param {jQuery} selector to add inputs to |
| 45 | + * @param {Array} license configuration, which must have a 'licenses' property, which is an array of license names |
| 46 | + * it may also have: 'prependTemplates' or 'filterTemplate', which alter the final wikitext value |
| 47 | + * 'prependTemplates' will prepend Templates. If prependTemplates were [ 'pre', 'pended' ], then... |
| 48 | + * [ 'fooLicense' ] -> "{{pre}}{{pended}}{{fooLicense}}" |
| 49 | + * 'filterTemplates' will filter Templates, as in "own work". If 'filterTemplate' was 'filter', then... |
| 50 | + * [ 'fooLicense', 'barLicense' ] -> {{filter|fooLicense|barLicense}} |
| 51 | + * |
| 52 | + */ |
| 53 | + function appendLicenses( $el, config ) { |
| 54 | + if ( !mw.isDefined( config['licenses'] && typeof config['licenses'] === 'object' ) ) { |
| 55 | + throw new Error( "improper license config" ); |
| 56 | + } |
| 57 | + $j.each( config['licenses'], function( i, name ) { |
| 58 | + if ( mw.isDefined( mw.UploadWizard.config.licenses[name] ) ) { |
| 59 | + var license = { name: name, props: mw.UploadWizard.config.licenses[name] }; |
| 60 | + _this.licenses.push( license ); |
| 61 | + var templates = mw.isDefined( license.props['templates'] ) ? license.props.templates : [ license.name ]; |
| 62 | + var origTemplateString = templates.join( '|' ); |
| 63 | + if ( mw.isDefined( config['prependTemplates'] ) ) { |
| 64 | + $j.each( config['prependTemplates'], function( i, template ) { |
| 65 | + templates.unshift( template ); |
| 66 | + } ); |
| 67 | + } |
| 68 | + if ( mw.isDefined( config['filterTemplate'] ) ) { |
| 69 | + templates.unshift( config['filterTemplate'] ); |
| 70 | + templates = [ templates.join( '|' ) ]; |
| 71 | + } |
| 72 | + // using inputs length to ensure that you can have two options which deliver same result, |
| 73 | + // but the label association still works |
| 74 | + var id = _this.name + '_' + templates.join('_') + '_' + _this.inputs.length; |
15 | 75 | |
16 | | - // TODO incompatibility check of this license versus others |
| 76 | + // the value is literal wikitext; turn template names (or template names + args) into wikitext templates |
| 77 | + var value = ( $j.map( templates, function( t ) { return '{{' + t + '}}'; } ) ).join( '' ); |
| 78 | + // IE6 is idiotic about radio buttons; you have to create them as HTML or clicks aren't recorded |
| 79 | + var $input = $j( '<input id="' + id + '" name="' + _this.name + '" type="' + _this.type + '" value="' + value + '" />' ); |
| 80 | + $input.click( function() { _this.$selector.trigger( 'changeLicenses' ); } ); |
| 81 | + // this is added so that setValues() can find one (or more) checkboxes to check - represent values without wikitext |
| 82 | + $input.data( 'templateString', origTemplateString ); |
| 83 | + _this.inputs.push( $input ); |
| 84 | + |
| 85 | + var messageKey = mw.isDefined( license.props['msg'] ) ? license.props.msg : '[missing msg for ' + license.name + ']'; |
| 86 | + var $icons = $j( '<span></span>' ); |
| 87 | + if ( mw.isDefined( license.props['icons'] ) ) { |
| 88 | + $j.each( license.props.icons, function( i, icon ) { |
| 89 | + $icons.append( $j( '<span></span>' ).addClass( 'mwe-upwiz-license-icon mwe-upwiz-' + icon + '-icon' ) ); |
| 90 | + } ); |
| 91 | + } |
| 92 | + $el.append( |
| 93 | + $input, |
| 94 | + $j( '<label />' ).attr( { 'for': id } ).msg( messageKey, _this.count ).append( $icons ), |
| 95 | + $j( '<br/>' ) |
| 96 | + // XXX help? |
| 97 | + ); |
| 98 | + } |
| 99 | + } ); |
| 100 | + } |
17 | 101 | |
18 | | - _this.$selector = $j( selector ); |
19 | | - _this.$selector.append( $j( '<div class="mwe-error"></div>' ) ); |
| 102 | + if ( mw.isDefined( config['licenseGroups'] ) ) { |
| 103 | + $j.each( config['licenseGroups'], function( i, group ) { |
| 104 | + if ( !mw.isDefined( group['licenses'] ) ) { |
| 105 | + throw new Error( 'improper config' ); |
| 106 | + } |
| 107 | + var $group = $j( '<div></div>' ).addClass( 'mwe-upwiz-deed-license-group' ); |
| 108 | + if ( mw.isDefined( group['head'] ) ) { |
| 109 | + $group.append( $j( '<p></p>' ).addClass( 'mwe-upwiz-deed-license-group-head' ).msg( group.head, _this.count ) ); |
| 110 | + } |
| 111 | + if ( mw.isDefined( group['subhead'] ) ) { |
| 112 | + $group.append( $j( '<p></p>' ).addClass( 'mwe-upwiz-deed-license-group-subhead' ).msg( group.subhead, _this.count ) ); |
| 113 | + } |
| 114 | + var $licensesDiv = $j( '<div></div>' ).addClass( 'mwe-upwiz-deed-license' ); |
| 115 | + appendLicenses( $licensesDiv, group ); |
| 116 | + $group.append( $licensesDiv ); |
| 117 | + _this.$selector.append( $group ); |
| 118 | + } ); |
20 | 119 | |
21 | | - $j.each( mw.UploadWizard.config[ 'licenses' ], function( i, licenseConfig ) { |
22 | | - var template = licenseConfig.template; |
23 | | - var messageKey = licenseConfig.messageKey; |
24 | | - |
25 | | - var name = 'license_' + template; |
26 | | - var id = 'licenseInput' + widgetCount + '_' + name; |
27 | | - var $input = $j( '<input />' ) |
28 | | - .attr( { id: id, name: name, type: 'checkbox', value: template } ) |
29 | | - // we use the selector because events can't be unbound unless they're in the DOM. |
30 | | - .click( function() { _this.$selector.trigger( 'changeLicenses' ); } ); |
31 | | - _this.inputs.push( $input ); |
32 | | - _this.$selector.append( |
33 | | - $input, |
34 | | - $j( '<label />' ).attr( { 'for': id } ).html( gM( messageKey ) ), |
35 | | - $j( '<br/>' ) |
36 | | - ); |
37 | | - } ); |
| 120 | + } else { |
| 121 | + appendLicenses( _this.$selector, config ); |
| 122 | + } |
38 | 123 | |
39 | 124 | if ( values ) { |
40 | 125 | _this.setValues( values ); |
— | — | @@ -46,14 +131,16 @@ |
47 | 132 | count: 0, |
48 | 133 | |
49 | 134 | /** |
50 | | - * Sets the value(s) of a license input. |
51 | | - * @param object of license-key to boolean values, e.g. { cc_by_sa_30: true, gfdl: true } |
| 135 | + * Sets the value(s) of a license input. This is a little bit klugey because it relies on an inverted dict, and in some |
| 136 | + * cases we are now letting license inputs create multiple templates. |
| 137 | + * @param object of license-key to boolean values, e.g. { 'cc_by_sa_30': true, 'gfdl': true, 'flickrreview|cc_by_sa_30': false } |
52 | 138 | */ |
53 | | - setValues: function( licenseValues ) { |
| 139 | + setValues: function( values ) { |
54 | 140 | var _this = this; |
55 | 141 | $j.each( _this.inputs, function( i, $input ) { |
56 | | - var template = $input.val(); |
57 | | - $input.attr( 'checked', ~~!!licenseValues[template] ); |
| 142 | + var templateString = $input.data( 'templateString' ); |
| 143 | + // !! to ensure boolean. ~~ to cast to 0 or 1. Similar to php's (int) (bool) val |
| 144 | + $input.attr( 'checked', ~~!!values[templateString] ); |
58 | 145 | } ); |
59 | 146 | // we use the selector because events can't be unbound unless they're in the DOM. |
60 | 147 | _this.$selector.trigger( 'changeLicenses' ); |
— | — | @@ -65,23 +152,33 @@ |
66 | 153 | setDefaultValues: function() { |
67 | 154 | var _this = this; |
68 | 155 | var values = {}; |
69 | | - $j.each( mw.UploadWizard.config[ 'licenses' ], function( i, licenseConfig ) { |
70 | | - values[ licenseConfig.template ] = licenseConfig['default']; |
| 156 | + $j.each( _this.defaults, function( i, lic ) { |
| 157 | + values[lic] = true; |
71 | 158 | } ); |
72 | 159 | _this.setValues( values ); |
73 | 160 | }, |
74 | 161 | |
75 | 162 | /** |
76 | | - * Gets the templates associated with checked inputs |
77 | | - * @return array of template names |
| 163 | + * Gets the wikitext associated with all checked inputs |
| 164 | + * @return string of wikitext (empty string if no inputs set) |
78 | 165 | */ |
79 | | - getTemplates: function() { |
80 | | - return $j( this.inputs ) |
81 | | - .filter( function() { return this.is( ':checked' ); } ) |
82 | | - .map( function() { return this.val(); } ); |
| 166 | + getWikiText: function() { |
| 167 | + // need to use makeArray because a jQuery-returned set of things won't have .join |
| 168 | + return $j.makeArray( |
| 169 | + this.getCheckedInputs().map( function() { return this.val(); } ) |
| 170 | + ).join( "" ); |
83 | 171 | }, |
84 | 172 | |
85 | 173 | /** |
| 174 | + * Gets which inputs are checked |
| 175 | + * @return {jQuery Array} of inputs |
| 176 | + */ |
| 177 | + getCheckedInputs: function() { |
| 178 | + return $j( this.inputs ).filter( function() { return this.is( ':checked' ); } ); |
| 179 | + }, |
| 180 | + |
| 181 | + |
| 182 | + /** |
86 | 183 | * Check if a valid value is set, also look for incompatible choices. |
87 | 184 | * Side effect: if no valid value, add notes to the interface. Add listeners to interface, to revalidate and remove notes. |
88 | 185 | * @return boolean; true if a value set, false otherwise |
— | — | @@ -95,8 +192,6 @@ |
96 | 193 | errorHtml = gM( 'mwe-upwiz-deeds-need-license' ); |
97 | 194 | } |
98 | 195 | |
99 | | - // XXX something goes here for licenses incompatible with each other |
100 | | - |
101 | 196 | var $errorEl = this.$selector.find( '.mwe-error' ); |
102 | 197 | if (isValid) { |
103 | 198 | $errorEl.fadeOut(); |
— | — | @@ -118,7 +213,7 @@ |
119 | 214 | * @return boolean |
120 | 215 | */ |
121 | 216 | isSet: function() { |
122 | | - return this.getTemplates().length > 0; |
| 217 | + return this.getCheckedInputs().length > 0; |
123 | 218 | } |
124 | 219 | |
125 | 220 | }; |
Index: branches/uploadwizard-firefogg/resources/mw.UploadWizardUploadInterface.js |
— | — | @@ -69,7 +69,6 @@ |
70 | 70 | .append( _this.$fileInputCtrl ) |
71 | 71 | ) |
72 | 72 | .append( _this.filenameCtrl ) |
73 | | - .append( _this.thumbnailParam ) |
74 | 73 | .get( 0 ); |
75 | 74 | |
76 | 75 | |
— | — | @@ -132,35 +131,31 @@ |
133 | 132 | * @param HTMLImageElement |
134 | 133 | */ |
135 | 134 | setPreview: function( image ) { |
136 | | - // encoding for url here? |
137 | | - $j( this.div ).find( '.mwe-upwiz-file-preview' ).append( |
138 | | - $j('<img />') |
139 | | - .css({'width':'100%', 'height': '100%'}) |
140 | | - .attr('src', image.src ) |
141 | | - ) |
| 135 | + var $preview = $j( this.div ).find( '.mwe-upwiz-file-preview' ); |
| 136 | + if ( image === null ) { |
| 137 | + $preview.addClass( 'mwe-upwiz-file-preview-broken' ); |
| 138 | + } else { |
| 139 | + // encoding for url here? |
| 140 | + $preview.css( 'background-image', 'url(' + image.src + ')' ); |
| 141 | + } |
142 | 142 | }, |
143 | 143 | |
144 | 144 | /** |
145 | 145 | * Set the status line for this upload with an internationalized message string. |
146 | 146 | * @param String msgKey: key for the message |
147 | 147 | * @param Array args: array of values, in case any need to be fed to the image. |
| 148 | + * @param Boolean error: if true, show an error |
148 | 149 | */ |
149 | 150 | setStatus: function( msgKey, args ) { |
150 | 151 | if ( !mw.isDefined( args ) ) { |
151 | 152 | args = []; |
152 | 153 | } |
153 | | - this.setStatusStr( gM( msgKey, args ) ); |
| 154 | + // get the status line for our upload |
| 155 | + var $s = $j( this.div ).find( '.mwe-upwiz-file-status' ); |
| 156 | + $s.msg( msgKey, args ).show(); |
154 | 157 | }, |
155 | 158 | |
156 | 159 | /** |
157 | | - * Set the status line for this upload |
158 | | - * @param String str: the string to use |
159 | | - */ |
160 | | - setStatusStr: function( str ) { |
161 | | - $j( this.div ).find( '.mwe-upwiz-file-status' ).html( str ).show(); |
162 | | - }, |
163 | | - |
164 | | - /** |
165 | 160 | * Clear the status line for this upload (hide it, in case there are paddings and such which offset other things.) |
166 | 161 | */ |
167 | 162 | clearStatus: function() { |
— | — | @@ -197,11 +192,14 @@ |
198 | 193 | // is this an error that we expect to have a message for? |
199 | 194 | var msgKey = 'mwe-upwiz-api-error-unknown-code'; |
200 | 195 | var args = [ code ]; |
| 196 | + |
| 197 | + if ( code === 'http' && info.textStatus === 'timeout' ) { |
| 198 | + code = 'timeout'; |
| 199 | + } |
| 200 | + |
201 | 201 | if ( $j.inArray( code, mw.Api.errors ) !== -1 ) { |
202 | 202 | msgKey = 'mwe-upwiz-api-error-' + code; |
203 | | - // args may change base on particular error messages. |
204 | | - // for instance, we are throwing away the extra info right now. Might be nice to surface that in a debug mode |
205 | | - args = []; |
| 203 | + args = $j.makeArray( info ); |
206 | 204 | } |
207 | 205 | this.setStatus( msgKey, args ); |
208 | 206 | }, |
— | — | @@ -214,11 +212,31 @@ |
215 | 213 | var _this = this; |
216 | 214 | _this.clearErrors(); |
217 | 215 | _this.upload.extractLocalFileInfo( _this.$fileInputCtrl.val() ); |
218 | | - if ( _this.isGoodExtension( _this.upload.title.getExtension() ) ) { |
| 216 | + var extension = _this.upload.title.getExtension(); |
| 217 | + var hasExtension = ! mw.isEmpty( extension ); |
| 218 | + var isGoodExtension = false; |
| 219 | + if ( hasExtension ) { |
| 220 | + isGoodExtension = $j.inArray( extension.toLowerCase(), mw.UploadWizard.config[ 'fileExtensions' ] ) !== -1; |
| 221 | + } |
| 222 | + if ( hasExtension && isGoodExtension ) { |
219 | 223 | _this.updateFilename(); |
220 | 224 | } else { |
221 | | - //_this.error( 'bad-filename-extension', ext ); |
222 | | - alert("bad extension"); |
| 225 | + var errorMessage = hasExtension ? 'mwe-upwiz-upload-error-bad-filename-extension' : 'mwe-upwiz-upload-error-bad-filename-no-extension'; |
| 226 | + $( '<div>' ) |
| 227 | + .append( |
| 228 | + $j( '<p>' ).msg( errorMessage, extension ), |
| 229 | + $j( '<p>' ).msg( 'mwe-upwiz-allowed-filename-extensions' ), |
| 230 | + $j( '<blockquote>' ).append( $j( '<tt>' ).append( |
| 231 | + mw.UploadWizard.config[ 'fileExtensions' ].join( " " ) |
| 232 | + ) ) |
| 233 | + ) |
| 234 | + .dialog({ |
| 235 | + width: 500, |
| 236 | + zIndex: 200000, |
| 237 | + autoOpen: true, |
| 238 | + title: gM( 'mwe-upwiz-help-popup' ) + ': ' + gM( 'mwe-upwiz-help-allowed-filename-extensions' ), |
| 239 | + modal: true |
| 240 | + }); |
223 | 241 | } |
224 | 242 | this.clearStatus(); |
225 | 243 | }, |
— | — | @@ -273,7 +291,22 @@ |
274 | 292 | // visible filename |
275 | 293 | $j( _this.form ).find( '.mwe-upwiz-visible-file-filename-text' ).html( path ); |
276 | 294 | |
277 | | - _this.upload.title = new mw.Title( mw.UploadWizardUtil.getBasename( path ), 'file' ); |
| 295 | + var filename = mw.UploadWizardUtil.getBasename( path ); |
| 296 | + try { |
| 297 | + _this.upload.title = new mw.Title( filename, 'file' ); |
| 298 | + } catch ( e ) { |
| 299 | + $( '<div>' ) |
| 300 | + .msg( 'mwe-upwiz-unparseable-filename', filename ) |
| 301 | + .dialog({ |
| 302 | + width: 500, |
| 303 | + zIndex: 200000, |
| 304 | + autoOpen: true, |
| 305 | + modal: true |
| 306 | + }); |
| 307 | + _this.$fileInputCtrl.val(); |
| 308 | + return; |
| 309 | + } |
| 310 | + |
278 | 311 | $j( _this.filenameCtrl ).val( _this.upload.title.getMain() ); |
279 | 312 | |
280 | 313 | if ( ! _this.isFilled ) { |
— | — | @@ -335,19 +368,6 @@ |
336 | 369 | // apply a error style to entire did |
337 | 370 | $j( _this.div ).addClass( 'mwe-upwiz-upload-error' ); |
338 | 371 | $j( _this.errorDiv ).show(); |
339 | | - }, |
340 | | - |
341 | | - /** |
342 | | - * This is used when checking for "bad" extensions in a filename. |
343 | | - * @param ext |
344 | | - * @return boolean if extension was acceptable |
345 | | - */ |
346 | | - isGoodExtension: function( ext ) { |
347 | | - // ugly but we don't have a base "uploadHandler" class |
348 | | - if( this.upload.getUploadHandler().isGoodExtension ){ |
349 | | - return this.upload.getUploadHandler().isGoodExtension( ext ); |
350 | | - } |
351 | | - return $j.inArray( ext.toLowerCase(), mw.UploadWizard.config[ 'fileExtensions' ] ) !== -1; |
352 | 372 | } |
353 | 373 | |
354 | | -}; |
\ No newline at end of file |
| 374 | +}; |
Index: branches/uploadwizard-firefogg/resources/mw.UploadWizardUtil.js |
— | — | @@ -16,26 +16,23 @@ |
17 | 17 | $j( toggleDiv ).append( $toggleLink ); |
18 | 18 | |
19 | 19 | |
20 | | - var toggle = function( open ) { |
21 | | - if ( typeof open === 'undefined' ) { |
22 | | - open = ! ( $j( this ).data( 'open' ) ) ; |
23 | | - } |
24 | | - $j( this ).data( 'open', open ); |
25 | | - if ( open ) { |
26 | | - moreDiv.show(); // maskSafeShow(); |
27 | | - /* when open, show control to close */ |
28 | | - $toggleLink.html( gM( 'mwe-upwiz-fewer-options' ) ); |
29 | | - $toggleLink.addClass( "mwe-upwiz-toggler-open" ); |
30 | | - } else { |
| 20 | + var toggle = function() { |
| 21 | + var isOpen = $toggleLink.hasClass( "mwe-upwiz-toggler-open" ); |
| 22 | + if ( isOpen ) { |
| 23 | + // hide the extra options |
31 | 24 | moreDiv.hide(); // maskSafeHide(); |
32 | 25 | /* when closed, show control to open */ |
33 | | - $toggleLink.html( gM( 'mwe-upwiz-more-options' ) ); |
34 | | - $toggleLink.removeClass( "mwe-upwiz-toggler-open" ); |
| 26 | + $toggleLink.msg( 'mwe-upwiz-more-options' ).removeClass( "mwe-upwiz-toggler-open" ); |
| 27 | + } else { |
| 28 | + // show the extra options |
| 29 | + moreDiv.show(); // maskSafeShow(); |
| 30 | + /* when open, show control to close */ |
| 31 | + $toggleLink.msg( 'mwe-upwiz-fewer-options' ).addClass( "mwe-upwiz-toggler-open" ); |
35 | 32 | } |
36 | 33 | }; |
| 34 | + |
| 35 | + moreDiv.hide(); |
37 | 36 | |
38 | | - toggle(false); |
39 | | - |
40 | 37 | $toggleLink.click( function( e ) { e.stopPropagation(); toggle(); } ); |
41 | 38 | |
42 | 39 | $j( moreDiv ).addClass( 'mwe-upwiz-toggled' ); |
Index: branches/uploadwizard-firefogg/resources/mw.DestinationChecker.js |
— | — | @@ -104,10 +104,11 @@ |
105 | 105 | */ |
106 | 106 | checkUnique: function() { |
107 | 107 | var _this = this; |
108 | | - |
109 | 108 | var found = false; |
110 | | - // XXX if input is empty don't bother? but preprocess gives us File:.png... |
111 | 109 | var title = _this.getTitle(); |
| 110 | + |
| 111 | + // if input is empty don't bother. |
| 112 | + if ( title == '' ) return; |
112 | 113 | |
113 | 114 | if ( _this.cachedResult[name] !== undefined ) { |
114 | 115 | _this.processResult( _this.cachedResult[name] ); |
— | — | @@ -126,8 +127,8 @@ |
127 | 128 | 'iiurlwidth': 150 |
128 | 129 | }; |
129 | 130 | |
130 | | - // Do the destination check |
131 | | - _this.api.get( params, function( data ) { |
| 131 | + |
| 132 | + var ok = function( data ) { |
132 | 133 | // Remove spinner |
133 | 134 | _this.spinner( false ); |
134 | 135 | |
— | — | @@ -138,7 +139,7 @@ |
139 | 140 | |
140 | 141 | if ( !data || !data.query || !data.query.pages ) { |
141 | 142 | // Ignore a null result |
142 | | - mw.log("mw.DestinationChecker::checkUnique> No data in checkUnique result"); |
| 143 | + mw.log("mw.DestinationChecker::checkUnique> No data in checkUnique result", 'debug'); |
143 | 144 | return; |
144 | 145 | } |
145 | 146 | |
— | — | @@ -146,7 +147,6 @@ |
147 | 148 | |
148 | 149 | if ( data.query.pages[-1] ) { |
149 | 150 | // No conflict found; this file name is unique |
150 | | - mw.log("mw.DestinationChecker::checkUnique> No pages in checkUnique result"); |
151 | 151 | result = { isUnique: true }; |
152 | 152 | |
153 | 153 | } else { |
— | — | @@ -157,8 +157,6 @@ |
158 | 158 | } |
159 | 159 | |
160 | 160 | // Conflict found, this filename is NOT unique |
161 | | - mw.log( "mw.DestinationChecker::checkUnique> conflict! " ); |
162 | | - |
163 | 161 | var ntitle; |
164 | 162 | if ( data.query.normalized ) { |
165 | 163 | ntitle = data.query.normalized[0].to; |
— | — | @@ -184,7 +182,16 @@ |
185 | 183 | _this.processResult( result ); |
186 | 184 | } |
187 | 185 | |
188 | | - } ); |
| 186 | + }; |
| 187 | + |
| 188 | + var err = function( code, result ) { |
| 189 | + _this.spinner( false ); |
| 190 | + mw.log("mw.DestinationChecker::checkUnique> error in checkUnique result: " + code, 'debug'); |
| 191 | + return; |
| 192 | + }; |
| 193 | + |
| 194 | + // Do the destination check |
| 195 | + _this.api.get( params, { ok: ok, err: err } ); |
189 | 196 | } |
190 | 197 | |
191 | 198 | }; |
Index: branches/uploadwizard-firefogg/resources/mw.Api.js |
— | — | @@ -47,8 +47,7 @@ |
48 | 48 | |
49 | 49 | // caller can supply handlers for http transport error or api errors |
50 | 50 | err: function( code, result ) { |
51 | | - var errorMsg = "mw.Api error: " + code; |
52 | | - mw.log( _method + errorMsg ); |
| 51 | + mw.log( "mw.Api error: " + code, 'debug' ); |
53 | 52 | }, |
54 | 53 | |
55 | 54 | timeout: 30000, /* 30 seconds */ |
— | — | @@ -129,6 +128,7 @@ |
130 | 129 | ajaxOptions.err( 'http', { xhr: xhr, textStatus: textStatus, exception: exception } ); |
131 | 130 | }; |
132 | 131 | |
| 132 | + |
133 | 133 | /* success just means 200 OK; also check for output and API errors */ |
134 | 134 | ajaxOptions.success = function( result ) { |
135 | 135 | if ( mw.isEmpty( result ) ) { |
— | — | @@ -153,6 +153,20 @@ |
154 | 154 | * available. |
155 | 155 | */ |
156 | 156 | mw.Api.errors = [ |
| 157 | + /* occurs when POST aborted - jQuery 1.4 can't distinguish abort or lost connection from 200 OK + empty result */ |
| 158 | + 'ok-but-empty', |
| 159 | + |
| 160 | + // timeout |
| 161 | + 'timeout', |
| 162 | + |
| 163 | + /* really a warning, but we treat it like an error */ |
| 164 | + 'duplicate', |
| 165 | + |
| 166 | + /* upload succeeded, but no image info. |
| 167 | + this is probably impossible, but might as well check for it */ |
| 168 | + 'noimageinfo', |
| 169 | + |
| 170 | + /* remote errors, defined in API */ |
157 | 171 | 'uploaddisabled', |
158 | 172 | 'nomodule', |
159 | 173 | 'mustbeposted', |
Index: branches/uploadwizard-firefogg/resources/mw.Title.js |
— | — | @@ -42,13 +42,13 @@ |
43 | 43 | var ext = null; |
44 | 44 | |
45 | 45 | /** |
46 | | - * strip every illegal char we can think of |
| 46 | + * strip some illegal chars: control chars, colon, less than, greater than, brackets, braces, pipe, whitespace |
47 | 47 | * yes, I know this leaves other insanity intact, like unicode bidi chars, but let's start someplace |
48 | 48 | * @return {String} |
49 | 49 | */ |
50 | 50 | function clean( s ) { |
51 | 51 | if ( mw.isDefined( s ) ) { |
52 | | - return s.replace( /[\x00-\x1f\s]+/g, '_' ); |
| 52 | + return s.replace( /[\x00-\x1f\x23\x3a\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' ); |
53 | 53 | } |
54 | 54 | } |
55 | 55 | |
Index: branches/uploadwizard-firefogg/resources/mw.UtilitiesTime.js |
— | — | @@ -15,8 +15,7 @@ |
16 | 16 | */ |
17 | 17 | mw.seconds2npt = function( sec, show_ms ) { |
18 | 18 | if ( isNaN( sec ) ) { |
19 | | - mw.log("mw.seconds2npt> Warning: trying to get npt time on NaN:" + sec); |
20 | | - return '0:00:00'; |
| 19 | + sec = 0; |
21 | 20 | } |
22 | 21 | |
23 | 22 | var tm = mw.seconds2Measurements( sec ); |
— | — | @@ -66,8 +65,7 @@ |
67 | 66 | */ |
68 | 67 | mw.npt2seconds = function ( npt_str ) { |
69 | 68 | if ( !npt_str ) { |
70 | | - // mw.log('npt2seconds:not valid ntp:'+ntp); |
71 | | - return false; |
| 69 | + return undefined; |
72 | 70 | } |
73 | 71 | // Strip {npt:}01:02:20 or 32{s} from time if present |
74 | 72 | npt_str = npt_str.replace( /npt:|s/g, '' ); |
Index: branches/uploadwizard-firefogg/resources/mw.Api.edit.js |
— | — | @@ -1,7 +1,5 @@ |
2 | 2 | // library to assist with edits |
3 | 3 | |
4 | | -// dependencies: [ mw.Api, jQuery ] |
5 | | - |
6 | 4 | ( function( mw, $ ) { |
7 | 5 | |
8 | 6 | // cached token so we don't have to keep fetching new ones for every single post |
— | — | @@ -19,36 +17,27 @@ |
20 | 18 | */ |
21 | 19 | postWithEditToken: function( params, ok, err ) { |
22 | 20 | var api = this; |
23 | | - var _method = 'mw.api.edit::postWithEditToken> '; |
24 | | - mw.log( 'post with edit token' ); |
25 | 21 | if ( cachedToken === null ) { |
26 | | - mw.log( _method + 'no cached token' ); |
27 | 22 | // We don't have a valid cached token, so get a fresh one and try posting. |
28 | 23 | // We do not trap any 'badtoken' or 'notoken' errors, because we don't want |
29 | 24 | // an infinite loop. If this fresh token is bad, something else is very wrong. |
30 | 25 | var useTokenToPost = function( token ) { |
31 | | - mw.log( _method + 'posting with token = ' + token ); |
32 | 26 | params.token = token; |
33 | 27 | this.post( params, ok, err ); |
34 | 28 | }; |
35 | | - mw.log( _method + 'getting edit token' ); |
36 | 29 | api.getEditToken( useTokenToPost, err ); |
37 | 30 | } else { |
38 | 31 | // We do have a token, but it might be expired. So if it is 'bad' then |
39 | 32 | // start over with a new token. |
40 | 33 | params.token = cachedToken; |
41 | | - mw.log( _method + 'we do have a token = ' + params.token ); |
42 | 34 | var getTokenIfBad = function( code, result ) { |
43 | | - mw.log( _method + "error with posting with token!" ); |
44 | 35 | if ( code === 'badtoken' ) { |
45 | | - mw.log( _method + "bad token; try again" ); |
46 | 36 | cachedToken = null; // force a new token |
47 | | - api.postWidthEditToken( params, ok, err ); |
| 37 | + api.postWithEditToken( params, ok, err ); |
48 | 38 | } else { |
49 | 39 | err( code, result ); |
50 | 40 | } |
51 | 41 | }; |
52 | | - mw.log ( _method + "posting with the token that was cached " ); |
53 | 42 | api.post( params, ok, getTokenIfBad ); |
54 | 43 | } |
55 | 44 | }, |
Index: branches/uploadwizard-firefogg/resources/mediawiki.language.parser.js |
— | — | @@ -1,5 +1,8 @@ |
2 | 2 | /** |
3 | | - * n.b. if this is ever moved to be mediawiki.language.parser, then mediawiki.language.procPLURAL will be obsolete |
| 3 | + * Experimental advanced wikitext parser-emitter. |
| 4 | + * See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs |
| 5 | + * |
| 6 | + * @author neilk@wikimedia.org |
4 | 7 | */ |
5 | 8 | |
6 | 9 | ( function( mw, $j ) { |
— | — | @@ -69,7 +72,7 @@ |
70 | 73 | * @return {jQuery} this |
71 | 74 | */ |
72 | 75 | return function( key /* , replacements */ ) { |
73 | | - var $target = this; |
| 76 | + var $target = this.empty(); |
74 | 77 | $j.each( parser.parse( key, getVariadicArgs( arguments, 1 ) ).contents(), function( i, node ) { |
75 | 78 | $target.append( node ); |
76 | 79 | } ); |
— | — | @@ -132,41 +135,9 @@ |
133 | 136 | /* |
134 | 137 | * Parses the input wikiText into an abstract syntax tree, essentially an s-expression. |
135 | 138 | * |
136 | | - * Why you would want to do this: ASTs make some complex stuff easy. |
137 | | - * - this allows the parser to be stateless -- all parsing state is in the AST, which is cached, or is just there temporarily as |
138 | | - * replacement parameters are swapped in |
139 | | - * - it's MUCH simpler to do complex replacements, such as swapping in jQuery objects into wikitext like "[$1 my link]". No string hackery required |
140 | | - * - decouples target format from parsing -- you can output a string of HTML, or jQuery-compatible array of nodes, or whatever you like. |
141 | | - * |
142 | | - * However, these are also all arguments for doing this on the server. Stay tuned... we can reduce the code on the client by half |
143 | | - * by porting everything below this function to PHP. |
144 | | - * |
145 | | - * Examples: |
146 | | - * "A simple input string" => "A simple input string" |
147 | | - * "Simple {{TEMPLATE}} message" => [ 'CONCAT', 'Simple ', [ 'TEMPLATE' ], ' message' ]; |
148 | | - * "Undelete {{PLURAL:$1|one edit|$1 edits}}", => |
149 | | - * [ 'CONCAT', |
150 | | - * 'Undelete ', |
151 | | - * [ 'PLURAL', |
152 | | - * [ 'REPLACE', 0 ], // zero-based index. $1 is argument 0 |
153 | | - * 'one edit', |
154 | | - * [ 'CONCAT', |
155 | | - * [ 'REPLACE', 0 ] |
156 | | - * ' edits' |
157 | | - * ] |
158 | | - * ] |
159 | | - * ] |
160 | | - * |
161 | | - * The following code is a highly hand-hacked and optimized |
162 | | - * parser based on a generated PEG parser using the grammar in the file mediawiki.parser.peg |
163 | | - * |
164 | | - * CAVEAT: This does not parse all wikitext, and it makes a lot of assumptions that may not reflect |
165 | | - * how the actual parser works. It works for pretty much all cases where we will want to pass translation |
166 | | - * strings to the frontend, however. |
| 139 | + * CAVEAT: This does not parse all wikitext. It could be more efficient, but it's pretty good already. |
| 140 | + * n.b. We want to move this functionality to the server. Nothing here is required to be on the client. |
167 | 141 | * |
168 | | - * More caveats: there are a lot of things here which could be more efficient, but it's already pretty |
169 | | - * efficient already and we may not use this client side for very long until we move it server side. |
170 | | - * |
171 | 142 | * @param {String} message string wikitext |
172 | 143 | * @throws Error |
173 | 144 | * @return {Mixed} abstract syntax tree |
Index: branches/uploadwizard-firefogg/resources/uploadWizard.css |
— | — | @@ -1,5 +1,8 @@ |
| 2 | +/* for feedback request message */ |
| 3 | +#contentSub { |
| 4 | + margin-left: 0; |
| 5 | +} |
2 | 6 | |
3 | | - |
4 | 7 | form.mwe-upwiz-form { |
5 | 8 | display: inline; |
6 | 9 | } |
— | — | @@ -170,6 +173,12 @@ |
171 | 174 | background: url('images/32px-Blank-document.svg.png') no-repeat center top; |
172 | 175 | } |
173 | 176 | |
| 177 | +.mwe-upwiz-file-preview-broken { |
| 178 | + height: 40px !important; |
| 179 | + width: 40px !important; |
| 180 | + background: url('images/32px-Blank-document-broken.svg.png') no-repeat center top !important; |
| 181 | +} |
| 182 | + |
174 | 183 | .mwe-upwiz-add-files-n { |
175 | 184 | float: left; |
176 | 185 | } |
— | — | @@ -275,10 +284,34 @@ |
276 | 285 | background: url('images/24px-spinner-0645ad.gif') no-repeat center center; |
277 | 286 | } |
278 | 287 | |
| 288 | +.mwe-upwiz-file.hover .mwe-upwiz-status-progress { |
| 289 | + background: url('images/24px-spinner-0645ad-e0f0ff.gif') no-repeat center center; |
| 290 | +} |
| 291 | + |
279 | 292 | .mwe-upwiz-status-stashed, .mwe-upwiz-status-uploaded { |
280 | 293 | background: url('images/32px-Dialog-apply-009900.svg.png') no-repeat center center; |
281 | 294 | } |
282 | 295 | |
| 296 | + |
| 297 | +.mwe-upwiz-license-icon { |
| 298 | + height: 18px; |
| 299 | + margin-left: 6px; |
| 300 | + padding: 9px; |
| 301 | + width: 18px; |
| 302 | +} |
| 303 | + |
| 304 | +.mwe-upwiz-cc-zero-icon { |
| 305 | + background: url('images/18px-Cc-zero.svg.png') no-repeat center center; |
| 306 | +} |
| 307 | + |
| 308 | +.mwe-upwiz-cc-sa-icon { |
| 309 | + background: url('images/18px-Share_Alike.svg.png') no-repeat center center; |
| 310 | +} |
| 311 | + |
| 312 | +.mwe-upwiz-cc-by-icon { |
| 313 | + background: url('images/18px-Cc-by_new_white.svg.png') no-repeat center center; |
| 314 | +} |
| 315 | + |
283 | 316 | .mwe-upwiz-status-error { |
284 | 317 | background: url('images/32px-Nuvola_apps_important_orange.svg.png') no-repeat center center; |
285 | 318 | font-weight: bold; |
— | — | @@ -466,15 +499,17 @@ |
467 | 500 | |
468 | 501 | .mwe-grow-textarea, .mwe-long-textarea { |
469 | 502 | overflow: hidden; |
470 | | - font-family: sans-serif; /* XXX is this right? */ |
| 503 | + font-family: sans-serif; |
471 | 504 | font-size: small; |
472 | 505 | } |
473 | 506 | |
474 | | -fieldset .mwe-long-textarea { |
475 | | - width: 17em; |
| 507 | + |
| 508 | +.mwe-title { |
| 509 | + font-family: sans-serif; |
| 510 | + font-size: small; |
| 511 | + width: 100%; |
476 | 512 | } |
477 | 513 | |
478 | | - |
479 | 514 | .mwe-upwiz-details-fieldname-input { |
480 | 515 | margin-bottom: 1em; |
481 | 516 | } |
— | — | @@ -489,7 +524,6 @@ |
490 | 525 | } |
491 | 526 | |
492 | 527 | fieldset.mwe-fieldset { |
493 | | - width: 450px; |
494 | 528 | border: 1px solid #cccccc; |
495 | 529 | padding-left: 12px; |
496 | 530 | padding-right: 12px; |
— | — | @@ -628,6 +662,11 @@ |
629 | 663 | background: url('images/question-hover.png') no-repeat; |
630 | 664 | } |
631 | 665 | |
| 666 | +/* Hack for Explorer 6 and 7 - line up buttons with other content */ |
| 667 | +.mwe-upwiz-buttons .ui-button, .cat-widget .ui-button { |
| 668 | + *vertical-align: middle; |
| 669 | +} |
| 670 | + |
632 | 671 | #upload-wizard .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; } |
633 | 672 | #upload-wizard .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; } |
634 | 673 | #upload-wizard .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; } |
— | — | @@ -637,3 +676,19 @@ |
638 | 677 | #upload-wizard .ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } |
639 | 678 | #upload-wizard .ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; } |
640 | 679 | #upload-wizard .ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; } |
| 680 | + |
| 681 | +.mwe-upwiz-deed-license-group { |
| 682 | + margin-top: 1em; |
| 683 | + margin-bottom: 1.5em; |
| 684 | +} |
| 685 | + |
| 686 | +.mwe-upwiz-deed-license-group-head { |
| 687 | + font-weight: bold; |
| 688 | + margin-bottom: 0em; |
| 689 | +} |
| 690 | + |
| 691 | +.mwe-upwiz-deed-license-group-subhead { |
| 692 | + color: #797979; |
| 693 | + margin-top: 0em; |
| 694 | + margin-bottom: 0em; |
| 695 | +} |
Index: branches/uploadwizard-firefogg/resources/mw.UploadWizard.js |
— | — | @@ -22,7 +22,7 @@ |
23 | 23 | this.transportWeight = 1; // default |
24 | 24 | this.detailsWeight = 1; // default |
25 | 25 | |
26 | | - // details |
| 26 | + // details |
27 | 27 | this.ui = new mw.UploadWizardUploadInterface( this, filesDiv ); |
28 | 28 | |
29 | 29 | // handler -- usually ApiUploadHandler |
— | — | @@ -62,14 +62,13 @@ |
63 | 63 | // we signal to the wizard to update itself, which has to delete the final vestige of |
64 | 64 | // this upload (the ui.div). We have to do this silly dance because we |
65 | 65 | // trigger through the div. Triggering through objects doesn't always work. |
66 | | - // TODO fix -- this now works in jquery 1.4.2 |
| 66 | + // TODO v.1.1 fix, don't need to use the div any more -- this now works in jquery 1.4.2 |
67 | 67 | $j( this.ui.div ).trigger( 'removeUploadEvent' ); |
68 | 68 | }, |
69 | 69 | |
70 | 70 | |
71 | 71 | /** |
72 | 72 | * Wear our current progress, for observing processes to see |
73 | | - * XXX this is kind of a misnomer; this event is not firing except for the very first time. |
74 | 73 | * @param fraction |
75 | 74 | */ |
76 | 75 | setTransportProgress: function ( fraction ) { |
— | — | @@ -80,6 +79,16 @@ |
81 | 80 | }, |
82 | 81 | |
83 | 82 | /** |
| 83 | + * Queue some warnings for possible later consumption |
| 84 | + */ |
| 85 | + addWarning: function( code, info ) { |
| 86 | + if ( !mw.isDefined( this.warnings ) ) { |
| 87 | + this.warnings = []; |
| 88 | + } |
| 89 | + this.warnings.push( [ code, info ] ); |
| 90 | + }, |
| 91 | + |
| 92 | + /** |
84 | 93 | * Stop the upload -- we have failed for some reason |
85 | 94 | */ |
86 | 95 | setError: function( code, info ) { |
— | — | @@ -95,60 +104,143 @@ |
96 | 105 | setTransported: function( result ) { |
97 | 106 | var _this = this; |
98 | 107 | if ( _this.state == 'aborted' ) { |
99 | | - return ; |
100 | | - } |
101 | | - if ( result.upload && result.upload.imageinfo ) { |
102 | | - mw.log( 'UploadWizard::setTransported> process api imageinfo' ); |
103 | | - // success |
104 | | - _this.state = 'transported'; |
105 | | - _this.transportProgress = 1; |
106 | | - _this.ui.setStatus( 'mwe-upwiz-getting-metadata' ); |
107 | | - _this.extractUploadInfo( result ); |
108 | | - |
109 | | - // use blocking preload for thumbnail, no loading spinner. |
110 | | - _this.getThumbnail( |
111 | | - function( image ) { |
112 | | - _this.ui.setPreview( image ); |
113 | | - _this.deedPreview.setup(); |
114 | | - _this.details.populate(); |
115 | | - _this.state = 'stashed'; |
116 | | - _this.ui.showStashed(); |
117 | | - }, |
118 | | - mw.UploadWizard.config[ 'iconThumbnailWidth' ], |
119 | | - mw.UploadWizard.config[ 'iconThumbnailMaxHeight' ] |
120 | | - ); |
121 | | - |
122 | | - } else if ( result.upload && result.upload.sessionkey ) { |
123 | | - // there was a warning - type error which prevented it from adding the result to the db |
124 | | - if ( result.upload.warnings.duplicate ) { |
125 | | - var duplicates = result.upload.warnings.duplicate; |
126 | | - _this.details.errorDuplicate( result.upload.sessionkey, duplicates ); |
| 108 | + return; |
| 109 | + } |
| 110 | + |
| 111 | + // default error state |
| 112 | + var code = 'unknown'; |
| 113 | + var info = 'unknown'; |
| 114 | + |
| 115 | + if ( result.upload && result.upload.warnings ) { |
| 116 | + if ( result.upload.warnings.exists ) { |
| 117 | + // the filename we uploaded is in use already. Not a problem since we stashed it under a temporary name anyway |
| 118 | + // potentially we could indicate to the upload that it should set the Title field to error state now, but we'll let them deal with that later. |
| 119 | + // however, we don't get imageinfo, so let's try to get it and pretend that we did |
| 120 | + var existsFileName = result.upload.warnings.exists; |
| 121 | + try { |
| 122 | + code = 'exists'; |
| 123 | + info = _this.filenameToUrl( existsFileName ).toString(); |
| 124 | + } catch ( e ) { |
| 125 | + code = 'unknown'; |
| 126 | + info = 'Warned about existing filename, but filename is unparseable: "' + existsFileName + "'"; |
| 127 | + } |
| 128 | + _this.addWarning( code, info ); |
| 129 | + _this.extractUploadInfo( result.upload ); |
| 130 | + var success = function( imageinfo ) { |
| 131 | + if ( imageinfo === null ) { |
| 132 | + _this.setError( 'noimageinfo' ); |
| 133 | + } else { |
| 134 | + result.upload.stashimageinfo = imageinfo; |
| 135 | + _this.setSuccess( result ); |
| 136 | + } |
| 137 | + }; |
| 138 | + _this.getStashImageInfo( success, [ 'timestamp', 'url', 'size', 'dimensions', 'sha1', 'mime', 'metadata', 'bitdepth' ] ); |
| 139 | + } else if ( result.upload.warnings.duplicate ) { |
| 140 | + if ( typeof result.upload.warnings.duplicate == 'object' ) { |
| 141 | + var duplicates = result.upload.warnings.duplicate; |
| 142 | + var $ul = $j( '<ul></ul>' ); |
| 143 | + $j.each( duplicates, function( i, filename ) { |
| 144 | + var $a = $j( '<a/>' ).append( filename ); |
| 145 | + var href; |
| 146 | + try { |
| 147 | + href = _this.filenameToUrl( filename ); |
| 148 | + $a.attr( { 'href': href, 'target': '_blank' } ); |
| 149 | + } catch ( e ) { |
| 150 | + $a.click( function() { alert('could not parse filename=' + filename ); } ); |
| 151 | + $a.attr( 'href', '#' ); |
| 152 | + } |
| 153 | + $ul.append( $j( '<li></li>' ).append( $a ) ); |
| 154 | + } ); |
| 155 | + var dialogFn = function() { |
| 156 | + $j( '<div></div>' ) |
| 157 | + .html( $ul ) |
| 158 | + .dialog( { |
| 159 | + width: 500, |
| 160 | + zIndex: 200000, |
| 161 | + autoOpen: true, |
| 162 | + title: gM( 'mwe-upwiz-api-error-duplicate-popup-title', duplicates.length ), |
| 163 | + modal: true |
| 164 | + } ); |
| 165 | + }; |
| 166 | + code = 'duplicate'; |
| 167 | + info = [ duplicates.length, dialogFn ]; |
| 168 | + } |
| 169 | + _this.setError( code, info ); |
127 | 170 | } |
128 | | - |
129 | | - // and other errors that result in a stash |
| 171 | + } else if ( result.upload && result.upload.result === 'Success' ) { |
| 172 | + if ( result.upload.imageinfo ) { |
| 173 | + _this.setSuccess( result ); |
| 174 | + } else { |
| 175 | + _this.setError( 'noimageinfo' ); |
| 176 | + } |
130 | 177 | } else { |
131 | | - // XXX handle errors better -- get code and pass to showError |
132 | | - var code = 'unknown'; |
133 | | - var info = 'unknown'; |
134 | 178 | if ( result.error ) { |
135 | | - code = result.error.code; |
136 | | - info = result.error.info; |
137 | | - } |
| 179 | + if ( result.error.code ) { |
| 180 | + code = result.error.code; |
| 181 | + } |
| 182 | + if ( result.error.info ) { |
| 183 | + info = result.error.info; |
| 184 | + } |
| 185 | + } |
138 | 186 | _this.setError( code, info ); |
139 | 187 | } |
140 | | - |
| 188 | + |
| 189 | + |
141 | 190 | }, |
142 | 191 | |
143 | 192 | |
144 | 193 | /** |
| 194 | + * Called from any upload success condition |
| 195 | + * @param {Mixed} result -- result of AJAX call |
| 196 | + */ |
| 197 | + setSuccess: function( result ) { |
| 198 | + var _this = this; // was a triumph |
| 199 | + _this.state = 'transported'; |
| 200 | + _this.transportProgress = 1; |
| 201 | + |
| 202 | + // I'm making a note here |
| 203 | + _this.ui.setStatus( 'mwe-upwiz-getting-metadata' ); |
| 204 | + if ( result.upload ) { |
| 205 | + _this.extractUploadInfo( result.upload ); |
| 206 | + // create the small thumbnail used on the 'upload' step |
| 207 | + _this.getThumbnail( |
| 208 | + function( image ) { |
| 209 | + // n.b. if server returns a URL, which is a 404, we do NOT get broken image |
| 210 | + _this.ui.setPreview( image ); // make the thumbnail the preview image |
| 211 | + }, |
| 212 | + mw.UploadWizard.config[ 'iconThumbnailWidth' ], |
| 213 | + mw.UploadWizard.config[ 'iconThumbnailMaxHeight' ] |
| 214 | + ); |
| 215 | + // create the large thumbnail that the other thumbnails link to |
| 216 | + _this.getThumbnail( |
| 217 | + function( image ) {}, |
| 218 | + mw.UploadWizard.config[ 'largeThumbnailWidth' ], |
| 219 | + mw.UploadWizard.config[ 'largeThumbnailMaxHeight' ] |
| 220 | + ); |
| 221 | + _this.deedPreview.setup(); |
| 222 | + _this.details.populate(); |
| 223 | + _this.state = 'stashed'; |
| 224 | + _this.ui.showStashed(); |
| 225 | + } else { |
| 226 | + _this.setError( 'noimageinfo' ); |
| 227 | + } |
| 228 | + |
| 229 | + }, |
| 230 | + |
| 231 | + /** |
145 | 232 | * Called when the file is entered into the file input |
146 | 233 | * Get as much data as possible -- maybe exif, even thumbnail maybe |
147 | 234 | */ |
148 | | - extractLocalFileInfo: function( localFilename ) { |
| 235 | + extractLocalFileInfo: function( filename ) { |
149 | 236 | if ( false ) { // FileAPI, one day |
150 | 237 | this.transportWeight = getFileSize(); |
151 | 238 | } |
152 | | - this.title = new mw.Title( mw.UploadWizardUtil.getBasename( localFilename ), 'file' ); |
| 239 | + // XXX sanitize filename |
| 240 | + try { |
| 241 | + this.title = new mw.Title( mw.UploadWizardUtil.getBasename( filename ).replace( /:/g, '_' ), 'file' ); |
| 242 | + } catch ( e ) { |
| 243 | + this.setError( 'mwe-upwiz-unparseable-filename', filename ); |
| 244 | + } |
153 | 245 | }, |
154 | 246 | |
155 | 247 | /** |
— | — | @@ -156,9 +248,17 @@ |
157 | 249 | * |
158 | 250 | * @param result The JSON object from a successful API upload result. |
159 | 251 | */ |
160 | | - extractUploadInfo: function( result ) { |
161 | | - this.sessionKey = result.upload.sessionkey; |
162 | | - this.extractImageInfo( result.upload.imageinfo ); |
| 252 | + extractUploadInfo: function( resultUpload ) { |
| 253 | + if ( resultUpload.sessionkey ) { |
| 254 | + this.sessionKey = resultUpload.sessionkey; |
| 255 | + } |
| 256 | + if ( resultUpload.imageinfo ) { |
| 257 | + this.extractImageInfo( resultUpload.imageinfo ); |
| 258 | + } else if ( resultUpload.stashimageinfo ) { |
| 259 | + this.extractImageInfo( resultUpload.stashimageinfo ); |
| 260 | + } |
| 261 | + |
| 262 | + |
163 | 263 | }, |
164 | 264 | |
165 | 265 | /** |
— | — | @@ -184,9 +284,10 @@ |
185 | 285 | } |
186 | 286 | } |
187 | 287 | |
188 | | - // TODO this needs to be rethought. |
189 | | - // we should already have an extension, but if we don't... ?? |
190 | 288 | if ( _this.title.getExtension() === null ) { |
| 289 | + 1; |
| 290 | + // TODO v1.1 what if we don't have an extension? Should be impossible as it is currently impossible to upload without extension, but you |
| 291 | + // never know... theoretically there is no restriction on extensions if we are uploading to the stash, but the check is performed anyway. |
191 | 292 | /* |
192 | 293 | var extension = mw.UploadWizardUtil.getExtension( _this.imageinfo.url ); |
193 | 294 | if ( !extension ) { |
— | — | @@ -199,7 +300,58 @@ |
200 | 301 | */ |
201 | 302 | } |
202 | 303 | }, |
| 304 | + |
203 | 305 | /** |
| 306 | + * Get information about stashed images |
| 307 | + * See API documentation for prop=stashimageinfo for what 'props' can contain |
| 308 | + * @param {Function} callback -- called with null if failure, with imageinfo data structure if success |
| 309 | + * @param {Array} properties to extract |
| 310 | + * @param {Number} optional, width of thumbnail. Will force 'url' to be added to props |
| 311 | + * @param {Number} optional, height of thumbnail. Will force 'url' to be added to props |
| 312 | + */ |
| 313 | + getStashImageInfo: function( callback, props, width, height ) { |
| 314 | + var _this = this; |
| 315 | + |
| 316 | + if (!mw.isDefined( props ) ) { |
| 317 | + props = []; |
| 318 | + } |
| 319 | + |
| 320 | + var params = { |
| 321 | + 'prop': 'stashimageinfo', |
| 322 | + 'siisessionkey': _this.sessionKey, |
| 323 | + 'siiprop': props.join( '|' ) |
| 324 | + }; |
| 325 | + |
| 326 | + if ( mw.isDefined( width ) || mw.isDefined( height ) ) { |
| 327 | + if ( ! $j.inArray( 'url', props ) ) { |
| 328 | + props.push( 'url' ); |
| 329 | + } |
| 330 | + if ( mw.isDefined( width ) ) { |
| 331 | + params['siiurlwidth'] = width; |
| 332 | + } |
| 333 | + if ( mw.isDefined( height ) ) { |
| 334 | + params['siiurlheight'] = height; |
| 335 | + } |
| 336 | + } |
| 337 | + |
| 338 | + var ok = function( data ) { |
| 339 | + if ( !data || !data.query || !data.query.stashimageinfo ) { |
| 340 | + mw.log("mw.UploadWizardUpload::getStashImageInfo> No data? "); |
| 341 | + callback( null ); |
| 342 | + return; |
| 343 | + } |
| 344 | + callback( data.query.stashimageinfo ); |
| 345 | + }; |
| 346 | + |
| 347 | + var err = function( code, result ) { |
| 348 | + mw.log( 'mw.UploadWizardUpload::getStashImageInfo> error: ' + code, 'debug' ); |
| 349 | + callback( null ); |
| 350 | + }; |
| 351 | + |
| 352 | + this.api.get( params, { ok: ok, err: err } ); |
| 353 | + }, |
| 354 | + |
| 355 | + /** |
204 | 356 | * Set the upload handler per browser capabilities |
205 | 357 | */ |
206 | 358 | getUploadHandler: function(){ |
— | — | @@ -218,12 +370,12 @@ |
219 | 371 | } |
220 | 372 | return this.uploadHandler; |
221 | 373 | }, |
222 | | - |
| 374 | + |
223 | 375 | /** |
224 | 376 | * Fetch a thumbnail for a stashed upload of the desired width. |
225 | 377 | * It is assumed you don't call this until it's been transported. |
226 | 378 | * |
227 | | - * @param callback - callback to execute once thumbnail has been obtained -- must accept Image object |
| 379 | + * @param callback - callback to execute once thumbnail has been obtained -- must accept Image object for success, null for error |
228 | 380 | * @param width - desired width of thumbnail (height will scale to match) |
229 | 381 | * @param height - (optional) maximum height of thumbnail |
230 | 382 | */ |
— | — | @@ -236,38 +388,29 @@ |
237 | 389 | if ( mw.isDefined( _this.thumbnails[key] ) ) { |
238 | 390 | callback( _this.thumbnails[key] ); |
239 | 391 | } else { |
240 | | - var params = { |
241 | | - 'prop': 'stashimageinfo', |
242 | | - 'siisessionkey': _this.sessionKey, |
243 | | - 'siiurlwidth': width, |
244 | | - 'siiurlheight': height, |
245 | | - 'siiprop': 'url' |
246 | | - }; |
247 | | - |
248 | | - this.api.get( params, function( data ) { |
249 | | - if ( !data || !data.query || !data.query.stashimageinfo ) { |
250 | | - mw.log("mw.UploadWizardUpload::getThumbnail> No data? "); |
251 | | - // XXX do something about the thumbnail spinner, maybe call the callback with a broken image. |
252 | | - return; |
253 | | - } |
254 | | - var thumbnails = data.query.stashimageinfo; |
255 | | - for ( var i = 0; i < thumbnails.length; i++ ) { |
256 | | - var thumb = thumbnails[i]; |
257 | | - if ( ! ( thumb.thumburl && thumb.thumbwidth && thumb.thumbheight ) ) { |
258 | | - mw.log( "mw.UploadWizardUpload::getThumbnail> thumbnail missing information" ); |
259 | | - // XXX error |
260 | | - return ; |
| 392 | + var apiCallback = function( thumbnails ) { |
| 393 | + if ( thumbnails === null ) { |
| 394 | + callback( null ); |
| 395 | + } else { |
| 396 | + for ( var i = 0; i < thumbnails.length; i++ ) { |
| 397 | + var thumb = thumbnails[i]; |
| 398 | + if ( thumb.thumberror || ( ! ( thumb.thumburl && thumb.thumbwidth && thumb.thumbheight ) ) ) { |
| 399 | + mw.log( "mw.UploadWizardUpload::getThumbnail> thumbnail error or missing information" ); |
| 400 | + callback( null ); |
| 401 | + return; |
| 402 | + } |
| 403 | + var image = document.createElement( 'img' ); |
| 404 | + $j( image ).load( function() { |
| 405 | + callback( image ); |
| 406 | + } ); |
| 407 | + image.width = thumb.thumbwidth; |
| 408 | + image.height = thumb.thumbheight; |
| 409 | + image.src = thumb.thumburl; |
| 410 | + _this.thumbnails[key] = image; |
261 | 411 | } |
262 | | - var image = document.createElement( 'img' ); |
263 | | - $j( image ).load( function() { |
264 | | - callback( image ); |
265 | | - } ); |
266 | | - image.width = thumb.thumbwidth; |
267 | | - image.height = thumb.thumbheight; |
268 | | - image.src = thumb.thumburl; |
269 | | - _this.thumbnails[key] = image; |
270 | 412 | } |
271 | | - } ); |
| 413 | + }; |
| 414 | + _this.getStashImageInfo( apiCallback, [ 'url' ], width, height ); |
272 | 415 | } |
273 | 416 | }, |
274 | 417 | |
— | — | @@ -279,32 +422,58 @@ |
280 | 423 | * @param height (optional) |
281 | 424 | */ |
282 | 425 | setThumbnail: function( selector, width, height ) { |
283 | | - |
284 | 426 | var _this = this; |
285 | 427 | if ( typeof width === 'undefined' || width === null || width <= 0 ) { |
286 | | - width = mw.UploadWizard.config[ 'thumbnailWidth' ]; |
| 428 | + width = mw.UploadWizard.config[ 'thumbnailWidth' ]; |
287 | 429 | } |
288 | 430 | width = parseInt( width, 10 ); |
289 | 431 | height = null; |
290 | 432 | if ( !mw.isEmpty( height ) ) { |
291 | 433 | height = parseInt( height, 10 ); |
292 | 434 | } |
293 | | - |
| 435 | + |
| 436 | + var href = '#'; |
| 437 | + $j.each( [ 'descriptionurl', 'url' ], function( i, propName ) { |
| 438 | + var prop = _this.imageinfo[ propName ]; |
| 439 | + if ( prop ) { |
| 440 | + href = prop; |
| 441 | + return false; |
| 442 | + } |
| 443 | + } ); |
| 444 | + |
294 | 445 | var callback = function( image ) { |
295 | | - $j( selector ).html( |
296 | | - $j( '<a/>' ) |
297 | | - .attr( { 'href': _this.imageinfo.url, |
298 | | - 'target' : '_new' } ) |
299 | | - .append( |
300 | | - $j( '<img/>' ) |
301 | | - .attr( { 'width': image.width, |
302 | | - 'height': image.height, |
303 | | - 'src': image.src } ) |
304 | | - ) |
305 | | - ); |
| 446 | + if ( image === null ) { |
| 447 | + $j( selector ).addClass( 'mwe-upwiz-file-preview-broken' ); |
| 448 | + _this.ui.setStatus( 'mwe-upwiz-thumbnail-failed' ); |
| 449 | + } else { |
| 450 | + $j( selector ).html( |
| 451 | + $j( '<a/>' ) |
| 452 | + .attr( { 'href': href, |
| 453 | + 'target' : '_new' } ) |
| 454 | + .append( |
| 455 | + $j( '<img/>' ) |
| 456 | + .attr( { 'width': image.width, |
| 457 | + 'height': image.height, |
| 458 | + 'src': image.src } ) |
| 459 | + ) |
| 460 | + ); |
| 461 | + } |
306 | 462 | }; |
307 | 463 | |
308 | 464 | _this.getThumbnail( callback, width, height ); |
| 465 | + }, |
| 466 | + |
| 467 | + /** |
| 468 | + * Given a filename like "Foo.jpg", get the URL to that filename, assuming the browser is on the same wiki. |
| 469 | + * Candidate for a utility function... |
| 470 | + * @param {String} filename |
| 471 | + */ |
| 472 | + filenameToUrl: function( filename ) { |
| 473 | + var fileUrl = new mw.Uri( document.URL ); |
| 474 | + fileUrl.path = wgScript; |
| 475 | + var fileTitle = new mw.Title( filename, 'file' ); |
| 476 | + fileUrl.query = { title: fileTitle, action: 'view' }; |
| 477 | + return fileUrl; |
309 | 478 | } |
310 | 479 | |
311 | 480 | }; |
— | — | @@ -344,7 +513,7 @@ |
345 | 514 | * Depending on whether we split uploading / detailing, it may actually always be as simple as loading a URL |
346 | 515 | */ |
347 | 516 | reset: function() { |
348 | | - window.location.reload(); |
| 517 | + window.location = wgArticlePath.replace( '$1', 'Special:UploadWizard?skiptutorial=true' ); |
349 | 518 | }, |
350 | 519 | |
351 | 520 | |
— | — | @@ -352,9 +521,22 @@ |
353 | 522 | * create the basic interface to make an upload in this div |
354 | 523 | * @param div The div in the DOM to put all of this into. |
355 | 524 | */ |
356 | | - createInterface: function( selector ) { |
| 525 | +createInterface: function( selector ) { |
357 | 526 | var _this = this; |
| 527 | + |
| 528 | + // remove first spinner |
| 529 | + $j( '#mwe-first-spinner' ).remove(); |
358 | 530 | |
| 531 | + // feedback request |
| 532 | + if ( UploadWizardConfig['feedbackPage'] != '' ) { |
| 533 | + $j( '#contentSub' ).html('<i>Please <a id="mwe-upwiz-feedback" href="#">let us know</a> what you think of Upload Wizard!</i>'); |
| 534 | + $j( '#mwe-upwiz-feedback') |
| 535 | + .click( function() { |
| 536 | + _this.launchFeedback(); |
| 537 | + return false; |
| 538 | + } ); |
| 539 | + } |
| 540 | + |
359 | 541 | // construct the arrow steps from the UL in the HTML |
360 | 542 | $j( '#mwe-upwiz-steps' ) |
361 | 543 | .addClass( 'ui-helper-clearfix ui-state-default ui-widget ui-helper-reset ui-helper-clearfix' ) |
— | — | @@ -390,8 +572,14 @@ |
391 | 573 | .click( function() { |
392 | 574 | // check if there is an upload at all (should never happen) |
393 | 575 | if ( _this.uploads.length === 0 ) { |
394 | | - // XXX use standard error message |
395 | | - alert( gM( 'mwe-upwiz-file-need-file' ) ); |
| 576 | + $j( '<div></div>' ) |
| 577 | + .html( gM( 'mwe-upwiz-file-need-file' ) ) |
| 578 | + .dialog({ |
| 579 | + width: 500, |
| 580 | + zIndex: 200000, |
| 581 | + autoOpen: true, |
| 582 | + modal: true |
| 583 | + }); |
396 | 584 | return; |
397 | 585 | } |
398 | 586 | |
— | — | @@ -413,7 +601,7 @@ |
414 | 602 | |
415 | 603 | $j( '#mwe-upwiz-stepdiv-deeds .mwe-upwiz-button-next') |
416 | 604 | .click( function() { |
417 | | - $('.mwe-upwiz-hint').each( function(i) { $(this).tipsy('hide') } ); // close tipsy help balloons |
| 605 | + $j( '.mwe-upwiz-hint' ).each( function(i) { $j( this ).tipsy( 'hide' ); } ); // close tipsy help balloons |
418 | 606 | // validate has the side effect of notifying the user of problems, or removing existing notifications. |
419 | 607 | // if returns false, you can assume there are notifications in the interface. |
420 | 608 | if ( _this.deedChooser.valid() ) { |
— | — | @@ -446,7 +634,7 @@ |
447 | 635 | |
448 | 636 | $j( '#mwe-upwiz-stepdiv-details .mwe-upwiz-button-next' ) |
449 | 637 | .click( function() { |
450 | | - $('.mwe-upwiz-hint').each( function(i) { $(this).tipsy('hide') } ); // close tipsy help balloons |
| 638 | + $j( '.mwe-upwiz-hint' ).each( function(i) { $j( this ).tipsy( 'hide' ); } ); // close tipsy help balloons |
451 | 639 | if ( _this.detailsValid() ) { |
452 | 640 | _this.detailsSubmit( function() { |
453 | 641 | _this.prefillThanksPage(); |
— | — | @@ -460,7 +648,7 @@ |
461 | 649 | // WIZARD |
462 | 650 | |
463 | 651 | // check to see if the the skip tutorial cookie is set |
464 | | - if ( document.cookie.indexOf('skiptutorial=1') != -1 ) { |
| 652 | + if ( document.cookie.indexOf('skiptutorial=1') != -1 || UploadWizardConfig['skipTutorial'] ) { |
465 | 653 | // "select" the second step - highlight, make it visible, hide all others |
466 | 654 | _this.moveToStep( 'file' ); |
467 | 655 | } else { |
— | — | @@ -471,6 +659,7 @@ |
472 | 660 | }, |
473 | 661 | |
474 | 662 | |
| 663 | + |
475 | 664 | // do some last minute prep before advancing to the DEEDS page |
476 | 665 | prepareAndMoveToDeeds: function() { |
477 | 666 | var _this = this; |
— | — | @@ -496,14 +685,14 @@ |
497 | 686 | deeds, |
498 | 687 | _this.uploads.length ); |
499 | 688 | |
500 | | - $j( '<div>' ) |
| 689 | + $j( '<div></div>' ) |
501 | 690 | .insertBefore( _this.deedChooser.$selector.find( '.mwe-upwiz-deed-ownwork' ) ) |
502 | | - .html( gM( 'mwe-upwiz-deeds-macro-prompt', _this.uploads.length ) ); |
| 691 | + .msg( 'mwe-upwiz-deeds-macro-prompt', _this.uploads.length ); |
503 | 692 | |
504 | 693 | if ( _this.uploads.length > 1 ) { |
505 | | - $j( '<div style="margin-top: 1em">' ) |
| 694 | + $j( '<div style="margin-top: 1em"></div>' ) |
506 | 695 | .insertBefore( _this.deedChooser.$selector.find( '.mwe-upwiz-deed-custom' ) ) |
507 | | - .html( gM( 'mwe-upwiz-deeds-custom-prompt' ) ); |
| 696 | + .msg( 'mwe-upwiz-deeds-custom-prompt' ); |
508 | 697 | } |
509 | 698 | |
510 | 699 | _this.moveToStep( 'deeds' ); |
— | — | @@ -582,13 +771,9 @@ |
583 | 772 | $j( upload.ui.div ).bind( 'filenameAccepted', function(e) { _this.updateFileCounts(); e.stopPropagation(); } ); |
584 | 773 | $j( upload.ui.div ).bind( 'removeUploadEvent', function(e) { _this.removeUpload( upload ); e.stopPropagation(); } ); |
585 | 774 | $j( upload.ui.div ).bind( 'filled', function(e) { |
586 | | - mw.log( "mw.UploadWizardUpload::newUpload> filled! received!" ); |
587 | 775 | _this.newUpload(); |
588 | | - mw.log( "mw.UploadWizardUpload::newUpload> filled! new upload!" ); |
589 | 776 | _this.setUploadFilled(upload); |
590 | | - mw.log( "mw.UploadWizardUpload::newUpload> filled! set upload filled!" ); |
591 | 777 | e.stopPropagation(); |
592 | | - mw.log( "mw.UploadWizardUpload::newUpload> filled! stop propagation!" ); |
593 | 778 | } ); |
594 | 779 | // XXX bind to some error state |
595 | 780 | |
— | — | @@ -615,9 +800,7 @@ |
616 | 801 | |
617 | 802 | upload.deedPreview = new mw.UploadWizardDeedPreview( upload ); |
618 | 803 | |
619 | | - // XXX do we really need to do this now? some things will even change after step 2. |
620 | | - // legacy. |
621 | | - // set up details |
| 804 | + // TODO v1.1 consider if we really have to set up details now |
622 | 805 | upload.details = new mw.UploadWizardDetails( upload, $j( '#mwe-upwiz-macro-files' ) ); |
623 | 806 | }, |
624 | 807 | |
— | — | @@ -854,7 +1037,7 @@ |
855 | 1038 | $j( '#mwe-upwiz-upload-ctrl-container' ).show(); |
856 | 1039 | |
857 | 1040 | // changes the "click here to add files" to "add another file" |
858 | | - $j( '#mwe-upwiz-add-file span' ).html( gM( 'mwe-upwiz-add-file-n' ) ); |
| 1041 | + $j( '#mwe-upwiz-add-file span' ).msg( 'mwe-upwiz-add-file-n' ); |
859 | 1042 | $j( '#mwe-upwiz-add-file-container' ).removeClass('mwe-upwiz-add-files-0'); |
860 | 1043 | $j( '#mwe-upwiz-add-file-container' ).addClass('mwe-upwiz-add-files-n'); |
861 | 1044 | |
— | — | @@ -881,7 +1064,7 @@ |
882 | 1065 | $j( '#mwe-upwiz-stepdiv-file .mwe-upwiz-buttons' ).hide(); |
883 | 1066 | |
884 | 1067 | // change "add another file" into "click here to add a file" |
885 | | - $j( '#mwe-upwiz-add-file' ).html( gM( 'mwe-upwiz-add-file-0' ) ); |
| 1068 | + $j( '#mwe-upwiz-add-file span' ).msg( 'mwe-upwiz-add-file-0' ); |
886 | 1069 | $j( '#mwe-upwiz-add-file-container' ).addClass('mwe-upwiz-add-files-0'); |
887 | 1070 | $j( '#mwe-upwiz-add-file-container' ).removeClass('mwe-upwiz-add-files-n'); |
888 | 1071 | } |
— | — | @@ -922,8 +1105,6 @@ |
923 | 1106 | // some details blocks cannot be submitted (for instance, identical file hash) |
924 | 1107 | _this.removeBlockedDetails(); |
925 | 1108 | |
926 | | - // XXX validate all |
927 | | - |
928 | 1109 | // remove ability to edit details |
929 | 1110 | $j.each( _this.uploads, function( i, upload ) { |
930 | 1111 | upload.details.div.mask(); |
— | — | @@ -969,23 +1150,27 @@ |
970 | 1151 | var _this = this; |
971 | 1152 | |
972 | 1153 | $j( '#mwe-upwiz-thanks' ) |
973 | | - .append( $j( '<h3 style="text-align: center;">' ).append( gM( 'mwe-upwiz-thanks-intro' ) ), |
| 1154 | + .append( $j( '<h3 style="text-align: center;"></h3>' ).msg( 'mwe-upwiz-thanks-intro' ), |
974 | 1155 | $j( '<p style="margin-bottom: 2em; text-align: center;">' ) |
975 | | - .append( gM( 'mwe-upwiz-thanks-explain', _this.uploads.length ) ) ); |
| 1156 | + .msg( 'mwe-upwiz-thanks-explain', _this.uploads.length ) ); |
976 | 1157 | |
977 | 1158 | $j.each( _this.uploads, function(i, upload) { |
978 | | - var thanksDiv = $j( '<div class="mwe-upwiz-thanks ui-helper-clearfix" />' ); |
979 | | - _this.thanksDiv = thanksDiv; |
| 1159 | + var id = 'thanksDiv' + i; |
| 1160 | + var $thanksDiv = $j( '<div></div>' ).attr( 'id', id ).addClass( "mwe-upwiz-thanks ui-helper-clearfix" ); |
| 1161 | + _this.thanksDiv = $thanksDiv; |
980 | 1162 | |
981 | | - var thumbnailDiv = $j( '<div class="mwe-upwiz-thumbnail mwe-upwiz-thumbnail-side" id="thanks-thumbnail"></div>' ); |
982 | | - upload.setThumbnail( thumbnailDiv ); |
| 1163 | + var $thumbnailDiv = $j( '<div class="mwe-upwiz-thumbnail mwe-upwiz-thumbnail-side"></div>' ); |
| 1164 | + $thanksDiv.append( $thumbnailDiv ); |
| 1165 | + upload.setThumbnail( $thumbnailDiv ); |
| 1166 | + //upload.setThumbnail( '#' + id + ' .mwe-upwiz-thumbnail' ); |
983 | 1167 | |
984 | | - thanksDiv.append( thumbnailDiv ); |
| 1168 | + // Switch the thumbnail link so that it points to the image description page |
| 1169 | + $thumbnailDiv.find( 'a' ).attr( 'href', upload.imageinfo.descriptionurl ); |
985 | 1170 | |
986 | 1171 | var thumbTitle = String(upload.title); |
987 | 1172 | var thumbWikiText = "[[" + thumbTitle.replace('_', ' ') + "|thumb|" + gM( 'mwe-upwiz-thanks-caption' ) + "]]"; |
988 | 1173 | |
989 | | - thanksDiv.append( |
| 1174 | + $thanksDiv.append( |
990 | 1175 | $j( '<div class="mwe-upwiz-data"></div>' ) |
991 | 1176 | .append( |
992 | 1177 | $j('<p/>').append( |
— | — | @@ -1009,13 +1194,87 @@ |
1010 | 1195 | ) |
1011 | 1196 | ); |
1012 | 1197 | |
1013 | | - $j( '#mwe-upwiz-thanks' ).append( thanksDiv ); |
1014 | | - // Switch the thumbnail link so that it points to the image description page |
1015 | | - $j( '#thanks-thumbnail a' ).attr( 'href', upload.imageinfo.descriptionurl ); |
| 1198 | + $j( '#mwe-upwiz-thanks' ).append( $thanksDiv ); |
1016 | 1199 | } ); |
1017 | 1200 | }, |
1018 | 1201 | |
1019 | 1202 | /** |
| 1203 | + * Build interface for collecting user feedback on Upload Wizard |
| 1204 | + */ |
| 1205 | + launchFeedback: function() { |
| 1206 | + _this = this; |
| 1207 | + |
| 1208 | + var displayError = function( message ) { |
| 1209 | + $j( '#mwe-upwiz-feedback-form div' ).hide(); // remove everything else from the dialog box |
| 1210 | + $j( '#mwe-upwiz-feedback-form' ).append ( $j( '<div style="color:#990000;margin-top:0.4em;"></div>' ).msg( message ) ); |
| 1211 | + } |
| 1212 | + |
| 1213 | + // Set up buttons for dialog box. We have to do it the hard way since the json keys are localized |
| 1214 | + var cancelButton = gM( 'mwe-upwiz-feedback-cancel' ); |
| 1215 | + var submitButton = gM( 'mwe-upwiz-feedback-submit' ); |
| 1216 | + var buttonSettings = {}; |
| 1217 | + buttonSettings[cancelButton] = function() { $j( this ).dialog( 'close' ); }; |
| 1218 | + buttonSettings[submitButton] = function() { |
| 1219 | + $feedbackForm.dialog({buttons:{}}); |
| 1220 | + $j( '#mwe-upwiz-feedback-form div' ).hide(); // remove everything else from the dialog box |
| 1221 | + $j( '#mwe-upwiz-feedback-form' ).append ( $j( '<div style="text-align:center;margin:3em 0;"></div>' ).append( gM( 'mwe-upwiz-feedback-adding' ), $j( '<br/>' ), $j( '<img src="http://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" />' ) ) ); |
| 1222 | + var subject = $j( '#mwe-upwiz-feedback-subject' ).val(); |
| 1223 | + var message = $j( '#mwe-upwiz-feedback-message' ).val(); |
| 1224 | + if ( message.indexOf( '~~~' ) == -1 ) { |
| 1225 | + message = message+' ~~~~'; |
| 1226 | + } |
| 1227 | + var useTokenToPostFeedback = function( token ) { |
| 1228 | + $j.ajax({ |
| 1229 | + url: wgScriptPath + '/api.php', |
| 1230 | + data: $.param({ |
| 1231 | + action: 'edit', |
| 1232 | + title: mw.UploadWizard.config['feedbackPage'], |
| 1233 | + section: 'new', |
| 1234 | + summary: subject, |
| 1235 | + text: message, |
| 1236 | + format: 'json', |
| 1237 | + token: token |
| 1238 | + }), |
| 1239 | + dataType: 'json', |
| 1240 | + type: 'POST', |
| 1241 | + success: function( data ) { |
| 1242 | + if ( typeof data.edit != 'undefined' ) { |
| 1243 | + if ( data.edit.result == 'Success' ) { |
| 1244 | + $feedbackForm.dialog( 'close' ); // edit complete, close dialog box |
| 1245 | + } else { |
| 1246 | + displayError( 'mwe-upwiz-feedback-error1' ); // unknown API result |
| 1247 | + } |
| 1248 | + } else { |
| 1249 | + displayError( 'mwe-upwiz-feedback-error2' ); // edit failed |
| 1250 | + } |
| 1251 | + }, |
| 1252 | + error: function( xhr ) { |
| 1253 | + displayError( 'mwe-upwiz-feedback-error3' ); // ajax request failed |
| 1254 | + } |
| 1255 | + }); // close Ajax request |
| 1256 | + }; // close useTokenToPost function |
| 1257 | + _this.api.getEditToken( useTokenToPostFeedback ); |
| 1258 | + }; // close submit button function |
| 1259 | + |
| 1260 | + // Construct the feedback form |
| 1261 | + var feedbackLink = '<a href="'+wgArticlePath.replace( '$1', mw.UploadWizard.config['feedbackPage'].replace( /\s/g, '_' ) )+'" target="_blank">'+mw.UploadWizard.config['feedbackPage']+'</a>'; |
| 1262 | + $feedbackForm = $j( '<div id="mwe-upwiz-feedback-form" style="position:relative;"></div>' ) |
| 1263 | + .append( $j( '<div style="margin-top:0.4em;"></div>' ).append( $j( '<small></small>' ).msg( 'mwe-upwiz-feedback-note', feedbackLink ) ) ) |
| 1264 | + .append( $j( '<div style="margin-top:1em;"></div>' ).append( gM( 'mwe-upwiz-feedback-subject' ), $j( '<br/>' ), $j( '<input type="text" id="mwe-upwiz-feedback-subject" name="subject" maxlength="60" style="width:99%;"/>' ) ) ) |
| 1265 | + .append( $j( '<div style="margin-top:0.4em;"></div>' ).append( gM( 'mwe-upwiz-feedback-message' ), $j( '<br/>' ), $j( '<textarea name="message" id="mwe-upwiz-feedback-message" style="width:99%;" rows="4" cols="60"></textarea>' ) ) ) |
| 1266 | + .dialog({ |
| 1267 | + width: 500, |
| 1268 | + autoOpen: false, |
| 1269 | + title: gM( 'mwe-upwiz-feedback-title' ), |
| 1270 | + modal: true, |
| 1271 | + buttons: buttonSettings |
| 1272 | + }); // close dialog, end $feedbackForm definition |
| 1273 | + |
| 1274 | + $feedbackForm.dialog( 'open' ); |
| 1275 | + |
| 1276 | + }, // close launchFeedback function |
| 1277 | + |
| 1278 | + /** |
1020 | 1279 | * Set a cookie which lets the user skip the tutorial step in the future |
1021 | 1280 | */ |
1022 | 1281 | setSkipTutorialCookie: function() { |
— | — | @@ -1038,6 +1297,7 @@ |
1039 | 1298 | stop: function() { |
1040 | 1299 | |
1041 | 1300 | } |
| 1301 | + |
1042 | 1302 | }; |
1043 | 1303 | |
1044 | 1304 | |
— | — | @@ -1118,13 +1378,30 @@ |
1119 | 1379 | }; |
1120 | 1380 | |
1121 | 1381 | /** |
1122 | | - * Adds a tipsy pop-up help button to the field. |
1123 | | - * @param name The short name of the field, for example, 'title'. This should correspond with a |
1124 | | - * message key of the form 'mwe-upwiz-tooltip-<name>'. |
| 1382 | + * Adds a tipsy pop-up help button to the field. Can be called in two ways -- with simple string id, which identifies |
| 1383 | + * the string as 'mwe-upwiz-tooltip-' plus that id, and creates the hint with a similar id |
| 1384 | + * or with function and id -- function will be called to generate the hint every time |
| 1385 | + * TODO v1.1 split into two plugins? |
| 1386 | + * @param key {string} -- will base the tooltip on a message found with this key |
| 1387 | + * @param fn {function} optional -- call this function every time tip is created to generate message. If present HTML element gets an id of the exact key specified |
1125 | 1388 | */ |
1126 | | - $j.fn.addHint = function( name ) { |
1127 | | - return this.append( $j( '<span class="mwe-upwiz-hint" id="mwe-upwiz-'+name+'-hint" onclick=\"$(this).tipsy(\'toggle\');return false;\">' ) |
1128 | | - .attr( 'title', gM( 'mwe-upwiz-tooltip-'+name ) ).tipsy( {html: true, opacity: 0.9, gravity: 'sw', trigger: 'manual'} ) ); |
| 1389 | + $j.fn.addHint = function( key, fn ) { |
| 1390 | + var attrs, contentSource, html = false; |
| 1391 | + if ( typeof fn === 'function' ) { |
| 1392 | + attrs = { id: key }; |
| 1393 | + contentSource = fn; |
| 1394 | + html = true; |
| 1395 | + } else { |
| 1396 | + attrs = { 'title': gM( 'mwe-upwiz-tooltip-' + key ) }; |
| 1397 | + contentSource = 'title'; |
| 1398 | + } |
| 1399 | + return this.append( |
| 1400 | + $j( '<span/>' ) |
| 1401 | + .addClass( 'mwe-upwiz-hint' ) |
| 1402 | + .attr( attrs ) |
| 1403 | + .click( function() { $j( this ).tipsy( 'toggle' ); return false; } ) |
| 1404 | + .tipsy( { title: contentSource, html: html, opacity: 1.0, gravity: 'sw', trigger: 'manual'} ) |
| 1405 | + ); |
1129 | 1406 | }; |
1130 | 1407 | |
1131 | 1408 | /** |
— | — | @@ -1183,7 +1460,7 @@ |
1184 | 1461 | $j( el ).find( "select" ).addClass( "masked-hidden" ); |
1185 | 1462 | } |
1186 | 1463 | |
1187 | | - var mask = $j( '<div class="mwe-upwiz-mask"/>' ) |
| 1464 | + var mask = $j( '<div class="mwe-upwiz-mask"></div>' ) |
1188 | 1465 | .css( { |
1189 | 1466 | 'backgroundColor' : 'white', |
1190 | 1467 | 'width' : el.offsetWidth + 'px', |
— | — | @@ -1191,7 +1468,7 @@ |
1192 | 1469 | 'z-index' : 90 |
1193 | 1470 | } ); |
1194 | 1471 | |
1195 | | - var status = $j( '<div class="mwe-upwiz-status"/>' ) |
| 1472 | + var status = $j( '<div class="mwe-upwiz-status"></div>' ) |
1196 | 1473 | .css( { |
1197 | 1474 | 'width' : el.offsetWidth + 'px', |
1198 | 1475 | 'height' : el.offsetHeight + 'px', |
Index: branches/uploadwizard-firefogg/resources/mw.UploadWizardDetails.js |
— | — | @@ -45,7 +45,7 @@ |
46 | 46 | // http://commons.wikimedia.org/wiki/MediaWiki:Filename-prefix-blacklist |
47 | 47 | // XXX make sure they can't use ctrl characters or returns or any other bad stuff. |
48 | 48 | _this.titleId = "title" + _this.upload.index; |
49 | | - _this.titleInput = $j( '<textarea type="text" id="' + _this.titleId + '" name="' + _this.titleId + '" rows="1" class="mwe-title mwe-long-textarea"></textarea>' ) |
| 49 | + _this.titleInput = $j( '<input type="text" id="' + _this.titleId + '" name="' + _this.titleId + '" class="mwe-title"/>' ) |
50 | 50 | .keyup( function() { |
51 | 51 | _this.upload.title.setNameText( _this.titleInput.value ); |
52 | 52 | // TODO update a display of filename |
— | — | @@ -54,22 +54,49 @@ |
55 | 55 | api: _this.upload.api, |
56 | 56 | spinner: function(bool) { _this.toggleDestinationBusy(bool); }, |
57 | 57 | preprocess: function( name ) { |
58 | | - // turn the contents of the input into a MediaWiki title ("File:foo_bar.jpg") to look up |
59 | | - return _this.upload.title.setNameText( name ).toString(); |
| 58 | + if ( name != '' ) { |
| 59 | + // turn the contents of the input into a MediaWiki title ("File:foo_bar.jpg") to look up |
| 60 | + return _this.upload.title.setNameText( name ).toString(); |
| 61 | + } else { |
| 62 | + return name; |
| 63 | + } |
60 | 64 | }, |
61 | 65 | processResult: function( result ) { _this.processDestinationCheck( result ); } |
62 | 66 | } ); |
63 | 67 | |
64 | | - _this.titleErrorDiv = $j('<div class="mwe-upwiz-details-input-error"><label class="mwe-error" for="' + _this.titleId + '" generated="true"/></div>'); |
| 68 | + _this.titleErrorDiv = $j('<div class="mwe-upwiz-details-input-error"><label class="mwe-validator-error" for="' + _this.titleId + '" generated="true"/></div>'); |
65 | 69 | |
| 70 | + var titleHintId = 'mwe-upwiz-title-hint-' + _this.upload.index; |
| 71 | + var $titleDialog = $('<div>') |
| 72 | + .html( gM( 'mwe-upwiz-dialog-title' ) ) |
| 73 | + .dialog({ |
| 74 | + width: 500, |
| 75 | + zIndex: 200000, |
| 76 | + autoOpen: false, |
| 77 | + title: gM( 'mwe-upwiz-help-popup' ) + ': ' + gM( 'mwe-upwiz-help-popup-title' ), |
| 78 | + modal: true |
| 79 | + }) |
| 80 | + .bind( "dialogclose", function( event, ui ) { |
| 81 | + $j( '#' + titleHintId ).tipsy( "hide" ); |
| 82 | + }); |
| 83 | + |
| 84 | + // tipsy hides tips by removing them from the DOM. This causes all bindings to be lost. |
| 85 | + // so we send a function to recreate everything, every time! |
| 86 | + // (is it really necessary for tipsy to remove elements?) |
| 87 | + var titleHinter = function() { |
| 88 | + return $j( '<span>' ).msg( 'mwe-upwiz-tooltip-title', function() { |
| 89 | + $titleDialog.dialog( 'open' ); |
| 90 | + // TODO scroll to the dialog, or otherwise ensure it's in the middle of the page no matter what |
| 91 | + } ); |
| 92 | + }; |
| 93 | + |
66 | 94 | var titleContainerDiv = $j('<div class="mwe-upwiz-details-fieldname-input ui-helper-clearfix"></div>') |
67 | 95 | .append( |
68 | 96 | _this.titleErrorDiv, |
69 | 97 | $j( '<div class="mwe-upwiz-details-fieldname"></div>' ) |
| 98 | + .msg( 'mwe-upwiz-title' ) |
70 | 99 | .requiredFieldLabel() |
71 | | - .append( gM( 'mwe-upwiz-title' ) ) |
72 | | - .addHint( 'title' ), |
73 | | - |
| 100 | + .addHint( titleHintId, titleHinter ), |
74 | 101 | $j( '<div class="mwe-upwiz-details-input"></div>' ).append( _this.titleInput ) |
75 | 102 | ); |
76 | 103 | |
— | — | @@ -86,7 +113,13 @@ |
87 | 114 | + '<div class="mwe-upwiz-details-fieldname"></div>' |
88 | 115 | + '<div class="mwe-upwiz-details-input"></div>' |
89 | 116 | + '</div>' ); |
90 | | - $categoriesDiv.find( '.mwe-upwiz-details-fieldname' ).append( gM( 'mwe-upwiz-categories' ) ).addHint( 'categories' ); |
| 117 | + var commonsCategoriesLink = $j( '<a>' ).attr( { 'target': '_blank', 'href': 'http://commons.wikimedia.org/wiki/Commons:Categories' } ); |
| 118 | + var categoriesHint = $j( '<span>' ).msg( 'mwe-upwiz-tooltip-categories', commonsCategoriesLink ); |
| 119 | + var categoriesHinter = function() { return categoriesHint; }; |
| 120 | + $categoriesDiv |
| 121 | + .find( '.mwe-upwiz-details-fieldname' ) |
| 122 | + .append( gM( 'mwe-upwiz-categories' ) ) |
| 123 | + .addHint( 'mwe-upwiz-categories-hint', categoriesHinter ); |
91 | 124 | var categoriesId = 'categories' + _this.upload.index; |
92 | 125 | $categoriesDiv.find( '.mwe-upwiz-details-input' ) |
93 | 126 | .append( $j( '<input/>' ).attr( { id: categoriesId, |
— | — | @@ -132,19 +165,7 @@ |
133 | 166 | otherInformationDiv |
134 | 167 | ); |
135 | 168 | |
136 | | - $titleDialog = $('<div>') |
137 | | - .html( gM( 'mwe-upwiz-dialog-title' ) ) |
138 | | - .dialog({ |
139 | | - width: 500, |
140 | | - zIndex: 200000, |
141 | | - autoOpen: false, |
142 | | - title: 'Help: Title', |
143 | | - modal: true |
144 | | - }) |
145 | | - .bind( "dialogclose", function(event, ui) { |
146 | | - $("#mwe-upwiz-title-hint").tipsy("hide"); |
147 | | - }); |
148 | | - |
| 169 | + |
149 | 170 | /* Build the form for the file upload */ |
150 | 171 | _this.$form = $j( '<form></form>' ); |
151 | 172 | _this.$form.append( |
— | — | @@ -204,6 +225,15 @@ |
205 | 226 | _this.addDescription( true, mw.config.get( 'wgUserLanguage' ) ); |
206 | 227 | $j( containerDiv ).append( _this.div ); |
207 | 228 | |
| 229 | + // make the title field required |
| 230 | + _this.$form.find( '.mwe-title' ) |
| 231 | + .rules( "add", { |
| 232 | + required: true, |
| 233 | + messages: { |
| 234 | + required: gM( 'mwe-upwiz-error-blank' ) |
| 235 | + } |
| 236 | + } ); |
| 237 | + |
208 | 238 | // make this a category picker |
209 | 239 | var hiddenCats = []; |
210 | 240 | if ( mw.isDefined( mw.UploadWizard.config.autoCategory ) ) { |
— | — | @@ -224,36 +254,33 @@ |
225 | 255 | * check entire form for validity |
226 | 256 | */ |
227 | 257 | // return boolean if we are ready to go. |
228 | | - // side effect: add error text to the page for fields in an incorrect state. |
| 258 | + // side effect: add error text to the page for fields in an incorrect state. |
229 | 259 | // we must call EVERY valid() function due to side effects; do not short-circuit. |
230 | | - valid: function() { |
| 260 | + valid: function() { |
231 | 261 | var _this = this; |
232 | | - // at least one description -- never mind, we are disallowing removal of first description |
233 | | - // all the descriptions -- check min & max length |
| 262 | + // at least one description -- never mind, we are disallowing removal of first description |
| 263 | + // all the descriptions -- check min & max length |
| 264 | + // categories are assumed valid |
| 265 | + // pop open the 'more-options' if the date is bad |
| 266 | + // location? |
234 | 267 | |
235 | | - // the title |
| 268 | + // make sure title is valid |
236 | 269 | var titleInputValid = $j( _this.titleInput ).data( 'valid' ); |
237 | 270 | if ( typeof titleInputValid == 'undefined' ) { |
238 | 271 | alert( "please wait, still checking the title for uniqueness..." ); |
239 | 272 | return false; |
240 | 273 | } |
241 | | - |
| 274 | + |
| 275 | + // make sure licenses are valid (needed for multi-file deed selection) |
| 276 | + var deedValid = _this.upload.deedChooser.valid(); |
| 277 | + |
242 | 278 | // all other fields validated with validator js |
243 | 279 | var formValid = _this.$form.valid(); |
244 | | - return titleInputValid && formValid; |
245 | 280 | |
246 | | - // categories are assumed valid |
247 | | - |
248 | | - // the license, if any |
| 281 | + return titleInputValid && deedValid && formValid; |
| 282 | + }, |
249 | 283 | |
250 | | - // pop open the 'more-options' if the date is bad |
251 | | - // the date |
252 | 284 | |
253 | | - // location? |
254 | | - }, |
255 | | - |
256 | | - |
257 | | - |
258 | 285 | /** |
259 | 286 | * toggles whether we use the 'macro' deed or our own |
260 | 287 | */ |
— | — | @@ -291,7 +318,6 @@ |
292 | 319 | */ |
293 | 320 | processDestinationCheck: function( result ) { |
294 | 321 | var _this = this; |
295 | | - |
296 | 322 | if ( result.isUnique ) { |
297 | 323 | $j( _this.titleInput ).data( 'valid', true ); |
298 | 324 | _this.$form.find( 'label[for=' + _this.titleId + ']' ).hide().empty(); |
— | — | @@ -302,7 +328,14 @@ |
303 | 329 | $j( _this.titleInput ).data( 'valid', false ); |
304 | 330 | |
305 | 331 | // result is NOT unique |
306 | | - var title = new mw.Title( result.title ).setNamespace( 'file' ).getNameText(); |
| 332 | + var title; |
| 333 | + try { |
| 334 | + title = new mw.Title( result.title ).setNamespace( 'file' ).getNameText(); |
| 335 | + } catch ( e ) { |
| 336 | + // unparseable result from unique test? |
| 337 | + title = '[unparseable name]'; |
| 338 | + } |
| 339 | + |
307 | 340 | /* var img = result.img; |
308 | 341 | var href = result.href; */ |
309 | 342 | |
— | — | @@ -463,7 +496,6 @@ |
464 | 497 | */ |
465 | 498 | populate: function() { |
466 | 499 | var _this = this; |
467 | | - mw.log( "mw.UploadWizardUpload::populate> populating details from upload" ); |
468 | 500 | _this.upload.setThumbnail( _this.thumbnailDiv, mw.UploadWizard.config['thumbnailWidth'], mw.UploadWizard.config['thumbnailMaxHeight'] ); |
469 | 501 | _this.prefillDate(); |
470 | 502 | _this.prefillSource(); |
— | — | @@ -487,11 +519,12 @@ |
488 | 520 | var _this = this; |
489 | 521 | var yyyyMmDdRegex = /^(\d\d\d\d)[:\/-](\d\d)[:\/-](\d\d)\D.*/; |
490 | 522 | var dateObj; |
491 | | - var metadata = _this.upload.imageinfo.metadata; |
492 | | - $j.each([metadata.datetimeoriginal, metadata.datetimedigitized, metadata.datetime, metadata['date']], |
493 | | - function( i, imageinfoDate ) { |
494 | | - if ( ! mw.isEmpty( imageinfoDate ) ) { |
495 | | - var matches = $j.trim( imageinfoDate ).match( yyyyMmDdRegex ); |
| 523 | + if ( _this.upload.imageinfo.metadata ) { |
| 524 | + var metadata = _this.upload.imageinfo.metadata; |
| 525 | + $j.each( [ 'datetimeoriginal', 'datetimedigitized', 'datetime', 'date' ], function( i, propName ) { |
| 526 | + var dateInfo = metadata[propName]; |
| 527 | + if ( ! mw.isEmpty( dateInfo ) ) { |
| 528 | + var matches = $j.trim( dateInfo ).match( yyyyMmDdRegex ); |
496 | 529 | if ( ! mw.isEmpty( matches ) ) { |
497 | 530 | dateObj = new Date( parseInt( matches[1], 10 ), |
498 | 531 | parseInt( matches[2], 10 ) - 1, |
— | — | @@ -499,13 +532,13 @@ |
500 | 533 | return false; // break from $j.each |
501 | 534 | } |
502 | 535 | } |
503 | | - } |
504 | | - ); |
| 536 | + } ); |
| 537 | + } |
505 | 538 | |
506 | 539 | // if we don't have EXIF or other metadata, let's use "now" |
507 | 540 | // XXX if we have FileAPI, it might be clever to look at file attrs, saved |
508 | 541 | // in the upload object for use here later, perhaps |
509 | | - if (typeof dateObj === 'undefined') { |
| 542 | + if ( !mw.isDefined( dateObj ) ) { |
510 | 543 | dateObj = new Date(); |
511 | 544 | } |
512 | 545 | dateStr = dateObj.getUTCFullYear() + '-' + pad( dateObj.getUTCMonth() ) + '-' + pad( dateObj.getUTCDate() ); |
— | — | @@ -551,13 +584,7 @@ |
552 | 585 | * "122/1" -- 122 m (altitude) |
553 | 586 | */ |
554 | 587 | prefillLocation: function() { |
555 | | - var _this = this; |
556 | | - var metadata = _this.upload.imageinfo.metadata; |
557 | | - if (metadata === undefined) { |
558 | | - return; |
559 | | - } |
560 | | - |
561 | | - |
| 588 | + /* unimplemented -- awaiting bawolff's GSoC 2010 project to be committedd... */ return; |
562 | 589 | }, |
563 | 590 | |
564 | 591 | /** |
— | — | @@ -594,7 +621,7 @@ |
595 | 622 | */ |
596 | 623 | prefillAuthor: function() { |
597 | 624 | var _this = this; |
598 | | - if (_this.upload.imageinfo.metadata.author !== undefined) { |
| 625 | + if ( _this.upload.imageinfo.metadata && _this.upload.imageinfo.metadata.author ) { |
599 | 626 | $j( _this.authorInput ).val( _this.upload.imageinfo.metadata.author ); |
600 | 627 | } |
601 | 628 | |
— | — | @@ -606,20 +633,22 @@ |
607 | 634 | */ |
608 | 635 | prefillLicense: function() { |
609 | 636 | var _this = this; |
610 | | - var copyright = _this.upload.imageinfo.metadata.copyright; |
611 | | - if (copyright !== undefined) { |
612 | | - if (copyright.match(/\bcc-by-sa\b/i)) { |
613 | | - alert("unimplemented cc-by-sa in prefillLicense"); |
614 | | - // XXX set license to be that CC-BY-SA |
615 | | - } else if (copyright.match(/\bcc-by\b/i)) { |
616 | | - alert("unimplemented cc-by in prefillLicense"); |
617 | | - // XXX set license to be that |
618 | | - } else if (copyright.match(/\bcc-zero\b/i)) { |
619 | | - alert("unimplemented cc-zero in prefillLicense"); |
620 | | - // XXX set license to be that |
621 | | - // XXX any other licenses we could guess from copyright statement |
622 | | - } else { |
623 | | - $j( _this.licenseInput ).val( copyright ); |
| 637 | + if ( _this.upload.imageinfo.metadata ) { |
| 638 | + var copyright = _this.upload.imageinfo.metadata.copyright; |
| 639 | + if (copyright !== undefined) { |
| 640 | + if (copyright.match(/\bcc-by-sa\b/i)) { |
| 641 | + alert("unimplemented cc-by-sa in prefillLicense"); |
| 642 | + // XXX set license to be that CC-BY-SA |
| 643 | + } else if (copyright.match(/\bcc-by\b/i)) { |
| 644 | + alert("unimplemented cc-by in prefillLicense"); |
| 645 | + // XXX set license to be that |
| 646 | + } else if (copyright.match(/\bcc-zero\b/i)) { |
| 647 | + alert("unimplemented cc-zero in prefillLicense"); |
| 648 | + // XXX set license to be that |
| 649 | + // XXX any other licenses we could guess from copyright statement |
| 650 | + } else { |
| 651 | + $j( _this.licenseInput ).val( copyright ); |
| 652 | + } |
624 | 653 | } |
625 | 654 | } |
626 | 655 | // if we still haven't set a copyright use the user's preferences? |
— | — | @@ -628,7 +657,6 @@ |
629 | 658 | |
630 | 659 | /** |
631 | 660 | * Convert entire details for this file into wikiText, which will then be posted to the file |
632 | | - * XXX there is a WikiText sanitizer in use on UploadForm -- use that here, or port it |
633 | 661 | * @return wikitext representing all details |
634 | 662 | */ |
635 | 663 | getWikiText: function() { |
— | — | @@ -699,6 +727,10 @@ |
700 | 728 | // group categories together, maybe? |
701 | 729 | wikiText += deed.getLicenseWikiText() + _this.div.find( '.categoryInput' ).get(0).getWikiText() + "\n\n"; |
702 | 730 | |
| 731 | + // sanitize wikitext if TextCleaner is defined (MediaWiki:TextCleaner.js) |
| 732 | + if ( typeof TextCleaner != 'undefined' && typeof TextCleaner.sanitizeWikiText == 'function' ) { |
| 733 | + wikiText = TextCleaner.sanitizeWikiText( wikiText, true ); |
| 734 | + } |
703 | 735 | |
704 | 736 | return wikiText; |
705 | 737 | }, |
— | — | @@ -713,7 +745,6 @@ |
714 | 746 | |
715 | 747 | // XXX check state of details for okayness ( license selected, at least one desc, sane filename ) |
716 | 748 | var wikiText = _this.getWikiText(); |
717 | | - mw.log( "mw.UploadWizardUpload::submit> submiting wikiText:\n" + wikiText ); |
718 | 749 | |
719 | 750 | var params = { |
720 | 751 | action: 'upload', |
— | — | @@ -728,10 +759,7 @@ |
729 | 760 | _this.completeDetailsSubmission(); |
730 | 761 | }; |
731 | 762 | |
732 | | - mw.log( "mw.UploadWizardUpload::submit> uploading: \n" + params ); |
733 | 763 | var callback = function( result ) { |
734 | | - mw.log( "mw.UploadWizardUpload::submit> result:\n" + result ); |
735 | | - mw.log( "mw.UploadWizardUpload::submit> successful upload" ); |
736 | 764 | finalCallback( result ); |
737 | 765 | }; |
738 | 766 | |
— | — | @@ -740,41 +768,6 @@ |
741 | 769 | _this.upload.api.postWithEditToken( params, callback ); |
742 | 770 | }, |
743 | 771 | |
744 | | - |
745 | | - /** |
746 | | - * Get new image info, for instance, after we renamed... or? published? an image |
747 | | - * XXX deprecated? |
748 | | - * XXX move to mw.API |
749 | | - * |
750 | | - * @param upload an UploadWizardUpload object |
751 | | - * @param title title to look up remotely |
752 | | - * @param endCallback execute upon completion |
753 | | - */ |
754 | | - getImageInfo: function( upload, callback ) { |
755 | | - var params = { |
756 | | - 'titles': upload.title.toString(), |
757 | | - 'prop': 'imageinfo', |
758 | | - 'iiprop': 'timestamp|url|user|size|sha1|mime|metadata' |
759 | | - }; |
760 | | - // XXX timeout callback? |
761 | | - this.api.get( params, function( data ) { |
762 | | - if ( data && data.query && data.query.pages ) { |
763 | | - if ( ! data.query.pages[-1] ) { |
764 | | - for ( var page_id in data.query.pages ) { |
765 | | - var page = data.query.pages[ page_id ]; |
766 | | - if ( ! page.imageinfo ) { |
767 | | - alert("unimplemented error check, missing imageinfo"); |
768 | | - // XXX not found? error |
769 | | - } else { |
770 | | - upload.extractImageInfo( page.imageinfo[0] ); |
771 | | - } |
772 | | - } |
773 | | - } |
774 | | - } |
775 | | - callback(); |
776 | | - } ); |
777 | | - }, |
778 | | - |
779 | 772 | completeDetailsSubmission: function() { |
780 | 773 | var _this = this; |
781 | 774 | _this.upload.state = 'complete'; |
Index: branches/uploadwizard-firefogg/resources/mw.ApiUploadHandler.js |
— | — | @@ -51,14 +51,7 @@ |
52 | 52 | |
53 | 53 | // we use JSON in HTML because according to mdale, some browsers cannot handle just JSON |
54 | 54 | _this.addFormInputIfMissing( 'format', 'jsonfm' ); |
55 | | - |
56 | | - // XXX only for testing, so it stops complaining about dupes |
57 | | - /* |
58 | | - if ( mw.UploadWizard.DEBUG ) { |
59 | | - _this.addFormInputIfMissing( 'ignorewarnings', '1' ); |
60 | | - } |
61 | | - */ |
62 | | - }, |
| 55 | + }, |
63 | 56 | /** |
64 | 57 | * Modify our form to have a fresh edit token. |
65 | 58 | * If successful, return true to a callback. |
Index: branches/uploadwizard-firefogg/resources/mw.Log.js |
— | — | @@ -1,44 +1,81 @@ |
2 | | -// dependencies: [ mw ] |
| 2 | +/** |
| 3 | + * Logging library |
| 4 | + * |
| 5 | + * Synopsis: |
| 6 | + * |
| 7 | + * mw.log( "falls in the forest" ); // not logged, we start off at 'silent' log level |
| 8 | + * |
| 9 | + * // set the minimum level for a log message to be shown. Can be 'silent', 'fatal', 'warn', 'info', 'debug' |
| 10 | + * mw.log.level = 'debug'; // show just about everything |
| 11 | + * mw.log( 'some random crap' ); // will be shown |
| 12 | + * |
| 13 | + * mw.log.level = 'warn'; // show less stuff |
| 14 | + * |
| 15 | + * mw.log( 'foo bar' ); // won't be shown, the default level for a log is 'info' |
| 16 | + * |
| 17 | + * mw.log( 'this is a warning', 'warn' ); // will be shown |
| 18 | + * |
| 19 | + * var warnCute = mw.log.getLogger( 'cuteness', 'warn' ); |
| 20 | + * warnCute( 'overload!' ); // will log 'cuteness> overload!' |
| 21 | + * |
| 22 | + * var logDebug = mw.log.getLogger( undefined, 'debug' ); |
| 23 | + * mw.log.level = 'debug'; |
| 24 | + * logDebug( "random spammy log for developers..." ); // will log "random spammy log for developers..."; |
| 25 | + */ |
3 | 26 | |
4 | 27 | ( function( mw, $j ) { |
5 | 28 | |
| 29 | + /* how all the logging levels sort */ |
| 30 | + var priority = { |
| 31 | + silent: 0, |
| 32 | + fatal: 10, |
| 33 | + warn: 20, |
| 34 | + info: 30, |
| 35 | + debug: 100 |
| 36 | + }; |
| 37 | + |
6 | 38 | /** |
7 | | - * Log a string msg to the console |
8 | | - * |
9 | | - * @param {String} string String to output to console |
10 | | - */ |
| 39 | + * Log output to the console. |
| 40 | + * |
| 41 | + * In the case that the browser does not have a console available, one is created by appending a |
| 42 | + * <div> element to the bottom of the body and then appending a <div> element to that for each |
| 43 | + * message. |
| 44 | + * |
| 45 | + * @author Michael Dale <mdale@wikimedia.org> |
| 46 | + * @author Trevor Parscal <tparscal@wikimedia.org> |
| 47 | + * @author Neil Kandalgaonkar <neilk@wikimedia.org> |
| 48 | + * @param {string} string Message to output to console |
| 49 | + * @param {string} optional, logging priority (see priority) |
| 50 | + */ |
11 | 51 | mw.log = function( s, level ) { |
12 | | - |
13 | | - if ( typeof level === 'undefined' ) { |
14 | | - level = 30; |
| 52 | + |
| 53 | + if ( typeof level === 'undefined' || ! priority.hasOwnProperty( level ) ) { |
| 54 | + level = 'info'; |
15 | 55 | } |
16 | 56 | |
17 | | - if ( level > mw.log.level ) { |
| 57 | + // don't show log message if lower priority than mw.log.level |
| 58 | + if ( priority[ mw.log.level ] < priority[ level ] ) { |
18 | 59 | return; |
19 | | - } |
20 | | - |
21 | | - // Add any prepend debug ss if necessary |
22 | | - if ( mw.log.preAppendLog ) { |
23 | | - s = mw.log.preAppendLog + s; |
24 | 60 | } |
25 | 61 | |
26 | 62 | if ( typeof window.console !== 'undefined' && typeof window.console.log === 'function' ) { |
27 | 63 | window.console.log( s ); |
28 | 64 | } else { |
29 | | -/* |
| 65 | + // Set timestamp |
| 66 | + var d = new Date(); |
| 67 | + var time = ( pad( d.getHours(), 2 ) + ':' + pad( d.getMinutes(), 2 ) + pad( d.getSeconds(), 2 ) + pad( d.getMilliseconds(), 3 ) ); |
30 | 68 | // Show a log box for console-less browsers |
31 | 69 | var $log = $( '#mw-log-console' ); |
32 | 70 | if ( !$log.length ) { |
33 | 71 | $log = $( '<div id="mw-log-console"></div>' ) |
34 | 72 | .css( { |
35 | | - 'position': 'absolute', |
| 73 | + 'position': 'fixed', |
36 | 74 | 'overflow': 'auto', |
37 | 75 | 'z-index': 500, |
38 | 76 | 'bottom': '0px', |
39 | 77 | 'left': '0px', |
40 | 78 | 'right': '0px', |
41 | | - 'height': '100px', |
42 | | - 'width': '100%', |
| 79 | + 'height': '150px', |
43 | 80 | 'background-color': 'white', |
44 | 81 | 'border-top': 'solid 2px #ADADAD' |
45 | 82 | } ) |
— | — | @@ -53,27 +90,42 @@ |
54 | 91 | 'padding': '0.125em 0.25em' |
55 | 92 | } ) |
56 | 93 | .text( s ) |
| 94 | + .append( '<span style="float:right">[' + time + ']</span>' ) |
57 | 95 | ); |
58 | | -*/ |
59 | 96 | } |
60 | 97 | }; |
61 | 98 | |
62 | | - mw.log.level = mw.log.NONE = 0; |
63 | | - mw.log.FATAL = 10; |
64 | | - mw.log.WARN = 20; |
65 | | - mw.log.INFO = 30; |
66 | | - mw.log.ALL = 100; |
67 | | - |
68 | | - mw.log.fatal = function( s ) { |
69 | | - mw.log( s, mw.log.FATAL ); |
| 99 | + // Logging is silent by default -- if you're debugging an app, figure out a way to turn it on |
| 100 | + mw.log.level = 0; |
| 101 | + |
| 102 | + /** |
| 103 | + * Convenience function for logging cases where you want to repeatedly log with a prefix for each message, |
| 104 | + * and/or at a particular logging level |
| 105 | + * |
| 106 | + * @param {string} prefix |
| 107 | + * @param {string} level name |
| 108 | + * @return {function} logging function which prepends that prefix, and logs at that level |
| 109 | + */ |
| 110 | + mw.log.getLogger = function( prefixArg, level ) { |
| 111 | + var prefix = typeof prefixArg === 'undefined' ? '' : prefixArg + '> '; |
| 112 | + return function( s ) { |
| 113 | + mw.log( prefix + s, level ); |
| 114 | + }; |
70 | 115 | }; |
71 | | - mw.log.warn = function( s ) { |
72 | | - mw.log( s, mw.log.WARN ); |
73 | | - }; |
74 | | - mw.log.info = function( s ) { |
75 | | - mw.log( s, mw.log.INFO ); |
76 | | - }; |
77 | | - mw.log.level = mw.log.ALL; |
78 | 116 | |
| 117 | + /** |
| 118 | + * Helper function for logging date/time -- given number, return string zero-padded to requested length |
| 119 | + * @param d {number} the number |
| 120 | + * @param n {number} length |
| 121 | + * @return {string} |
| 122 | + */ |
| 123 | + function pad( d, n ) { |
| 124 | + var s = d.toString(); |
| 125 | + while ( s.length < n ) { |
| 126 | + s = '0' + s; |
| 127 | + } |
| 128 | + return s; |
| 129 | + } |
| 130 | + |
79 | 131 | } )( window.mediaWiki, jQuery ); |
80 | 132 | |