Index: branches/js2-work/phase3/js/uploadWizard.js |
— | — | @@ -1 +0,0 @@ |
2 | | -console.log('uploadwizard loaded'); |
Index: branches/js2-work/phase3/js/mwEmbed/loader.js |
— | — | @@ -32,7 +32,8 @@ |
33 | 33 | 'EmbedPlayer', |
34 | 34 | 'ApiProxy', |
35 | 35 | 'Sequencer', |
36 | | - 'TimedText' |
| 36 | + 'TimedText', |
| 37 | + 'UploadWizard' |
37 | 38 | ]; |
38 | 39 | |
39 | 40 | /** |
— | — | @@ -162,4 +163,4 @@ |
163 | 164 | "$j.ui.draggable" : "jquery/jquery.ui/ui/ui.draggable.js", |
164 | 165 | "$j.ui.selectable" : "jquery/jquery.ui/ui/ui.selectable.js" |
165 | 166 | |
166 | | -} ); |
\ No newline at end of file |
| 167 | +} ); |
Index: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/loader.js |
— | — | @@ -0,0 +1,87 @@ |
| 2 | +/* |
| 3 | +* Loader for UploadWizard module: |
| 4 | +*/ |
| 5 | + |
| 6 | +// Scope everythign in "mw" ( keeps the global namespace clean ) |
| 7 | +( function( mw ) { |
| 8 | + |
| 9 | + mw.addMessages( { |
| 10 | + "mwe-loading-upwiz" : "Loading upload wizard" |
| 11 | + }); |
| 12 | + |
| 13 | + // Add class file paths ( From ROOT ) |
| 14 | + mw.addClassFilePaths( { |
| 15 | + |
| 16 | + // "mw.UploadHandler" : "modules/UploadWizard/mw.UploadHandler.js", |
| 17 | + "mw.UploadWizard" : "modules/UploadWizard/mw.UploadWizard.js", |
| 18 | + "mw.UploadApiProcessor" : "modules/UploadWizard/mw.UploadApiProcessor.js", |
| 19 | + "mw.IframeTransport" : "modules/UploadWizard/mw.IframeTransport.js", |
| 20 | + "mw.ApiUploadHandler" : "modules/UploadWizard/mw.ApiUploadHandler.js", |
| 21 | + |
| 22 | + "mw.MockUploadHandler" : "modules/UploadWizard/mw.MockUploadHandler.js" |
| 23 | + |
| 24 | + }); |
| 25 | + |
| 26 | + mw.addClassStyleSheets( { |
| 27 | + 'mw.UploadWizard' : 'modules/UploadWizard/css/uploadWizard.css' |
| 28 | + } ); |
| 29 | + |
| 30 | + //Set a variable for the base upload interface for easy inclution |
| 31 | + var baseUploadlibs = [ |
| 32 | + [ |
| 33 | + 'mw.UploadHandler', |
| 34 | + 'mw.UploadInterface', |
| 35 | + '$j.ui' |
| 36 | + ], |
| 37 | + [ |
| 38 | + '$j.ui.progressbar', |
| 39 | + '$j.ui.dialog', |
| 40 | + '$j.ui.draggable' |
| 41 | + ] |
| 42 | + ]; |
| 43 | + |
| 44 | + var mwBaseFirefoggReq = baseUploadlibs.slice( 0 ) |
| 45 | + mwBaseFirefoggReq[0].push('mw.Firefogg'); |
| 46 | + |
| 47 | + |
| 48 | + /** |
| 49 | + * Note: We should move relevant parts of these style sheets to the addMedia/css folder |
| 50 | + * phase 2: We should separate out sheet sets per sub-module: |
| 51 | + */ |
| 52 | + |
| 53 | + mw.addModuleLoader( 'UploadWizard.UploadWizard', function( callback ) { |
| 54 | + //Clone the array: |
| 55 | + var request = mwBaseFirefoggReq.slice( 0 ) ; |
| 56 | + |
| 57 | + //Add uploadwizard classes to a new "request" var: |
| 58 | + request.push( [ |
| 59 | + 'mw.IframeTransport', |
| 60 | + 'mw.ApiUploadHandler', |
| 61 | + 'mw.UploadWizard' |
| 62 | + ] ); |
| 63 | + |
| 64 | + mw.load( request, function() { |
| 65 | + callback( 'UploadWizard.UploadWizard' ); |
| 66 | + }); |
| 67 | + |
| 68 | + } ); |
| 69 | + |
| 70 | + mw.addModuleLoader( 'UploadWizard.UploadWizardTest', function( callback ) { |
| 71 | + //Clone the array: |
| 72 | + var request = mwBaseFirefoggReq.slice( 0 ) ; |
| 73 | + |
| 74 | + //Add uploadwizard classes to a new "request" var: |
| 75 | + request.push( [ |
| 76 | + 'mw.IframeTransport', |
| 77 | + 'mw.ApiUploadHandler', |
| 78 | + 'mw.MockUploadHandler', |
| 79 | + 'mw.UploadWizard' |
| 80 | + ] ); |
| 81 | + |
| 82 | + mw.load( request, function() { |
| 83 | + callback( 'UploadWizard.UploadWizardTest' ); |
| 84 | + }); |
| 85 | + |
| 86 | + } ); |
| 87 | + |
| 88 | +} )( window.mw ); |
Property changes on: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/loader.js |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 89 | + native |
Index: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.UploadWizard.js |
— | — | @@ -0,0 +1,943 @@ |
| 2 | +mw.addMessages({ |
| 3 | + 'mwe-upwiz-tab-file': 'Step 1', |
| 4 | + 'mwe-upwiz-tab-metadata': 'Step 2', |
| 5 | + 'mwe-upwiz-tab-thanks': 'Step 3', |
| 6 | + 'mwe-upwiz-intro': 'Introductory text (short)', |
| 7 | + 'mwe-upwiz-select-files': 'Select files:', |
| 8 | + 'mwe-upwiz-add-file-n': 'Add another file', |
| 9 | + 'mwe-upwiz-add-file-0': 'Add a file', |
| 10 | + 'mwe-upwiz-browse': 'Browse...', |
| 11 | + 'mwe-upwiz-completed': 'OK', |
| 12 | + 'mwe-upwiz-uploading': 'uploading...', |
| 13 | + 'mwe-upwiz-remove-upload': 'Remove this upload', |
| 14 | + 'mwe-upwiz-upload': 'Upload', |
| 15 | + 'mwe-upwiz-upload-count': '$1 of $2 files uploaded', |
| 16 | + 'mwe-upwiz-progressbar-uploading': 'uploading', |
| 17 | + 'mwe-upwiz-remaining': '$1 remaining', |
| 18 | + 'mwe-upwiz-intro-metadata': 'Thank you for uploading your works! Now we need some basic information in order to complete your upload.', |
| 19 | + 'mwe-upwiz-provenance-ownwork': 'They are entirely your own work.', |
| 20 | + 'mwe-upwiz-provenance-ownwork-assert': 'I, $1, the copyright holder of this work, hereby grant anyone the right to use these works for any purpose, as long as they credit me and share derivative work under the same terms.', |
| 21 | + 'mwe-upwiz-provenance-ownwork-assert-note': 'This means you release your work under a double Creative Commons Attribution ShareAlike and GFDL license.', |
| 22 | + 'mwe-upwiz-provenance-permission': 'Their author gave you explicit permission to upload them', |
| 23 | + 'mwe-upwiz-provenance-website': 'They come from a website', |
| 24 | + 'mwe-upwiz-provenance-custom': 'Did you know? You can <a href="$1">customize</a> the default options you see here.', |
| 25 | + 'mwe-upwiz-more-options': 'more options...', |
| 26 | + 'mwe-upwiz-desc-lang': 'Description in', |
| 27 | + 'mwe-upwiz-desc-lang-another': 'add a description in another language', |
| 28 | + 'mwe-upwiz-title': 'Title', |
| 29 | + 'mwe-upwiz-categories-intro': 'Help people find your works by adding categories', |
| 30 | + 'mwe-upwiz-categories-another': 'Add other categories', |
| 31 | + 'mwe-upwiz-previously-uploaded': 'This file was previously uploaded to $1 and is already available <a href="$2">here</a>.', |
| 32 | + 'mwe-upwiz-about-this-work': 'About this work', |
| 33 | + 'mwe-upwiz-media-type': 'Media type', |
| 34 | + 'mwe-upwiz-date-created': 'Date created', |
| 35 | + 'mwe-upwiz-geotag': 'Location', |
| 36 | + 'mwe-upwiz-copyright-info': 'Copyright information', |
| 37 | + 'mwe-upwiz-author': 'Author', |
| 38 | + 'mwe-upwiz-license': 'License', |
| 39 | + 'mwe-upwiz-about-format': 'About the file', |
| 40 | + 'mwe-upwiz-autoconverted': 'This file was automatically converted to the $1 format', |
| 41 | + 'mwe-upwiz-filename-tag': 'File name:', |
| 42 | + 'mwe-upwiz-other': 'Other information', |
| 43 | + 'mwe-upwiz-other-prefill': 'Free wikitext field', |
| 44 | + 'mwe-upwiz-showall': 'show all', |
| 45 | + 'mwe-upwiz-desc-lang': 'Description in', // caution: FRAGMENT -- bad for i18n |
| 46 | + |
| 47 | + 'mwe-upwiz-upload-error-bad-filename-extension': 'This wiki does not accept filenames with the extension "$1".', |
| 48 | + 'mwe-upwiz-upload-error-duplicate': 'This file was previously uploaded to this wiki.', |
| 49 | + 'mwe-upwiz-upload-error-stashed-anyway': 'Post anyway?', |
| 50 | + 'mwe-upwiz-ok': 'OK', |
| 51 | + 'mwe-upwiz-cancel': 'Cancel', |
| 52 | + |
| 53 | + |
| 54 | + // available licenses should be a configuration of the MediaWiki instance, |
| 55 | + // not hardcoded here. |
| 56 | + // but, MediaWiki has no real concept of a License as a first class object -- there are templates and then specially-parsed |
| 57 | + // texts to create menus -- hack on top of hacks -- a bit too much to deal with ATM |
| 58 | + 'mwe-lic-pd': 'Public domain', |
| 59 | + 'mwe-lic-cc-0': 'Creative Commons Zero waiver', |
| 60 | + 'mwe-lic-cc-by-3.0': 'Creative Commons Attribution 3.0', |
| 61 | + 'mwe-lic-cc-by-sa-3.0': 'Creative Commons Attribution ShareAlike 3.0', |
| 62 | + 'mwe-lic-gfdl': 'GFDL' |
| 63 | +}); |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +// this interface only works for the wizard... should say so in class name |
| 68 | +mw.UploadWizardUploadInterface = function(filenameAcceptedCb) { |
| 69 | + var _this = this; |
| 70 | + |
| 71 | + _this.filenameAcceptedCb = filenameAcceptedCb; |
| 72 | + |
| 73 | + // may need to collaborate with the particular upload type sometimes |
| 74 | + // for the interface, as well as the uploadwizard. OY. |
| 75 | + _this.div = $j('<div></div>').get(0); |
| 76 | + |
| 77 | + _this.fileInputCtrl = $j('<input size=40 class="mwe-upwiz-file" name="file" type="file"/>').get(0); |
| 78 | + |
| 79 | + // XXX not sure if we will have a filename here -- we may want to autogenerate a "stashed" filename, |
| 80 | + // with this flow |
| 81 | + _this.filenameCtrl = $j('<input type="hidden" name="filename" value=""/>').get(0); |
| 82 | + |
| 83 | + _this.form = $j('<form class="mwe-upwiz-form"></form>').append(_this.fileInputCtrl).append(_this.filenameCtrl).get(0); |
| 84 | + |
| 85 | + _this.progressMessage = $j('<span class="mwe-upwiz-status-message" style="display: none"></span>').get(0); |
| 86 | + |
| 87 | + $j(_this.fileInputCtrl).change( function() { _this.fileChanged() } ); |
| 88 | + |
| 89 | + _this.errorDiv = $j('<div class="mwe-upwiz-upload-error" style="display: none;"></div>').get(0); |
| 90 | + |
| 91 | + |
| 92 | + $j(_this.div).append(_this.form) |
| 93 | + .append(_this.removeCtrl) |
| 94 | + .append(_this.progressMessage) |
| 95 | + .append(_this.errorDiv); |
| 96 | + |
| 97 | + // _this.progressBar = (no progress bar for individual uploads yet) |
| 98 | + // add a metadata thing to metadata |
| 99 | +}; |
| 100 | + |
| 101 | +mw.UploadWizardUploadInterface.prototype = { |
| 102 | + /* start! */ |
| 103 | + start: function() { |
| 104 | + var _this = this; |
| 105 | + $j(_this.removeCtrl).hide(); |
| 106 | + }, |
| 107 | + |
| 108 | + /* generically busy, but not a fraction. Encoding, or transports that don't know progress */ |
| 109 | + busy: function() { |
| 110 | + var _this = this; |
| 111 | + // for now we implement this as looking like "100% progress" |
| 112 | + // e.g. an animated bar that takes up all the space |
| 113 | + _this.progress(1.0); |
| 114 | + }, |
| 115 | + |
| 116 | + /* show progress with a certain fraction */ |
| 117 | + progress: function(fraction) { |
| 118 | + var _this = this; |
| 119 | + $j(_this.progressMessage).addClass('mwe-upwiz-status-progress') |
| 120 | + .html(gM( 'mwe-upwiz-uploading' )) |
| 121 | + .show(); |
| 122 | + // update individual progress bar with fraction? |
| 123 | + }, |
| 124 | + |
| 125 | + // this is just completed in the sense that it's all uploaded. There may be other errors? |
| 126 | + // really need to rethink the UI / metadata separation |
| 127 | + completed: function(result) { |
| 128 | + var _this = this; |
| 129 | + $j(_this.progressMessage).removeClass('mwe-upwiz-status-progress') |
| 130 | + .addClass('mwe-upwiz-status-completed') |
| 131 | + .html(gM( 'mwe-upwiz-completed' )); |
| 132 | + }, |
| 133 | + |
| 134 | + fileChanged: function() { |
| 135 | + var _this = this; |
| 136 | + _this.clearErrors(); |
| 137 | + var ext = _this.getExtension(); |
| 138 | + if (_this.isGoodExtension(ext)) { |
| 139 | + _this.updateFilename(); |
| 140 | + } else { |
| 141 | + _this.error('bad-filename-extension', ext); |
| 142 | + } |
| 143 | + }, |
| 144 | + |
| 145 | + updateFilename: function() { |
| 146 | + var _this = this; |
| 147 | + var path = $j(_this.fileInputCtrl).attr('value'); |
| 148 | + var filename = _this.convertPathToFilename(path); |
| 149 | + // XXX store the "desired" filename for later, when we rename the file |
| 150 | + // this is a hack to get a temporary file guaranteed unique -- will change perhaps, later |
| 151 | + filename = mw.getConfig('userName') + "-" + (new Date()).getTime() + "-" + filename; |
| 152 | + $j(_this.filenameCtrl).attr('value', filename); |
| 153 | + _this.filenameAcceptedCb(); |
| 154 | + }, |
| 155 | + |
| 156 | + clearErrors: function() { |
| 157 | + var _this = this; |
| 158 | + // XXX this should be changed to something Theme compatible |
| 159 | + $j(_this.div).removeClass('mwe-upwiz-upload-error'); |
| 160 | + $j(_this.errorDiv).hide().empty(); |
| 161 | + }, |
| 162 | + |
| 163 | + error: function() { |
| 164 | + var _this = this; |
| 165 | + var args = Array.prototype.slice.call(arguments); // copies arguments into a real array |
| 166 | + var msg = 'mwe-upwiz-upload-error-' + args[0]; |
| 167 | + $j(_this.errorDiv).append($j('<p class="mwe-upwiz-upload-error">' + gM(msg, args.slice(1)) + '</p>')); |
| 168 | + // apply a error style to entire did |
| 169 | + $j(_this.div).addClass('mwe-upwiz-upload-error'); |
| 170 | + $j(_this.errorDiv).show(); |
| 171 | + }, |
| 172 | + |
| 173 | + // arguably should be about filename, not path? |
| 174 | + // may check for common bad patterns here, like DSC_NNNNN, filenames too short, too long, etc. |
| 175 | + |
| 176 | + getExtension: function() { |
| 177 | + var _this = this; |
| 178 | + var path = $j(_this.fileInputCtrl).attr('value'); |
| 179 | + return path.substr( path.lastIndexOf( '.' ) + 1 ).toLowerCase(); |
| 180 | + }, |
| 181 | + |
| 182 | + // XXX this is common utility code |
| 183 | + // used when converting contents of a file input and coming up with a suitable "filename" for mediawiki |
| 184 | + // test: what if path is length 0 |
| 185 | + // what if path is all separators |
| 186 | + // what if path ends with a separator character |
| 187 | + // what if it ends with multiple separator characters |
| 188 | + convertPathToFilename: function(path) { |
| 189 | + if (path === undefined || path == '') { |
| 190 | + return ''; |
| 191 | + } |
| 192 | + |
| 193 | + var lastFileSeparatorIdx = Math.max(path.lastIndexOf( '/' ), path.lastIndexOf( '\\' )); |
| 194 | + // lastFileSeparatorIdx is now -1 if no separator found, or some index in the string. |
| 195 | + // so, +1, that is either 0 (beginning of string) or the character after last separator. |
| 196 | + // caution! could go past end of string... need to be more careful |
| 197 | + var filename = path.substring( lastFileSeparatorIdx + 1, 10000 ); |
| 198 | + |
| 199 | + // Capitalise first letter and replace spaces by underscores |
| 200 | + filename = filename.charAt( 0 ).toUpperCase() + filename.substring( 1, 10000 ); |
| 201 | + filename.replace(/ /g, '_' ); |
| 202 | + return filename; |
| 203 | + }, |
| 204 | + |
| 205 | + // XXX this is common utility code |
| 206 | + // XXX unused, copied because we'll probably need it... stripped from old doDestinationFill |
| 207 | + // this is used when checking for "bad" extensions in a filename. |
| 208 | + isGoodExtension: function(ext) { |
| 209 | + var _this = this; |
| 210 | + var found = false; |
| 211 | + var extensions = mw.getConfig('fileExtensions'); |
| 212 | + if (extensions) { |
| 213 | + for ( var i = 0; i < extensions.length; i++ ) { |
| 214 | + if ( extensions[i].toLowerCase() == ext ) { |
| 215 | + found = true; |
| 216 | + } |
| 217 | + } |
| 218 | + } |
| 219 | + return found; |
| 220 | + } |
| 221 | + |
| 222 | +}; |
| 223 | + |
| 224 | + |
| 225 | +mw.UploadWizard = function() { |
| 226 | + |
| 227 | + // this detect upload mode checks the API. Slow! |
| 228 | + // works for the add media wizard since it is talking to a remote server |
| 229 | + // we need to make it configure itself based on local globals if local server, remote only if remote. |
| 230 | + // UploadHandler.detectUploadMode(); |
| 231 | + |
| 232 | + // XXX does not work yet |
| 233 | + |
| 234 | + this.uploadHandlerClass = mw.getConfig('uploadHandlerClass') || this.getUploadHandlerClass(); |
| 235 | + this.isCompleted = false; |
| 236 | + |
| 237 | + this.uploads = []; |
| 238 | + // leading underline for privacy. DO NOT TAMPER. |
| 239 | + this._uploadsQueued = []; |
| 240 | + this._uploadsInProgress = []; |
| 241 | + this._uploadsCompleted = []; |
| 242 | + |
| 243 | + this.uploadsBeginTime = null; |
| 244 | + |
| 245 | + return this; |
| 246 | +}; |
| 247 | + |
| 248 | +mw.UploadWizardDescription = function(languageCode) { |
| 249 | + var _this = this; |
| 250 | + |
| 251 | + // XXX obtain list of real languages from config |
| 252 | + var languageMenu = $j('<select name="lang" class="mwe-upwiz-desc-lang"></select>'); |
| 253 | + |
| 254 | + // this could be cached, and cloned as necessary...? |
| 255 | + var languages = mw.getConfig('languages'); |
| 256 | + for (var i = 0; i < languages.length; i++) { |
| 257 | + var language = languages[i]; |
| 258 | + var selected = ""; |
| 259 | + if (language.code == languageCode) { |
| 260 | + selected = " selected"; |
| 261 | + } |
| 262 | + var option = $j('<option value="' + language.code + selected + ">" + language.name + '</option>'); |
| 263 | + languageMenu.append(option); |
| 264 | + } |
| 265 | + |
| 266 | + |
| 267 | + _this.languageMenu = languageMenu.get(0); |
| 268 | + _this.description = $j('<input name="desc" class="mwe-upwiz-desc-lang-text" type="text" size="40"/>').get(0); |
| 269 | + |
| 270 | + _this.removeCtrl = $j('<a class="mwe-upwiz-desc-lang-remove" href="#">x</a>').get(0); |
| 271 | + _this.removeCtrl.click = function () { _this.remove() }; |
| 272 | + |
| 273 | + _this.div = $j('<div class="mwe-upwiz-desc-lang"></div>') |
| 274 | + .append(_this.languageMenu) |
| 275 | + .append(_this.description) |
| 276 | + .append(_this.removeCtrl); |
| 277 | + |
| 278 | +}; |
| 279 | + |
| 280 | +mw.UploadWizardDescription.prototype = { |
| 281 | + remove: function() { |
| 282 | + // XXX todo |
| 283 | + }, |
| 284 | + |
| 285 | + getWikiText: function() { |
| 286 | + return '{{' + _this.languageMenu.value() + '|' + _this.description.value() + '}}' |
| 287 | + } |
| 288 | +}; |
| 289 | + |
| 290 | +mw.UploadWizardMetadata = function(containerDiv) { |
| 291 | + |
| 292 | + var _this = this; |
| 293 | + _this.descriptions = []; |
| 294 | + |
| 295 | + _this.div = $j('<div class="mwe-upwiz-metadata-file"></div>'); |
| 296 | + |
| 297 | + _this.thumbnail = $j('<img class="mwe-upwiz-thumbnail"/>'); |
| 298 | + var thumbnailDiv = $j('<div class="mwe-upwiz-thumbnail"></div>').append(_this.thumbnail).get(0); |
| 299 | + |
| 300 | + _this.errorDiv = $j('<div class="mwe-upwiz-metadata-error"></div>'); |
| 301 | + |
| 302 | + _this.dataDiv = $j('<div class="mwe-upwiz-metadata-data"></div>'); |
| 303 | + |
| 304 | + _this.descriptionsDiv = $j('<div class="mwe-upwiz-metadata-descriptions"></div>'); |
| 305 | + |
| 306 | + _this.descriptionsContainerDiv = |
| 307 | + $j('<div class="mwe-upwiz-metadata-descriptions-container"></div>') |
| 308 | + .append( $j('<div class="mwe-upwiz-metadata-descriptions-title">' + gM('mwe-upwiz-desc-lang') + '</div>') ) |
| 309 | + .append(_this.descriptionsDiv) |
| 310 | + .append( $j('<div class="mwe-upwiz-metadata-descriptions-add"></div>') |
| 311 | + .append( $j('<a href="#">' + gM('mwe-upwiz-desc-lang-another') + '</a>').click( function() { _this.addDescription() } ) ) |
| 312 | + ); |
| 313 | + |
| 314 | + |
| 315 | + $j(_this.div) |
| 316 | + .append(_this.thumbnailDiv) |
| 317 | + .append(_this.errorDiv) |
| 318 | + .append($j(_this.dataDiv) |
| 319 | + .append(_this.descriptionsContainerDiv)); |
| 320 | + |
| 321 | + |
| 322 | + |
| 323 | + // create the basic HTML |
| 324 | + // thumbnail |
| 325 | + |
| 326 | + |
| 327 | + // description in [ English ] |
| 328 | + // description field |
| 329 | + // title |
| 330 | + |
| 331 | + // about this work |
| 332 | + // media type |
| 333 | + // date created |
| 334 | + // location widget |
| 335 | + |
| 336 | + // copyright info <--- THIS IS THE IMPORTANT BIT |
| 337 | + // Author |
| 338 | + // License |
| 339 | + |
| 340 | + // About the file... |
| 341 | + |
| 342 | + // Other info |
| 343 | + |
| 344 | + _this.addDescription(); |
| 345 | + $j(containerDiv).append(_this.div); |
| 346 | + |
| 347 | + |
| 348 | +}; |
| 349 | + |
| 350 | +mw.UploadWizardMetadata.prototype = { |
| 351 | + |
| 352 | + addDescription: function(languageCode) { |
| 353 | + var _this = this; |
| 354 | + if (languageCode === undefined) { |
| 355 | + languageCode = mw.getConfig('userLanguage'); |
| 356 | + } else { |
| 357 | + // is languageCodeuage sane? |
| 358 | + // if not, raise some kind of error |
| 359 | + } |
| 360 | + |
| 361 | + // we assume we always add new descriptions in the user's languageCodeuage |
| 362 | + var description = new mw.UploadWizardDescription(languageCode); |
| 363 | + $j(_this.descriptionsDiv).append(description.div); |
| 364 | + _this.descriptions.push(description); |
| 365 | + }, |
| 366 | + |
| 367 | + // this is a lot like upload ui's error -- should merge |
| 368 | + error: function() { |
| 369 | + var _this = this; |
| 370 | + var args = Array.prototype.slice.call(arguments); // copies arguments into a real array |
| 371 | + var msg = 'mwe-upwiz-upload-error-' + args[0]; |
| 372 | + $j(_this.errorDiv).append($j('<p class="mwe-upwiz-upload-error">' + gM(msg, args.slice(1)) + '</p>')); |
| 373 | + // apply a error style to entire did |
| 374 | + $j(_this.div).addClass('mwe-upwiz-upload-error'); |
| 375 | + $j(_this.dataDiv).hide(); |
| 376 | + $j(_this.errorDiv).show(); |
| 377 | + }, |
| 378 | + |
| 379 | + // just like error but with ok/cancel |
| 380 | + errorDuplicate: function(sessionKey, duplicates) { |
| 381 | + /* |
| 382 | + TODO - do something clever to get page URLs and image URLs |
| 383 | + var duplicatePageTitles = result.upload.warnings.duplicate; |
| 384 | + var duplicates = []; |
| 385 | + for (var i = 0; i < duplicates.length; i++) { |
| 386 | + imageInfo = mw.getJSON(undefined, |
| 387 | + {'titles' : duplicatePageTitles[i], 'prop' : 'imageinfo'}) |
| 388 | + function() { _this.renderUploads() }); |
| 389 | + duplicates.push({ |
| 390 | + // ?? async, so we should insert later... |
| 391 | + }) |
| 392 | + } |
| 393 | + */ |
| 394 | + _this.error('duplicate'); |
| 395 | + // add placeholder spinners to div, and then fetch the thumbnails and so on async |
| 396 | + // meanwhile... |
| 397 | + //$j(_this.errorDiv).append( |
| 398 | + // $j('<form></form>'); |
| 399 | + // same as normal error but you get to ok/cancel, which resubmits with ignore warnings |
| 400 | + }, |
| 401 | + |
| 402 | + // given the API result pull some info into the form (for instance, extracted from EXIF, desired filename) |
| 403 | + populateFromResult: function(result) { |
| 404 | + var upload = result.upload; |
| 405 | + _this.setThumbnail(upload.imageinfo, mw.getConfig('thumbnailWidth')); |
| 406 | + _this.setFilename(upload.filename); |
| 407 | + _this.setDescription(); // is there anything worthwhile here? image comment? |
| 408 | + _this.setDate(upload.metadata); |
| 409 | + _this.setLocation(upload.metadata); // we could be VERY clever with location sensing... |
| 410 | + //_this.setProvenance(result); |
| 411 | + //_this.setAuthor(_this.config.user, upload.exif.Copyright); |
| 412 | + }, |
| 413 | + |
| 414 | + // transform the url of the image into a suitable thumbnail url |
| 415 | + // XXX THIS IS EVIL AND WRONG TO DO ON THE CLIENT SIDE |
| 416 | + // the API should be returning a "thumbnail template" of sorts, as has been proposed before |
| 417 | + // desiredWidth is in pixels |
| 418 | + setThumbnail: function(imageInfo, desiredWidth) { |
| 419 | + |
| 420 | + var url = imageInfo.url; |
| 421 | + |
| 422 | + // may be off-by-ones if the image scaler on the server rounds differently |
| 423 | + var desiredHeight = desiredWidth; // assume square |
| 424 | + if (imageInfo.height && imageInfo.width) { |
| 425 | + desiredHeight = parseInt(imageInfo.height * (desiredWidth / imageInfo.width)); |
| 426 | + } |
| 427 | + |
| 428 | + if (url === undefined || url === '') { |
| 429 | + // XXX what the hell? |
| 430 | + } |
| 431 | + |
| 432 | + // the image url looks like: http://host/path/to/images/filename.jpg |
| 433 | + // a thumbnail url looks like: http://host/path/to/images/filename.jpg/120px-filename.jpg |
| 434 | + // filename.jpg must be identical in both cases |
| 435 | + |
| 436 | + var basename = url.exec(/\/([^\/]+)$/)[1]; |
| 437 | + |
| 438 | + if (basename == undefined || basename == '') { |
| 439 | + // XXX what the hell? |
| 440 | + } |
| 441 | + |
| 442 | + var thumbnailUrl = url + '/' + desiredWidth + 'px-' + basename; |
| 443 | + |
| 444 | + // this should already be true |
| 445 | + _this.thumbnail.width = desiredWidth; |
| 446 | + |
| 447 | + // but this might be new info |
| 448 | + _this.thumbnail.height = desiredHeight; |
| 449 | + _this.thumbnail.src = thumbnailUrl; |
| 450 | + |
| 451 | + }, |
| 452 | + |
| 453 | + getWikiText: function() { |
| 454 | + wikiText = ''; |
| 455 | + |
| 456 | + |
| 457 | + // http://commons.wikimedia.org/wiki/Template:Information |
| 458 | + |
| 459 | + // can we be more slick and do this with maps, applys, joins? |
| 460 | + var information = { |
| 461 | + 'description' : '', // {{lang|description in lang}}* required |
| 462 | + 'date' : '', // YYYY, YYYY-MM, or YYYY-MM-DD required - use jquery but allow editing, then double check for sane date. |
| 463 | + 'source' : '', // {{own}} or wikitext optional |
| 464 | + 'author' : '', // any wikitext, but particularly {{Creator:Name Surname}} required |
| 465 | + 'permission' : '', // leave blank; by default will be "see below" optional |
| 466 | + 'other_versions' : '', // pipe separated list, other versions optional |
| 467 | + 'other_fields' : '' // ??? additional table fields |
| 468 | + }; |
| 469 | + |
| 470 | + // sanity check the descriptions -- do not have two in the same lang |
| 471 | + // all should be a known lang |
| 472 | + for (var i = 0; i < _this.descriptions.length; i++) { |
| 473 | + // XXX trim the descriptions here, remove leading or trailing whitespace |
| 474 | + information['Description'] += _this.descriptions[i].getWikiText() + "\n"; |
| 475 | + } |
| 476 | + |
| 477 | + var info = ''; |
| 478 | + for (var key in information) { |
| 479 | + info += '|' + key/ + '=' + information[key]; |
| 480 | + } |
| 481 | + |
| 482 | + wikiText += "=={int:filedesc}==\n"; |
| 483 | + |
| 484 | + return '{{Information ' + info + '}}'; |
| 485 | + |
| 486 | + wikiText += "=={int:license}==\n"; |
| 487 | + // XXX get the real one -- usually dual license GFDL/cc-by-sa |
| 488 | + wikiText += "{{cc-by-sa-3.0}}\n"; |
| 489 | + // http://commons.wikimedia.org/wiki/Template:Information |
| 490 | + |
| 491 | + |
| 492 | + return wikiText; |
| 493 | + }, |
| 494 | + |
| 495 | + submit: function() { |
| 496 | + // XXX check state of metadata for okayness (license selected, at least one desc, sane filename) |
| 497 | + var wikiText = _this.getWikiText(); |
| 498 | + console.log(wikiText); |
| 499 | + // do some api call to edit the info |
| 500 | + |
| 501 | + // api.php ? action=edit & title=Talk:Main_Page & section=new & summary=Hello%20World & text=Hello%20everyone! & watch & basetimestamp=2008-03-20T17:26:39Z & token=cecded1f35005d22904a35cc7b736e18%2B%5C |
| 502 | + // caution this may result in a captcha response, which user will have to solve |
| 503 | + // |
| 504 | + |
| 505 | + // then, if the filename was changed, do another api call to move the page |
| 506 | + // THIS MAY NOT WORK ON ALL WIKIS. for instance, on Commons, it may be that only admins can move pages. This is another example of how |
| 507 | + // we need an "incomplete" upload status |
| 508 | + // we are presuming this File page is brand new, so let's not bother with the whole redirection deal. ('noredirect') |
| 509 | + |
| 510 | + /* |
| 511 | + Note: In this example, all parameters are passed in a GET request just for the sake of simplicity. However, action=move requires POST requests; GET requests will cause an error. Moving Main Pgae (sic) and its talk page to Main Page, without creating a redirect |
| 512 | + api.php ? action=move & from=Main%20Pgae & to=Main%20Page & reason=Oops,%20misspelling & movetalk & noredirect & token=58b54e0bab4a1d3fd3f7653af38e75cb%2B\ |
| 513 | + */ |
| 514 | + |
| 515 | + |
| 516 | + } |
| 517 | +}; |
| 518 | + |
| 519 | + |
| 520 | +mw.UploadWizard.prototype = { |
| 521 | + maxUploads: 10, // arbitrary |
| 522 | + maxSimultaneousUploads: 2, // arbitrary |
| 523 | + tabs: [ 'file', 'metadata', 'thanks' ], |
| 524 | + |
| 525 | + /* |
| 526 | + // list possible upload handlers in order of preference |
| 527 | + // these should all be in the mw.* namespace |
| 528 | + // hardcoded for now. maybe some registry system might work later, like, all |
| 529 | + // things which subclass off of UploadHandler |
| 530 | + uploadHandlers: [ |
| 531 | + 'FirefoggUploadHandler', |
| 532 | + 'XhrUploadHandler', |
| 533 | + 'ApiIframeUploadHandler', |
| 534 | + 'SimpleUploadHandler', |
| 535 | + 'NullUploadHandler' |
| 536 | + ], |
| 537 | + |
| 538 | + // let's figure out exactly what we can use. |
| 539 | + // |
| 540 | + getUploadHandlerClass: function() { |
| 541 | + var _this = this; |
| 542 | + for (var i = 0; i < uploadHandlers.length; i++) { |
| 543 | + var klass = mw[uploadHandlers[i]]; |
| 544 | + if (klass != undefined && klass.canRun(this.config)) { |
| 545 | + return klass; |
| 546 | + } |
| 547 | + } |
| 548 | + // this should never happen; NullUploadHandler should always work |
| 549 | + return null; |
| 550 | + }, |
| 551 | + */ |
| 552 | + |
| 553 | + getUploadHandlerClass: function() { |
| 554 | + return mw.ApiUploadHandler; |
| 555 | + }, |
| 556 | + |
| 557 | + // create the basic interface to make an upload in this div |
| 558 | + createInterface: function(div) { |
| 559 | + var _this = this; |
| 560 | + div.innerHTML = |
| 561 | + |
| 562 | + '<div id="mwe-upwiz-tabs" class="mwe-upwiz-tabs">' |
| 563 | + + '<ul>' |
| 564 | + + '<li id="mwe-upwiz-tab-file">' + gM('mwe-upwiz-tab-file') + '</li>' |
| 565 | + + '<li id="mwe-upwiz-tab-metadata">' + gM('mwe-upwiz-tab-metadata') + '</li>' |
| 566 | + + '<li id="mwe-upwiz-tab-thanks">' + gM('mwe-upwiz-tab-thanks') + '</li>' |
| 567 | + + '</ul>' |
| 568 | + + '</div>' |
| 569 | + |
| 570 | + |
| 571 | + + '<div id="mwe-upwiz-content">' |
| 572 | + + '<div id="mwe-upwiz-tabdiv-file">' |
| 573 | + + '<div id="mwe-upwiz-intro">' + gM('mwe-upwiz-intro') + '</div>' |
| 574 | + + '<div id="mwe-upwiz-select-files">' + gM('mwe-upwiz-select-files') + '</div>' |
| 575 | + + '<div id="mwe-upwiz-files"></div>' |
| 576 | + + '<div><a id="mwe-upwiz-add-file">' + gM("mwe-upwiz-add-file-0") + '</a></div>' |
| 577 | + + '<div><button id="mwe-upwiz-upload-ctrl" disabled="disabled">' + gM("mwe-upwiz-upload") + '</button></div>' |
| 578 | + + '<div id="mwe-upwiz-progress" style="display:none">' |
| 579 | + + '<div>' |
| 580 | + + '<div id="mwe-upwiz-progress-bar"></div>' |
| 581 | + + '<div id="mwe-upwiz-etr"></div>' |
| 582 | + + '</div>' |
| 583 | + + '<div id="mwe-upwiz-count"></div>' |
| 584 | + + '</div>' |
| 585 | + + '<div style="clear: left;"></div>' |
| 586 | + + '</div>' |
| 587 | + + '</div>' |
| 588 | + + '<div id="mwe-upwiz-tabdiv-metadata">' |
| 589 | + + '<div id="mwe-upwiz-metadata-macro"></div>' |
| 590 | + + '<div id="mwe-upwiz-metadata-files"></div>' |
| 591 | + + '</div>' |
| 592 | + + '<div id="mwe-upwiz-tabdiv-thanks">' |
| 593 | + + '<div id="mwe-upwiz-thanks"></div>' |
| 594 | + + '</div>' |
| 595 | + +'</div>' |
| 596 | + |
| 597 | + + '<div id="mwe-upwiz-clearing"></div>'; |
| 598 | + |
| 599 | + // within FILE tab div |
| 600 | + // select files: |
| 601 | + // place for file interfaces |
| 602 | + $j('#mwe-upwiz-add-file').click( function() { _this.addUpload() } ); |
| 603 | + $j('#mwe-upwiz-upload-ctrl').click( function() { _this.startUploads() } ); |
| 604 | + |
| 605 | + // Create global progress bar |
| 606 | + $j( '#mwe-upwiz-progress-bar' ).progressbar({ |
| 607 | + value: 0 |
| 608 | + }); |
| 609 | + |
| 610 | + // add one to start |
| 611 | + _this.addUpload(); |
| 612 | + |
| 613 | + // "select" the first tab - highlight, make it visible, hide all others |
| 614 | + _this.moveToTab('file'); |
| 615 | + }, |
| 616 | + |
| 617 | + moveToTab: function(selectedTabName) { |
| 618 | + var _this = this; |
| 619 | + for (var i=0; i < _this.tabs.length; i++) { |
| 620 | + tabName = _this.tabs[i]; |
| 621 | + var tabDiv = $j('#mwe-upwiz-tabdiv-' + tabName); |
| 622 | + var tab = $j('#mwe-upwiz-tab-' + tabName); |
| 623 | + if (selectedTabName == tabName) { |
| 624 | + tabDiv.show(); |
| 625 | + tab.addClass('mwe-upwiz-tab-highlight'); |
| 626 | + } else { |
| 627 | + //tabDiv.hide(); |
| 628 | + tab.removeClass('mwe-upwiz-tab-highlight'); |
| 629 | + } |
| 630 | + } |
| 631 | + // XXX possibly select appropriate form field to begin work |
| 632 | + }, |
| 633 | + |
| 634 | + // add an Upload, with controls. |
| 635 | + // XXX study what Mdale is doing to create this file form controls... he has full control over CSS etc and the browsing button. |
| 636 | + addUpload: function() { |
| 637 | + var _this = this; |
| 638 | + var idx = _this.uploads.length; // or? |
| 639 | + if (idx + 1 > _this.maxUploads) { |
| 640 | + return false; |
| 641 | + } |
| 642 | + |
| 643 | + // could (should?) be an object, but so far it doesn't have its own methods really. |
| 644 | + // plus, here in UploadWizard, we have additional concepts like metadata... |
| 645 | + var upload = {}; |
| 646 | + |
| 647 | + // API |
| 648 | + // XXX hardcoded for now. Maybe passed through config or guessed at here. |
| 649 | + // upload.api = new mw.UploadApiProcessor(function(result) { _this.uploadCompleted); |
| 650 | + |
| 651 | + |
| 652 | + // UI |
| 653 | + var filenameAcceptedCb = function() { |
| 654 | + _this.updateFileCounts(); |
| 655 | + }; |
| 656 | + var ui = new mw.UploadWizardUploadInterface(filenameAcceptedCb); |
| 657 | + ui.removeCtrl = $j('<a title="' + gM( 'mwe-upwiz-remove-upload') + '" href="#" class="mwe-upwiz-file-remove">x</a>') |
| 658 | + .click( function() { _this.removeUpload(upload) } ) |
| 659 | + .get(0); |
| 660 | + $j(ui.div).append(ui.removeCtrl); |
| 661 | + |
| 662 | + upload.ui = ui; |
| 663 | + |
| 664 | + // handler -- usually ApiUploadHandler |
| 665 | + upload.handler = new _this.uploadHandlerClass(upload.ui); |
| 666 | + |
| 667 | + // this is for UI only... |
| 668 | + upload.handler.addProgressCb( function(fraction) { _this.uploadProgress(upload, fraction) } ); |
| 669 | + |
| 670 | + // this is only the UI one, so is the result even going to be there? |
| 671 | + upload.handler.addCompletedCb( function(result) { _this.uploadCompleted(upload, result) } ); |
| 672 | + |
| 673 | + // not sure about this...UI only? |
| 674 | + // this will tell us that at least one of our uploads has had an error -- may change messaging, |
| 675 | + // like, please fix below |
| 676 | + upload.handler.addErrorCb( function(error) { _this.uploadError(upload, error) } ); |
| 677 | + |
| 678 | + // metadata |
| 679 | + upload.metadata = new mw.UploadWizardMetadata($j('#mwe-upwiz-metadata-files')); |
| 680 | + |
| 681 | + |
| 682 | + |
| 683 | + _this.uploads.push(upload); |
| 684 | + |
| 685 | + $j("#mwe-upwiz-files").append(upload.ui.div); |
| 686 | + |
| 687 | + // update the uploadUi to add files - we may be over limit |
| 688 | + _this.updateFileCounts(); |
| 689 | + |
| 690 | + // the next thing we probably want to do is to get the file, so let's save them a click. |
| 691 | + // XXX why doesn't this work? |
| 692 | + // $j(ui.fileInputCtrl).trigger('click'); |
| 693 | + }, |
| 694 | + |
| 695 | + /* Remove an upload from our array of uploads, and the HTML UI |
| 696 | + We can remove the HTML UI directly, as jquery will just get the parent. |
| 697 | + We need to grep through the array of uploads, since we don't know the current index. */ |
| 698 | + removeUpload: function(upload) { |
| 699 | + var _this = this; |
| 700 | + $j(upload.ui.div).remove(); |
| 701 | + $j(upload.metadata.div).remove(); |
| 702 | + _this.removeItem(_this.uploads, upload); |
| 703 | + _this.updateFileCounts(); |
| 704 | + }, |
| 705 | + |
| 706 | + // using a second array to iterate, because we will be splicing the main one, _this.uploads |
| 707 | + removeEmptyUploads: function() { |
| 708 | + var _this = this; |
| 709 | + var toRemove = []; |
| 710 | + for (var i = 0; i < _this.uploads.length; i++) { |
| 711 | + if (_this.uploads[i].ui.fileInputCtrl.value == "") { |
| 712 | + toRemove.push(_this.uploads[i]); |
| 713 | + } |
| 714 | + }; |
| 715 | + for (var i = 0; i < toRemove.length; i++) { |
| 716 | + _this.removeUpload(toRemove[i]); |
| 717 | + } |
| 718 | + }, |
| 719 | + |
| 720 | + startUploads: function() { |
| 721 | + var _this = this; |
| 722 | + _this.removeEmptyUploads(); |
| 723 | + // remove the upload button, and the add file button |
| 724 | + $j('#mwe-upwiz-upload-ctrl').hide(); |
| 725 | + $j('#mwe-upwiz-add-file').hide(); |
| 726 | + |
| 727 | + // remove ability to change files |
| 728 | + // ideally also hide the "button"... but then we require styleable file input CSS trickery |
| 729 | + // although, we COULD do this just for files already in progress... |
| 730 | + |
| 731 | + // XXX we just want to VISUALLY lock it in -- disabling this seems to remove it from form post |
| 732 | + // $j('.mwe-upwiz-file').attr('disabled', 'disabled'); |
| 733 | + |
| 734 | + // add the upload progress bar, with ETA |
| 735 | + // add in the upload count |
| 736 | + $j('#mwe-upwiz-progress').show(); |
| 737 | + |
| 738 | + _this.uploadsBeginTime = (new Date()).getTime(); |
| 739 | + |
| 740 | + var canGetFileSize = (_this.uploadHandlerClass.prototype.getFileSize !== undefined); |
| 741 | + |
| 742 | + // queue the uploads |
| 743 | + _this.totalWeight = 0; |
| 744 | + for (var i = 0; i < _this.uploads.length; i++) { |
| 745 | + var upload = _this.uploads[i]; |
| 746 | + |
| 747 | + // we may want to do something clever here to detect |
| 748 | + // whether this is a real or dummy weight |
| 749 | + if (canGetFileSize) { |
| 750 | + upload.weight = upload.getFileSize(); |
| 751 | + } else { |
| 752 | + upload.weight = 1; |
| 753 | + } |
| 754 | + _this.totalWeight += upload.weight; |
| 755 | + |
| 756 | + _this._uploadsQueued.push(upload); |
| 757 | + } |
| 758 | + setTimeout( function () { _this._startUploadsQueued(); }, 0 ); |
| 759 | + }, |
| 760 | + |
| 761 | + // making this another thread of execution, because we want to avoid any race condition |
| 762 | + // this way, this is the only "thread" that can start uploads |
| 763 | + // it may miss a newly completed upload but it will get it eventually |
| 764 | + _startUploadsQueued: function() { |
| 765 | + var _this = this; |
| 766 | + var uploadsToStart = Math.min(_this.maxSimultaneousUploads - _this._uploadsInProgress.length, _this._uploadsQueued.length); |
| 767 | + console.log("_startUploadsQueued: should start " + uploadsToStart + " uploads"); |
| 768 | + while (uploadsToStart--) { |
| 769 | + var upload = _this._uploadsQueued.shift(); |
| 770 | + _this._uploadsInProgress.push(upload); |
| 771 | + upload.handler.start(); |
| 772 | + } |
| 773 | + if (_this._uploadsQueued.length) { |
| 774 | + setTimeout( function () { _this._startUploadsQueued(); }, 1000 ); |
| 775 | + } |
| 776 | + }, |
| 777 | + |
| 778 | + |
| 779 | + // could be a spinning loop by itself, but this is annoying to debug |
| 780 | + // and then we'd have to be careful to note the state-transition to completed once and only once. Race conditions. |
| 781 | + showProgress: function() { |
| 782 | + var _this = this; |
| 783 | + if (_this.isCompleted) { |
| 784 | + return; |
| 785 | + } |
| 786 | + |
| 787 | + //var updateFileCounts = false; |
| 788 | + var fraction = 0; |
| 789 | + for (var i = 0; i < _this.uploads.length; i++) { |
| 790 | + var upload = _this.uploads[i]; |
| 791 | + console.log("progress of " + upload.ui.fileInputCtrl.value + " = " + upload.progress); |
| 792 | + fraction += upload.progress * (upload.weight / _this.totalWeight); |
| 793 | + } |
| 794 | + _this.showProgressBar(fraction); |
| 795 | + |
| 796 | + var remainingTime = _this.getRemainingTime(_this.uploadsBeginTime, fraction); |
| 797 | + if (remainingTime !== null) { |
| 798 | + _this.showRemainingTime(remainingTime); |
| 799 | + } |
| 800 | + }, |
| 801 | + |
| 802 | + // show the progress bar |
| 803 | + showProgressBar: function(fraction) { |
| 804 | + $j( '#mwe-upwiz-progress-bar' ).progressbar( 'value', parseInt( fraction * 100 ) ); |
| 805 | + }, |
| 806 | + |
| 807 | + // show remaining time for all the uploads |
| 808 | + // remainingTime is in milliseconds |
| 809 | + // XXX should be localized - x hours, x minutes, x seconds |
| 810 | + showRemainingTime: function(remainingTime) { |
| 811 | + $j( '#mwe-upwiz-etr' ).html( gM( 'mwe-upwiz-remaining', mw.seconds2npt(parseInt(remainingTime / 1000)) ) ); |
| 812 | + }, |
| 813 | + |
| 814 | + |
| 815 | + // XXX should be refactored with the very similar code in mw.UploadInterface.js:updateProgress |
| 816 | + // returns time in whatever units getTime() returns; presumed milliseconds |
| 817 | + getRemainingTime: function (beginTime, fractionCompleted) { |
| 818 | + if ( beginTime ) { |
| 819 | + var elapsedTime = ( new Date() ).getTime() - beginTime; |
| 820 | + if (fractionCompleted > 0.0 && elapsedTime > 0) { // or some other minimums for good data |
| 821 | + var rate = fractionCompleted / elapsedTime; |
| 822 | + return parseInt( ( 1.0 - fractionCompleted ) / rate ); |
| 823 | + } |
| 824 | + } |
| 825 | + return null; |
| 826 | + }, |
| 827 | + |
| 828 | + // okay we are in a confusing state here -- are we asking for progress to be stored in the uploadhandler for our perusal or |
| 829 | + // to be explicitly forwarded to us |
| 830 | + uploadProgress: function(upload, progress) { |
| 831 | + console.log("upload progress is " + progress); |
| 832 | + var _this = this; |
| 833 | + //debugger; |
| 834 | + upload.progress = progress; |
| 835 | + _this.showProgress(); |
| 836 | + }, |
| 837 | + |
| 838 | + uploadCompleted: function(upload, result) { |
| 839 | + var _this = this; |
| 840 | + _this._uploadsCompleted.push(upload); |
| 841 | + _this.removeItem(_this._uploadsInProgress, upload); |
| 842 | + |
| 843 | + if ( result.upload && result.upload.imageinfo && result.upload.imageinfo.descriptionurl ) { |
| 844 | + setTimeout( function() { upload.metadata.populateFromResult(result); }, 0 ); |
| 845 | + |
| 846 | + } else if (result.upload && result.upload.sessionkey) { |
| 847 | + // there was a warning-type error which prevented it from adding the result to the db |
| 848 | + if (result.upload.warnings.duplicate) { |
| 849 | + var duplicates = result.upload.warnings.duplicate; |
| 850 | + _this.metadata.errorDuplicate(result.upload.sessionkey, duplicates); |
| 851 | + } |
| 852 | + |
| 853 | + // XXX namespace collision |
| 854 | + // and other errors that result in a stash |
| 855 | + } else if (0 /* actual failure */) { |
| 856 | + // we may want to tag or otherwise queue it as an upload to retry |
| 857 | + } |
| 858 | + |
| 859 | + _this.updateFileCounts(); |
| 860 | + }, |
| 861 | + |
| 862 | + |
| 863 | + // depending on number of file upoads, change link text (and/or disable it) |
| 864 | + // change button disabled/enabled |
| 865 | + updateFileCounts: function() { |
| 866 | + console.log("update counts"); |
| 867 | + var _this = this; |
| 868 | + var link = $j('#mwe-upwiz-add-file').get(0); |
| 869 | + link.innerHTML = gM( |
| 870 | + 'mwe-upwiz-add-file-' + (_this.uploads.length === 0 ? '0' : 'n') |
| 871 | + ); |
| 872 | + if (_this.uploads.length < _this.maxUploads) { |
| 873 | + link.removeAttribute('disabled'); |
| 874 | + } else { |
| 875 | + link.setAttribute('disabled', true); |
| 876 | + } |
| 877 | + |
| 878 | + var hasFile; |
| 879 | + for (var i = 0; i < _this.uploads.length; i++) { |
| 880 | + var upload = _this.uploads[i]; |
| 881 | + if (upload.ui.fileInputCtrl.value != "") { |
| 882 | + hasFile = true; |
| 883 | + } |
| 884 | + } |
| 885 | + if (hasFile) { |
| 886 | + $j('#mwe-upwiz-upload-ctrl').removeAttr('disabled'); |
| 887 | + } else { |
| 888 | + $j('#mwe-upwiz-upload-ctrl').attr('disabled', 'disabled'); |
| 889 | + } |
| 890 | + |
| 891 | + |
| 892 | + $j('#mwe-upwiz-count').html( gM('mwe-upwiz-upload-count', [ _this._uploadsCompleted.length, _this.uploads.length ]) ); |
| 893 | + |
| 894 | + if (_this._uploadsCompleted.length == _this.uploads.length) { |
| 895 | + // is this enough to stop the progress monitor? |
| 896 | + _this.isCompleted = true; |
| 897 | + // set progress to 100% |
| 898 | + _this.showProgressBar(1); |
| 899 | + _this.showRemainingTime(0); |
| 900 | + |
| 901 | + // XXX then should make the progress bar not have the animated lines when done. Solid blue, or fade away or something. |
| 902 | + // likewise, the remaining time should disappear, fadeout maybe. |
| 903 | + |
| 904 | + // do some sort of "all done" thing for the UI - advance to next tab maybe. |
| 905 | + alert("all done!"); |
| 906 | + _this.moveToTab('metadata'); |
| 907 | + } |
| 908 | + |
| 909 | + }, |
| 910 | + |
| 911 | + |
| 912 | + pause: function() { |
| 913 | + |
| 914 | + }, |
| 915 | + |
| 916 | + stop: function() { |
| 917 | + |
| 918 | + }, |
| 919 | + |
| 920 | + // |
| 921 | + // entire METADATA TAB |
| 922 | + // |
| 923 | + |
| 924 | + createMetadata: function() { |
| 925 | + |
| 926 | + }, |
| 927 | + |
| 928 | + submitMetadata: function() { |
| 929 | + |
| 930 | + }, |
| 931 | + |
| 932 | + // utility function. not an object method |
| 933 | + removeItem: function(items, item) { |
| 934 | + for (var i = 0; i < items.length; i++) { |
| 935 | + if (items[i] === item) { |
| 936 | + items.splice(i, 1); |
| 937 | + } |
| 938 | + } |
| 939 | + }, |
| 940 | + |
| 941 | + |
| 942 | + |
| 943 | +}; |
| 944 | + |
Property changes on: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.UploadWizard.js |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 945 | + native |
Index: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/css/uploadWizard.css |
— | — | @@ -0,0 +1,117 @@ |
| 2 | +/* XXX all these absolute pixel floats are evil, will bite us when i18n-ized */ |
| 3 | +/* XXX mwe-upwiz-add-file jumps to the right when all file inputs are removed */ |
| 4 | + |
| 5 | +form.mwe-upwiz-form { |
| 6 | + display: inline; |
| 7 | +} |
| 8 | + |
| 9 | +.upload-section { |
| 10 | + padding: 1em; |
| 11 | + margin-bottom: 0.5em; |
| 12 | + margin-top: 0.5em; |
| 13 | + border: 1px solid #e0e0e0; |
| 14 | +} |
| 15 | + |
| 16 | +#mwe-upwiz-tabs { |
| 17 | + float: left; |
| 18 | + width: 100px; |
| 19 | +} |
| 20 | + |
| 21 | +#mwe-upwiz-content { |
| 22 | + margin-left: 100px; |
| 23 | +} |
| 24 | + |
| 25 | +#mwe-upwiz-clearing { |
| 26 | + clear: left; |
| 27 | + width: 100%; |
| 28 | +} |
| 29 | + |
| 30 | +#mwe-upwiz-tabs ul { |
| 31 | + list-style-type: none; |
| 32 | + list-style-image: none; |
| 33 | +} |
| 34 | + |
| 35 | +#mwe-upwiz-tabs ul li { |
| 36 | + padding: 0.5em; |
| 37 | + margin: 0px; |
| 38 | + color: #999999; |
| 39 | + border-color: #e0e0e0; |
| 40 | + border-width: 1px 0px 1px 1px; |
| 41 | + border-style: solid; |
| 42 | +} |
| 43 | + |
| 44 | + |
| 45 | +#mwe-upwiz-tabs ul li.mwe-upwiz-tab-highlight { |
| 46 | + font-weight: bold; |
| 47 | + color: black; |
| 48 | + border-color: #999999; |
| 49 | +} |
| 50 | + |
| 51 | +#mwe-upwiz-content { |
| 52 | + padding: 1em; |
| 53 | +} |
| 54 | + |
| 55 | +/* perhaps a general class for links that are actually "buttons" */ |
| 56 | +#mwe-upwiz-add-file, .mwe-upwiz-file-remove { |
| 57 | + cursor: pointer; |
| 58 | +} |
| 59 | + |
| 60 | +#mwe-upwiz-select-files { |
| 61 | + float: left; |
| 62 | + width: 100px; |
| 63 | +} |
| 64 | + |
| 65 | +#mwe-upwiz-files { |
| 66 | + margin-left: 100px; |
| 67 | +} |
| 68 | + |
| 69 | +#mwe-upwiz-add-file { |
| 70 | + margin-left: 100px; |
| 71 | +} |
| 72 | + |
| 73 | +.mwe-upwiz-file-remove { |
| 74 | + margin-left: 5px; |
| 75 | + padding: 5px; |
| 76 | +} |
| 77 | + |
| 78 | +a.mwe-upwiz-file-remove:link, |
| 79 | +a.mwe-upwiz-file-remove:active, |
| 80 | +a.mwe-upwiz-file-remove:visited { |
| 81 | + color: #999999; |
| 82 | + text-decoration: none; |
| 83 | +} |
| 84 | + |
| 85 | +a.mwe-upwiz-file-remove:hover { |
| 86 | + color: #ff0000; |
| 87 | + text-decoration: none; |
| 88 | +} |
| 89 | + |
| 90 | +a[disabled=true] { |
| 91 | + color: #999999; |
| 92 | + text-decoration: none; |
| 93 | + cursor: default; |
| 94 | +} |
| 95 | + |
| 96 | +a[disabled=true]:hover { |
| 97 | + text-decoration: none; |
| 98 | +} |
| 99 | + |
| 100 | +.mwe-upwiz-status-progress { |
| 101 | + font-weight: bold; |
| 102 | + color: #ff9900; |
| 103 | +} |
| 104 | + |
| 105 | +/* XXX add background of checkmark, with left-padding offset */ |
| 106 | +.mwe-upwiz-status-completed { |
| 107 | + font-weight: bold; |
| 108 | + color: #009900; |
| 109 | +} |
| 110 | + |
| 111 | +.mwe-upwiz-upload-warning { |
| 112 | + background: #ffffe0; |
| 113 | +} |
| 114 | + |
| 115 | +.mwe-upwiz-metadata-error { |
| 116 | + display: none; |
| 117 | + background: #ffffe0; |
| 118 | +} |
Property changes on: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/css/uploadWizard.css |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 119 | + native |
Index: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.ApiUploadHandler.js |
— | — | @@ -0,0 +1,135 @@ |
| 2 | +/** |
| 3 | + * An attempt to refactor out the stuff that does API-via-iframe transport |
| 4 | + * In the hopes that this will eventually work for AddMediaWizard too |
| 5 | + */ |
| 6 | + |
| 7 | +// n.b. if there are message strings, or any assumption about HTML structure of the form. |
| 8 | +// then we probably did it wrong |
| 9 | + |
| 10 | +mw.ApiUploadHandler = function(ui) { |
| 11 | + var _this = this; |
| 12 | + |
| 13 | + _this.ui = ui; |
| 14 | + |
| 15 | + var form = _this.ui.form; |
| 16 | + |
| 17 | + _this.completedCallbacks = []; |
| 18 | + _this.progressCallbacks = []; |
| 19 | + _this.errorCallbacks = []; |
| 20 | + |
| 21 | + _this.configureForm(); |
| 22 | + |
| 23 | + // hardcoded for now |
| 24 | + // can also use Xhr Binary depending on config |
| 25 | + _this.transport = new mw.IframeTransport( |
| 26 | + _this.ui.form, |
| 27 | + function(fraction){ _this.progress(fraction) }, |
| 28 | + function(result) { _this.completed(result) } |
| 29 | + ); |
| 30 | + |
| 31 | +}; |
| 32 | + |
| 33 | +mw.ApiUploadHandler.prototype = { |
| 34 | + addProgressCb: function(fn) { |
| 35 | + var _this = this; |
| 36 | + _this.progressCallbacks.push(function(progress) { fn(progress) }); |
| 37 | + }, |
| 38 | + |
| 39 | + |
| 40 | + addCompletedCb: function(fn) { |
| 41 | + var _this = this; |
| 42 | + _this.completedCallbacks.push(function() { fn() }); |
| 43 | + }, |
| 44 | + |
| 45 | + addErrorCb: function(fn) { |
| 46 | + var _this = this; |
| 47 | + _this.errorCallbacks.push(function() { fn(error) }); |
| 48 | + }, |
| 49 | + |
| 50 | + configureForm: function() { |
| 51 | + var apiUrl = mw.getLocalApiUrl(); // XXX or? throw new Error("configuration", "no API url"); |
| 52 | + if (! (mw.getConfig('token') ) ) { |
| 53 | + throw new Error("configuration", "no edit token"); |
| 54 | + } |
| 55 | + |
| 56 | + var _this = this; |
| 57 | + console.log("configuring form for Upload API"); |
| 58 | + |
| 59 | + // Set the form action |
| 60 | + try { |
| 61 | + $j(_this.ui.form) |
| 62 | + .attr('action', apiUrl) |
| 63 | + .attr('method', 'POST') |
| 64 | + .attr('enctype', 'multipart/form-data'); |
| 65 | + } catch ( e ) { |
| 66 | + alert("oops, form modification didn't work in ApiUploadHandler"); |
| 67 | + mw.log("IE for some reason error's out when you change the action"); |
| 68 | + // well, if IE fucks this up perhaps we should do something to make sure it writes correctly |
| 69 | + // from the outset? |
| 70 | + } |
| 71 | + |
| 72 | + _this.addFormInputIfMissing('token', mw.getConfig('token')); |
| 73 | + _this.addFormInputIfMissing('action', 'upload'); |
| 74 | + _this.addFormInputIfMissing('format', 'jsonfm'); |
| 75 | + }, |
| 76 | + |
| 77 | + addFormInputIfMissing: function(name, value) { |
| 78 | + var _this = this; |
| 79 | + var $jForm = $j(_this.ui.form); |
| 80 | + if ( $jForm.find( "[name='" + name + "']" ).length == 0 ) { |
| 81 | + $jForm.append( |
| 82 | + $j('<input />') |
| 83 | + .attr({ |
| 84 | + 'type': "hidden", |
| 85 | + 'name' : name, |
| 86 | + 'value' : value |
| 87 | + }) |
| 88 | + ); |
| 89 | + } |
| 90 | + }, |
| 91 | + |
| 92 | + start: function() { |
| 93 | + var _this = this; |
| 94 | + console.log("api: upload start!") |
| 95 | + _this.beginTime = (new Date()).getTime(); |
| 96 | + _this.ui.start(); |
| 97 | + _this.ui.busy(); |
| 98 | + $j(this.ui.form).submit(); |
| 99 | + }, |
| 100 | + |
| 101 | + progress: function(fraction) { |
| 102 | + console.log("api: upload progress!") |
| 103 | + var _this = this; |
| 104 | + _this.ui.progress(fraction); |
| 105 | + for (var i = 0; i < _this.progressCallbacks.length; i++) { |
| 106 | + debugger; |
| 107 | + _this.progressCallbacks[i](fraction); |
| 108 | + } |
| 109 | + }, |
| 110 | + |
| 111 | + // this is not quite the right place for all this code |
| 112 | + // perhaps should be abstract to any uploadHandler, or not |
| 113 | + // in this at all |
| 114 | + completed: function(result) { |
| 115 | + console.log("api: upload completed!") |
| 116 | + var _this = this; |
| 117 | + |
| 118 | + _this.ui.completed(); |
| 119 | + |
| 120 | + for (var i = 0; i < _this.completedCallbacks.length; i++) { |
| 121 | + _this.completedCallbacks[i](result); |
| 122 | + } |
| 123 | + }, |
| 124 | + |
| 125 | + error: function(error) { |
| 126 | + console.log("api: error!"); |
| 127 | + var _this = this; |
| 128 | + _this.ui.error(error); |
| 129 | + for (var i = 0; i < _this.errorCallbacks.length; i++) { |
| 130 | + _this.errorCallbacks[i](error); |
| 131 | + } |
| 132 | + } |
| 133 | +}; |
| 134 | + |
| 135 | + |
| 136 | + |
Property changes on: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.ApiUploadHandler.js |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 137 | + native |
Index: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.MockUploadHandler.js |
— | — | @@ -0,0 +1,75 @@ |
| 2 | +// Currently this doesn't at all follow the interface that Mdale made in UploadHandler |
| 3 | +// will have to figure this out. |
| 4 | + |
| 5 | +// this should be loaded with test suites when appropriate. separate file. |
| 6 | +mw.MockUploadHandler = function(ui) { |
| 7 | + this.ui = ui; |
| 8 | + this.nextState = null; |
| 9 | + this.progress = 0.0; |
| 10 | + this.isCompleted = false; |
| 11 | + |
| 12 | + _this.completedCallbacks = []; |
| 13 | + _this.progressCallbacks = []; |
| 14 | + _this.warningCallbacks = []; |
| 15 | + |
| 16 | + // if we ever get a pause control, |
| 17 | + // may need to turn this into an array of pairs, start & stops |
| 18 | + this.beginTime = null; |
| 19 | +}; |
| 20 | + |
| 21 | +mw.MockUploadHandler.prototype = { |
| 22 | + addProgressCb: function(fn) { |
| 23 | + var _this = this; |
| 24 | + _this.progressCallbacks.push(function(progress) { fn(_this, progress) }); |
| 25 | + }, |
| 26 | + |
| 27 | + |
| 28 | + addCompletedCb: function(fn) { |
| 29 | + var _this = this; |
| 30 | + _this.completedCallbacks.push(function() { fn(_this) }); |
| 31 | + }, |
| 32 | + |
| 33 | + addWarningCb: function(fn) { |
| 34 | + var _this = this; |
| 35 | + _this.warningCallbacks.push(function() { fn(_this, warning) }); |
| 36 | + }, |
| 37 | + |
| 38 | + |
| 39 | + start: function () { |
| 40 | + var _this = this; |
| 41 | + _this.beginTime = (new Date()).getTime(); |
| 42 | + _this.nextState = _this.cont; |
| 43 | + _this.ui.start(); |
| 44 | + _this.nextState(); |
| 45 | + }, |
| 46 | + cont: function () { |
| 47 | + var _this = this; |
| 48 | + var delta = 0.0001; // static? |
| 49 | + _this.progress += 0.1; |
| 50 | + _this.ui.progress(_this.progress); |
| 51 | + _this.progressCb(_this, _this.progress); |
| 52 | + if (1.0 - _this.progress < delta) { |
| 53 | + _this.completed(); |
| 54 | + } else { |
| 55 | + setTimeout( function() { _this.nextState() }, 1000 ); |
| 56 | + } |
| 57 | + }, |
| 58 | + |
| 59 | + completed: function() { |
| 60 | + var _this = this; |
| 61 | + _this.isCompleted = true; |
| 62 | + _this.ui.completed(); |
| 63 | + _this.completedCb(_this); |
| 64 | + }, |
| 65 | + stop: function () { |
| 66 | + // tell the interface that we're stopped |
| 67 | + }, |
| 68 | + |
| 69 | + //pause: function () { // }, |
| 70 | + //resume: function () { // this.nextState = this.stop(); }, |
| 71 | + remove: function () { |
| 72 | + // ?? |
| 73 | + } |
| 74 | +}; |
| 75 | + |
| 76 | + |
Property changes on: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.MockUploadHandler.js |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 77 | + native |
Index: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.UploadApiProcessor.js |
— | — | @@ -0,0 +1,296 @@ |
| 2 | +// might be useful to raise exceptions when the api goes wrong |
| 3 | +// however, when everything is async, then who receives them? |
| 4 | +mw.UploadApiProcessor = function(doneCb, errorCb) { |
| 5 | + var _this = this; |
| 6 | + _this.doneCb = doneCb; |
| 7 | + _this.errorCb = errorCb; |
| 8 | +}; |
| 9 | + |
| 10 | + |
| 11 | +mw.UploadApiProcessor.prototype = { |
| 12 | + |
| 13 | + warnings: [ |
| 14 | + 'badfilename', // was the resultant filename different from desired? If so return what we actually got in the 'badfilename' warnings |
| 15 | + 'filetype-unwanted-type', // bad filetype, as determined from extenstion. content is the bad extension |
| 16 | + 'large-file', // $wgUploadSizeWarning, numeric value of largest size (bytes, kb, what?) |
| 17 | + 'emptyfile', // set to "true" if file was empty |
| 18 | + 'exists', // set to true if file by that name already existed |
| 19 | + 'duplicate', // hash collision found |
| 20 | + 'duplicate-archive' // hash collision found in archives |
| 21 | + ], |
| 22 | + |
| 23 | + errors: [ |
| 24 | + 'empty-file', |
| 25 | + 'filetype-missing', //(missing an extension) |
| 26 | + 'filetype-banned', // (extension banned) // also returns: { filetype => the filetype we thought it was, allowed => [ extensions ] } |
| 27 | + 'filename-tooshort', |
| 28 | + 'illegal-filename', // { filename => verification[filtered] } |
| 29 | + 'overwrite', // overwrite existing file (failed?) |
| 30 | + 'verification-error', // {details => verification-details} |
| 31 | + 'hookaborted', // error => verificationerror } |
| 32 | + 'unknown_error' |
| 33 | + ], |
| 34 | + |
| 35 | + |
| 36 | + // NB |
| 37 | + // It's not clear if we can even get these errors (error_msg_key, error_onlykey) any more |
| 38 | + |
| 39 | + // There are many possible error messages here, so we don't load all |
| 40 | + // message text in advance, instead we use mw.getRemoteMsg() for some. |
| 41 | + // |
| 42 | + // This code is similar to the error handling code formerly in |
| 43 | + // SpecialUpload::processUpload() |
| 44 | + error_msg_key: { |
| 45 | + '2' : 'largefileserver', |
| 46 | + '3' : 'emptyfile', |
| 47 | + '4' : 'minlength1', |
| 48 | + '5' : 'illegalfilename' |
| 49 | + }, |
| 50 | + |
| 51 | + // NOTE:: handle these error types |
| 52 | + error_onlykey: { |
| 53 | + '1': 'BEFORE_PROCESSING', |
| 54 | + '6': 'PROTECTED_PAGE', |
| 55 | + '7': 'OVERWRITE_EXISTING_FILE', |
| 56 | + '8': 'FILETYPE_MISSING', |
| 57 | + '9': 'FILETYPE_BADTYPE', |
| 58 | + '10': 'VERIFICATION_ERROR', |
| 59 | + '11': 'UPLOAD_VERIFICATION_ERROR', |
| 60 | + '12': 'UPLOAD_WARNING', |
| 61 | + '13': 'INTERNAL_ERROR', |
| 62 | + '14': 'MIN_LENGTH_PARTNAME' |
| 63 | + }, |
| 64 | + |
| 65 | + |
| 66 | + /** |
| 67 | + * Process the result of an action=upload API request, into a useful |
| 68 | + * data structure. |
| 69 | + * upload, |
| 70 | + * errors, |
| 71 | + * warnings |
| 72 | + * Augment with error messages in the local language if possible |
| 73 | + * |
| 74 | + */ |
| 75 | + processResult: function( result ) { |
| 76 | + var _this = this; |
| 77 | + mw.log( 'processResult::' ); |
| 78 | + |
| 79 | + // debugger; |
| 80 | + |
| 81 | + var parsedResult = _this.parseResult(result); |
| 82 | + |
| 83 | + if ( _this.doneCb && typeof _this.doneCb == 'function' ) { |
| 84 | + mw.log( "call doneCb" ); |
| 85 | + _this.doneCb( parsedResult ); |
| 86 | + |
| 87 | + } |
| 88 | + return true; |
| 89 | + }, |
| 90 | + |
| 91 | + |
| 92 | + parseResult: function( result ) { |
| 93 | + if ( result.upload && result.upload.imageinfo && result.upload.imageinfo.descriptionurl ) { |
| 94 | + result.isSuccess = true; |
| 95 | + } |
| 96 | + |
| 97 | + return result; |
| 98 | + |
| 99 | + } |
| 100 | + |
| 101 | + |
| 102 | +}; |
| 103 | + |
| 104 | + |
| 105 | + if ( result.error || ( result.upload && result.upload.result == "Failure" ) ) { |
| 106 | + |
| 107 | + // Check a few places for the error code |
| 108 | + var error_code = 0; |
| 109 | + var errorReplaceArg = ''; |
| 110 | + if ( result.error && result.error.code ) { |
| 111 | + error_code = result.error.code; |
| 112 | + } else if ( result.upload.code ) { |
| 113 | + if ( typeof result.upload.code == 'object' ) { |
| 114 | + if ( result.upload.code[0] ) { |
| 115 | + error_code = result.upload.code[0]; |
| 116 | + } |
| 117 | + if ( result.upload.code['status'] ) { |
| 118 | + error_code = result.upload.code['status']; |
| 119 | + if ( result.upload.code['filtered'] ) { |
| 120 | + errorReplaceArg = result.upload.code['filtered']; |
| 121 | + } |
| 122 | + } |
| 123 | + } else { |
| 124 | + result.upload.code; // XXX ?? |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + var error_msg = ''; |
| 129 | + if ( typeof result.error == 'string' ) { |
| 130 | + error_msg = result.error; |
| 131 | + } |
| 132 | + |
| 133 | + if ( !error_code || error_code == 'unknown-error' ) { |
| 134 | + if ( typeof JSON != 'undefined' ) { |
| 135 | + mw.log( 'Error: result: ' + JSON.stringify( result ) ); |
| 136 | + } |
| 137 | + if ( result.upload.error == 'internal-error' ) { |
| 138 | + // Do a remote message load |
| 139 | + errorKey = result.upload.details[0]; |
| 140 | + |
| 141 | + mw.getRemoteMsg( errorKey, function() { |
| 142 | + _this.ui.setPrompt( gM( 'mwe-uploaderror' ), gM( errorKey ), buttons ); |
| 143 | + |
| 144 | + }); |
| 145 | + return false; |
| 146 | + } |
| 147 | + |
| 148 | + _this.ui.setPrompt( |
| 149 | + gM('mwe-uploaderror'), |
| 150 | + gM('mwe-unknown-error') + '<br>' + error_msg, |
| 151 | + buttons ); |
| 152 | + return false; |
| 153 | + } |
| 154 | + |
| 155 | + if ( result.error && result.error.info ) { |
| 156 | + _this.ui.setPrompt( gM( 'mwe-uploaderror' ), result.error.info, buttons ); |
| 157 | + return false; |
| 158 | + } |
| 159 | + |
| 160 | + if ( typeof error_code == 'number' |
| 161 | + && typeof _this.error_msg_key[error_code] == 'undefined' ) |
| 162 | + { |
| 163 | + if ( result.upload.code.finalExt ) { |
| 164 | + _this.ui.setPrompt( |
| 165 | + gM( 'mwe-uploaderror' ), |
| 166 | + gM( 'mwe-wgfogg_warning_bad_extension', result.upload.code.finalExt ), |
| 167 | + buttons ); |
| 168 | + } else { |
| 169 | + _this.ui.setPrompt( |
| 170 | + gM( 'mwe-uploaderror' ), |
| 171 | + gM( 'mwe-unknown-error' ) + ' : ' + error_code, |
| 172 | + buttons ); |
| 173 | + } |
| 174 | + return false; |
| 175 | + } |
| 176 | + |
| 177 | + mw.log( 'get key: ' + _this.error_msg_key[ error_code ] ) |
| 178 | + mw.getRemoteMsg( _this.error_msg_key[ error_code ], function() { |
| 179 | + _this.ui.setPrompt( |
| 180 | + gM( 'mwe-uploaderror' ), |
| 181 | + gM( _this.error_msg_key[ error_code ], errorReplaceArg ), |
| 182 | + buttons ); |
| 183 | + }); |
| 184 | + mw.log( "api.error" ); |
| 185 | + return false; |
| 186 | + } |
| 187 | + |
| 188 | + |
| 189 | + } |
| 190 | + */ |
| 191 | + |
| 192 | +/* |
| 193 | + // this doesn't seem to do anything |
| 194 | + // Check for warnings: |
| 195 | + if ( result.upload && result.upload.warnings ) { |
| 196 | + for ( var wtype in result.upload.warnings ) { |
| 197 | + var winfo = result.upload.warnings[wtype] |
| 198 | + switch ( wtype ) { |
| 199 | + case 'duplicate': |
| 200 | + case 'exists': |
| 201 | + if ( winfo[1] && winfo[1].title && winfo[1].title.mTextform ) { |
| 202 | + push warnings, { type: wtype, text: winfo[1].title.mTextform }, |
| 203 | + } else { |
| 204 | + push warnings, { type: wtype } |
| 205 | + } |
| 206 | + break; |
| 207 | + case 'file-thumbnail-no': |
| 208 | + push warnings, { type: wtype, info: winfo } |
| 209 | + break; |
| 210 | + default: |
| 211 | + push warnings: { type: wtype, } |
| 212 | + break; |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + if ( result.upload.sessionkey ) { |
| 217 | + _this.warnings_sessionkey = result.upload.sessionkey; |
| 218 | + } |
| 219 | + |
| 220 | + } |
| 221 | +*/ |
| 222 | +/* |
| 223 | + // Check upload.error |
| 224 | + if ( result.upload && result.upload.error ) { |
| 225 | + mw.log( ' result.upload.error: ' + result.upload.error ); |
| 226 | + _this.ui.setPrompt( |
| 227 | + gM( 'mwe-uploaderror' ), |
| 228 | + gM( 'mwe-unknown-error' ) + '<br>', |
| 229 | + buttons ); |
| 230 | + return false; |
| 231 | + } |
| 232 | +*/ |
| 233 | + |
| 234 | + /* |
| 235 | + // this ONLY applies to copy by URL method -- factor out? |
| 236 | + if ( result.upload && result.upload.upload_session_key ) { |
| 237 | + // Async upload, do AJAX status polling |
| 238 | + _this.upload_session_key = result.upload.upload_session_key; |
| 239 | + _this.doAjaxUploadStatus(); |
| 240 | + mw.log( "set upload_session_key: " + _this.upload_session_key ); |
| 241 | + return; |
| 242 | + } |
| 243 | + */ |
| 244 | + |
| 245 | + /* |
| 246 | + var buttons = {}; |
| 247 | + // "Return" button |
| 248 | + buttons[ gM( 'mwe-return-to-form' ) ] = function() { |
| 249 | + $j( this ).dialog( 'destroy' ).remove(); |
| 250 | + _this.form_post_override = false; |
| 251 | + } |
| 252 | + // "Go to resource" button |
| 253 | + buttons[ gM('mwe-go-to-resource') ] = function() { |
| 254 | + window.location = url; |
| 255 | + }; |
| 256 | + _this.action_done = true; |
| 257 | + _this.interface.setPrompt( |
| 258 | + gM( 'mwe-successfulupload' ), |
| 259 | + gM( 'mwe-upload_done', url), |
| 260 | + buttons ); |
| 261 | + mw.log( 'result.upload.imageinfo::' + url ); |
| 262 | + return true; |
| 263 | + */ |
| 264 | + |
| 265 | +/* |
| 266 | + // Create the "ignore warning" button |
| 267 | + var buttons = {}; |
| 268 | + buttons[ gM( 'mwe-ignorewarning' ) ] = function() { |
| 269 | + // Check if we have a stashed key: |
| 270 | + if ( _this.warnings_sessionkey ) { |
| 271 | + //set to "loading" |
| 272 | + $j( '#upProgressDialog' ).html( mw.loading_spinner() ); |
| 273 | + //setup request: |
| 274 | + var request = { |
| 275 | + 'action': 'upload', |
| 276 | + 'sessionkey': _this.warnings_sessionkey, |
| 277 | + 'ignorewarnings': 1, |
| 278 | + 'filename': $j( '#wpDestFile' ).val(), |
| 279 | + 'token' : _this.editToken, |
| 280 | + 'comment' : _this.getUploadDescription() |
| 281 | + }; |
| 282 | + //run the upload from stash request |
| 283 | + mw.getJSON(_this.api_url, request, function( data ) { |
| 284 | + _this.processApiResult( data ); |
| 285 | + } ); |
| 286 | + } else { |
| 287 | + mw.log( 'No session key re-sending upload' ) |
| 288 | + |
| 289 | + |
| 290 | + //do a stashed upload |
| 291 | + $j( '#wpIgnoreWarning' ).attr( 'checked', true ); |
| 292 | + $j( _this.editForm ).submit(); |
| 293 | + } |
| 294 | + }; |
| 295 | + */ |
| 296 | + |
| 297 | + |
Property changes on: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.UploadApiProcessor.js |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 298 | + native |
Index: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/README |
— | — | @@ -0,0 +1,66 @@ |
| 2 | +What this is |
| 3 | +============ |
| 4 | + |
| 5 | +It's the first stab at a more usable multimedia uploader, particularly for Wikimedia Commons. |
| 6 | + |
| 7 | +Main features: |
| 8 | + - Multiple simultaneous file uploads |
| 9 | + - Batch assertion of license ("all these files were CC-BY-SA") |
| 10 | + - "Incomplete" uploads, where the data can be uploaded and metadata edited without really adding the file to the collection. |
| 11 | + - (Eventually) Gee-whiz technology like HTML5 drag & drop from the desktop |
| 12 | + |
| 13 | +http://usability.wikimedia.org/wiki/Multimedia:NewUpload |
| 14 | + |
| 15 | + |
| 16 | +General disclaimer |
| 17 | +=================== |
| 18 | + |
| 19 | +For the moment, (March 2010) UploadWizard is all alpha quality code. |
| 20 | +It often does not conform to the MediaWiki coding standards. This will have to be fixed before it gets merged into trunk. |
| 21 | + |
| 22 | +It uses MwEmbed because it was found to be very difficult to separate it from the framework. |
| 23 | +Furthermore, it is somewhat redundant with the AddMedia module. AddMedia has a lot of useful code but |
| 24 | +not factored in the way that would be most convenient for this module. Michael Dale (mdale) and myself (NeilK) |
| 25 | +are attempting to refactor things and meet in the middle. |
| 26 | + |
| 27 | +Presently (March 2010) we are sprinting to get a hacky interface up to conduct a usability test on our |
| 28 | +basic ideas about updating media uploads for commons. Nothing here is expected to stay here permanently. |
| 29 | + |
| 30 | +Stay tuned, or, talk to NeilK about this, if anything here bothers you. |
| 31 | + |
| 32 | + |
| 33 | +How to use it |
| 34 | +============= |
| 35 | + |
| 36 | +This is *very* complicated at present. Obviously we want to make this simpler in the future. |
| 37 | + |
| 38 | +Currently mdale tests with the live Commons site by loading in code from a JS2-enabled prototype. |
| 39 | +This is accomplished with a gadget on Commons (or other additions to the skin). |
| 40 | + |
| 41 | +Here's how you might do such a thing: |
| 42 | + |
| 43 | +- Set up an instance of MediaWiki from trunk; or, if feeling brave[1], use a real site like Commons itself. |
| 44 | +- Set up an instance of MediaWiki from js-work branch, which your browser can access. |
| 45 | +- On this trunk-based Mediawiki, install a gadget which is invoked on all pages to load some code from the JS2-enabled site. Since |
| 46 | + this is all accomplished in your browser, you could use a local hostname. (See below). |
| 47 | +- Point your browser to the upload page on the trunk-based mediawiki, with a special parameter: Special:Upload?uploadWizard=1 . |
| 48 | + |
| 49 | + |
| 50 | +[1] Currently I (NeilK) use a local MediaWiki, since we are uploading stuff *without* the normal checks |
| 51 | + that the Commons people have carefully added. |
| 52 | + |
| 53 | +[2] The gadget might look like this: |
| 54 | + |
| 55 | +----- cut here ---- |
| 56 | +// Once mwEmbed is "on" we can use mw.setConfig calls: |
| 57 | +if( typeof mwAddMediaConfig == 'undefined'){ |
| 58 | +mwAddMediaConfig = {}; |
| 59 | +} |
| 60 | +mwAddMediaConfig['enabled_providers'] = [ 'wiki_commons', 'upload' ] |
| 61 | + |
| 62 | +importScriptURI('http://js2-work.yoursite.local/js/mwEmbed/remotes/mediaWiki.js?&uselang=en&debug=true' ); |
| 63 | +----- cut here ---- |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | + |
Index: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.IframeTransport.js |
— | — | @@ -0,0 +1,96 @@ |
| 2 | +mw.IframeTransport = function(form, progressCb, completedCb) { |
| 3 | + var _this = this; |
| 4 | + |
| 5 | + _this.form = form; |
| 6 | + _this.progressCb = progressCb; |
| 7 | + _this.completedCb = completedCb; |
| 8 | + |
| 9 | + _this.iframeId = 'f_' + ( $j( 'iframe' ).length + 1 ); |
| 10 | + |
| 11 | + //IE only works if you "create element with the name" (not jquery style) |
| 12 | + var iframe; |
| 13 | + try { |
| 14 | + iframe = document.createElement( '<iframe name="' + _this.iframeId + '">' ); |
| 15 | + } catch (ex) { |
| 16 | + iframe = document.createElement('iframe'); |
| 17 | + } |
| 18 | + |
| 19 | + // we configure form on load, because the first time it loads, it's blank |
| 20 | + // then we configure it to deal with an API submission |
| 21 | + $j( iframe ) |
| 22 | + .attr({ 'src' : 'javascript:false;', |
| 23 | + 'id' : _this.iframeId, |
| 24 | + 'name' : _this.iframeId }) |
| 25 | + .load(function() { _this.configureForm() }) |
| 26 | + .css('display', 'none'); |
| 27 | + |
| 28 | + $j( "body" ).append( iframe ); |
| 29 | +}; |
| 30 | + |
| 31 | +mw.IframeTransport.prototype = { |
| 32 | + configureForm: function() { |
| 33 | + console.log("configuring form for iframe transport"); |
| 34 | + var _this = this; |
| 35 | + // Set the form target to the iframe |
| 36 | + var $jForm = $j(_this.form); |
| 37 | + $jForm.attr( 'target', _this.iframeId ); |
| 38 | + |
| 39 | + // attach an additional handler to the form, so, when submitted, it starts showing the progress |
| 40 | + // XXX this is lame .. there should be a generic way to indicate busy status... |
| 41 | + $jForm.submit( function() { |
| 42 | + console.log("submitting to iframe..."); |
| 43 | + _this.progressCb(1.0); |
| 44 | + return true; |
| 45 | + } ); |
| 46 | + |
| 47 | + // Set up the completion callback |
| 48 | + $j( '#' + _this.iframeId ).load( function() { |
| 49 | + console.log("received result in iframe"); |
| 50 | + _this.processIframeResult( $j( this ).get( 0 ) ); |
| 51 | + }); |
| 52 | + }, |
| 53 | + |
| 54 | + /** |
| 55 | + * Process the result of the form submission, returned to an iframe. |
| 56 | + * This is the iframe's onload event. |
| 57 | + * |
| 58 | + * @param {Element} iframe iframe to extract result from |
| 59 | + */ |
| 60 | + processIframeResult: function( iframe ) { |
| 61 | + var _this = this; |
| 62 | + var doc = iframe.contentDocument ? iframe.contentDocument : frames[iframe.id].document; |
| 63 | + // Fix for Opera 9.26 |
| 64 | + if ( doc.readyState && doc.readyState != 'complete' ) { |
| 65 | + console.log("not complete"); |
| 66 | + return; |
| 67 | + } |
| 68 | + |
| 69 | + // Fix for Opera 9.64 |
| 70 | + if ( doc.body && doc.body.innerHTML == "false" ) { |
| 71 | + console.log("no innerhtml"); |
| 72 | + return; |
| 73 | + } |
| 74 | + var response; |
| 75 | + if ( doc.XMLDocument ) { |
| 76 | + // The response is a document property in IE |
| 77 | + response = doc.XMLDocument; |
| 78 | + } else if ( doc.body ) { |
| 79 | + // Get the json string |
| 80 | + // XXX wait... why are we grepping it out of an HTML doc? We requested jsonfm, why? |
| 81 | + json = $j( doc.body ).find( 'pre' ).text(); |
| 82 | + mw.log( 'iframe:json::' + json) |
| 83 | + if ( json ) { |
| 84 | + response = window["eval"]( "(" + json + ")" ); |
| 85 | + } else { |
| 86 | + response = {}; |
| 87 | + } |
| 88 | + } else { |
| 89 | + // Response is a xml document |
| 90 | + response = doc; |
| 91 | + } |
| 92 | + // Process the API result |
| 93 | + _this.completedCb( response ); |
| 94 | + } |
| 95 | +}; |
| 96 | + |
| 97 | + |
Property changes on: branches/js2-work/phase3/js/mwEmbed/modules/UploadWizard/mw.IframeTransport.js |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 98 | + native |
Index: branches/js2-work/phase3/js/mwEmbed/remotes/mediaWiki.js |
— | — | @@ -5,7 +5,6 @@ |
6 | 6 | var urlparts = getRemoteEmbedPath(); |
7 | 7 | var mwEmbedHostPath = urlparts[0]; |
8 | 8 | var mwRemoteVersion = 'r104'; |
9 | | -var mwUseScriptLoader = true; |
10 | 9 | |
11 | 10 | // Log the mwRemote version ( will determine what version of js we get ) |
12 | 11 | if( window.console ){ |
— | — | @@ -17,10 +16,13 @@ |
18 | 17 | var mwReqParam = { }; |
19 | 18 | for ( var i = 0; i < reqParts.length; i++ ) { |
20 | 19 | var p = reqParts[i].split( '=' ); |
21 | | - if ( p.length == 2 ) |
| 20 | + if ( p.length == 2 ) { |
22 | 21 | mwReqParam[ p[0] ] = p[1]; |
| 22 | + } |
23 | 23 | } |
24 | 24 | |
| 25 | +var mwUseScriptLoader = (mwReqParam['debug'] != 'true'); |
| 26 | + |
25 | 27 | // Use wikibits onLoad hook: ( since we don't have js2 / mw object loaded ) |
26 | 28 | addOnloadHook( function() { |
27 | 29 | doPageSpecificRewrite(); |
— | — | @@ -96,11 +98,15 @@ |
97 | 99 | |
98 | 100 | // Upload page -> Firefogg / upload API / uploadWizard integration |
99 | 101 | if ( wgPageName == "Special:Upload" ) { |
100 | | - var scriptName = 'uploadPage.js'; |
101 | | - if ( location.search.indexOf('uploadWizard=1') != -1 ) { |
102 | | - scriptName = 'uploadWizard.js'; |
103 | | - } |
104 | | - loadMwEmbed( [ |
| 102 | + var scriptUrl = null; |
| 103 | + var scriptName = null; |
| 104 | + var libraries = []; |
| 105 | + if ( location.search.indexOf('uploadWizard=1') !== -1 ) { |
| 106 | + scriptName = 'uploadWizardPage.js'; |
| 107 | + libraries = []; |
| 108 | + } else { |
| 109 | + scriptName = 'uploadPage.js'; |
| 110 | + libraries = [ |
105 | 111 | 'mw.UploadHandler', |
106 | 112 | 'mw.UploadInterface', |
107 | 113 | 'mw.Firefogg', |
— | — | @@ -108,10 +114,10 @@ |
109 | 115 | '$j.ui.progressbar', |
110 | 116 | '$j.ui.dialog', |
111 | 117 | '$j.ui.draggable' |
112 | | - ], function() { |
113 | | - mw.load( mwEmbedHostPath + '/' + scriptName + '?' + mwGetReqArgs() ); |
114 | | - } |
115 | | - ); |
| 118 | + ]; |
| 119 | + } |
| 120 | + var scriptUrl = mwEmbedHostPath + '/' + scriptName + '?' + mwGetReqArgs() |
| 121 | + loadMwEmbed(libraries, function() { mw.load( scriptUrl ) } ); |
116 | 122 | } |
117 | 123 | |
118 | 124 | // Special api proxy page |
Index: branches/js2-work/phase3/js/uploadWizardPage.js |
— | — | @@ -0,0 +1,40 @@ |
| 2 | +/* |
| 3 | + * This script is run on [[Special:Upload]]. |
| 4 | + * Creates an interface for uploading files in multiple steps, hence "wizard" |
| 5 | + */ |
| 6 | + |
| 7 | + |
| 8 | +mw.ready( function() { |
| 9 | + |
| 10 | + // steal the edit token from the existing page |
| 11 | + var token = $j( "#wpEditToken" ).val(); |
| 12 | + |
| 13 | + var licensingDiv = $j('<div id="upload-licensing" class="upload-section">Licensing tutorial</div>').get(0); |
| 14 | + var wizardDiv = $j('<div id="upload-wizard" class="upload-section"></div>').get(0); |
| 15 | + $j('#bodyContent').empty().append(licensingDiv).append(wizardDiv); |
| 16 | + |
| 17 | + // change title on page too -- this doesn't have to be internationalized yet since this is just for testing |
| 18 | + // when it has its own page it will have a real title |
| 19 | + $j('#firstHeading').html("Upload wizard"); |
| 20 | + |
| 21 | + mw.load( 'UploadWizard.UploadWizardTest', function () { |
| 22 | + |
| 23 | + mw.setDefaultConfig('uploadHandlerClass', null); |
| 24 | + mw.setConfig('userName', wgUserName); |
| 25 | + mw.setConfig('userLanguage', wgUserLanguage); |
| 26 | + mw.setConfig('fileExtensions', wgFileExtensions); |
| 27 | + mw.setConfig('token', token); |
| 28 | + mw.setConfig('languages', [ |
| 29 | + { code: 'en', name: 'English' }, |
| 30 | + { code: 'fr', name: 'Français' }, |
| 31 | + { code: 'es', name: 'Espagnol' }, |
| 32 | + ]); |
| 33 | + mw.setConfig('thumbnailWidth', 220); // new standard size |
| 34 | + |
| 35 | + var uploadWizard = new mw.UploadWizard(); |
| 36 | + uploadWizard.createInterface(wizardDiv); |
| 37 | + |
| 38 | + }); |
| 39 | + |
| 40 | + |
| 41 | +} ); |
Property changes on: branches/js2-work/phase3/js/uploadWizardPage.js |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 42 | + native |