Guest User

ia_uploader.js

a guest
Jul 9th, 2022
42
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.  
  2. var IA_SOLR = (function ($) {
  3.     var ia_solr = {};
  4.  
  5.     ia_solr.baseURL = '/advancedsearch.php';
  6.  
  7.     ia_solr.getQuery = function(query, fields, rows, page, sortFields) {
  8.  
  9.         // Set some defaults
  10.         fields = fields || ['identifier'];
  11.         rows = rows || 50;
  12.         page = page || 1;
  13.  
  14.         var params = { q: query, output: 'json', save: 'yes',
  15.                        fl: fields,
  16.                        rows: rows,
  17.                        page: page
  18.         };
  19.         if (sortFields) {
  20.             params.sort = sortFields;
  21.         }
  22.  
  23.         return ia_solr.baseURL + '?' + $.param(params);
  24.     };
  25.  
  26.     ia_solr.getUserUploadQuery = function(uploader, rows, page, sortFields) {
  27.         var fields = ['identifier', 'avg_rating', 'collection', 'date',
  28.                       'downloads', 'licenseurl', 'subject', 'title', 'description', 'language',
  29.                       // $$$ these won't actually come back - 'updatedate', 'updater',
  30.                       'publicdate'
  31.                        ];
  32.         return ia_solr.getQuery('uploader:' + uploader, fields, rows, page, sortFields);
  33.     };
  34.  
  35.     return ia_solr;
  36.  
  37. }(jQuery));
  38.  
  39. var IA_UPLOADER = (function ($) {
  40.  
  41.     var ia_uploader = {};
  42.  
  43.     // $$$ update to production before deployment
  44.     ia_uploader.apiEndpoint = '/upload/app/upload_api.php';
  45.     ia_uploader.s3_base_url = 'https://s3.us.archive.org/';
  46.     ia_uploader.s3_access_key = null;
  47.     ia_uploader.s3_secret_key = null;
  48.     ia_uploader.mode = 'upload'; //'upload' to new item or 'add' files to existing item
  49.     ia_uploader.version = '';
  50.     ia_uploader.is_admin = 0;
  51.     ia_uploader.max_id_length = 0;
  52.  
  53.     ia_uploader.meta_index = {};
  54.     ia_uploader.identifier = null;
  55.     ia_uploader.suggested_identifier = null;
  56.     ia_uploader.primary_collection = null;
  57.     ia_uploader.files_array = [];
  58.     ia_uploader.license_url = "";
  59.  
  60.     ia_uploader.xhr = new XMLHttpRequest();
  61.  
  62.     ia_uploader.current_file = 0;
  63.     ia_uploader.total_bytes = 0.0;
  64.     ia_uploader.total_sent = 0.0;
  65.     ia_uploader.num_entries_queued = 0;
  66.     ia_uploader.num_entries_processed = 0;
  67.     ia_uploader.treetable_id_counter = 0;
  68.     ia_uploader.largest_file = {size:0, index:0};
  69.     ia_uploader.upload_start_time = null;
  70.     ia_uploader.RETURN_KEYCODE = 13;
  71.     ia_uploader.CREATIVE_COMMONS_LICENSE_VERSION = '4.0';
  72.  
  73.     /* Old implementation
  74.     ia_uploader.Uploader = function(targetElement) {
  75.         this._jTargetElement = $(targetElement);
  76.  
  77.         this.init = function() {
  78.             // Template elements are cloned to create new elements
  79.             this._itemTemplate = $('.item.template').clone().removeClass('template');
  80.             this._inProgressTemplate = $('.inProgressRow.template').clone().removeClass('template');
  81.         };
  82.  
  83.         // ---- Item rows ----
  84.         this.setItemValues = function(jItemElement, params) {
  85.             // $$$ # files not yet implemented
  86.             //console.log(params);
  87.             jItemElement.find('.itemName a').attr('href', '/details/' + params.identifier).html(params.title);
  88.             jItemElement.find('.license').html(params.licenseurl);
  89.             jItemElement.find('.language').html(this.formatLanguage(params.language));
  90.             jItemElement.find('.ratings').html(this.formatRating(params.avg_rating));
  91.             jItemElement.find('.views').html(params.downloads);
  92.             jItemElement.find('.description').html(params.description);
  93.             jItemElement.find('.keywords').html(this.formatSubject(params.subject));
  94.  
  95.             // $$$ update time not yet implemented
  96.             jItemElement.find('.updateTime').html(this.formatDate(params.publicdate)); // $$$ get updatedate to come back from solr
  97.             return jItemElement;
  98.         };
  99.  
  100.         this.newItem = function(params) {
  101.             return this.setItemValues(this._itemTemplate.clone(), params);
  102.         };
  103.  
  104.         this.addItem = function(params) {
  105.             this.newItem(params).appendTo(this._jTargetElement.find('.uploadsTable')).show();
  106.         };
  107.  
  108.  
  109.         // ---- Uploads table ----
  110.         this.populateUploadsTable = function(query) {
  111.  
  112.             var self = this;
  113.             $.ajax(
  114.                 // $$$ maxing out at 500 items - pageinate
  115.                 { url: IA_SOLR.getUserUploadQuery(query, 500, 1, ['publicdate desc']), type: 'jsonp',
  116.                   success: function(data)
  117.                     {
  118.                         if (data.responseHeader.status == 0) {
  119.                             for (var i = 0; i < data.response.docs.length; i++) {
  120.                                 self.addItem(data.response.docs[i]);
  121.                             }
  122.                         } else {
  123.                             // XXX show error
  124.                         }
  125.                     }
  126.                 }
  127.             );
  128.         };
  129.  
  130.  
  131.         // ---- Data formatting ----
  132.         this.formatRating = function(rating) {
  133.             switch (rating) {
  134.                 case 1:
  135.                     return '*';
  136.                 case 2:
  137.                     return '**';
  138.                 case 3:
  139.                     return '***';
  140.                 case 4:
  141.                     return '****';
  142.                 case 5:
  143.                     return '*****';
  144.             }
  145.  
  146.             return '';
  147.         };
  148.  
  149.         this.formatLanguage = function(language) {
  150.             if (!language) {
  151.                 return '';
  152.             }
  153.  
  154.             return "In " + language.join(', ');
  155.         };
  156.  
  157.         this.formatSubject = function(subject) {
  158.             if (!subject) {
  159.                 return '';
  160.             }
  161.  
  162.             return subject.join(', ');
  163.         };
  164.  
  165.         this.formatDate = function(dateStr) {
  166.             return prettyDate(dateStr) || dateStr;
  167.         };
  168.     };
  169.     */
  170.  
  171.     ia_uploader.capitalize = function(word) {
  172.         return word.substr(0,1).toUpperCase() + word.substring(1);
  173.     };
  174.  
  175.     /* Given a filename convert it to an item title. Try to break at word boundaries and otherwise clean up. */
  176.     ia_uploader.filenameToTitle = function(filename) {
  177.         // $$$ look for common camera filenames, e.g. IMG_00122.jpg
  178.         //     and return e.g. "Mang's Photo 122" or "Mang's Movie 1281"
  179.  
  180.         var extension = new RegExp('\\.[a-zA-Z][a-zA-Z0-9]*$');
  181.         filename = filename.replace(extension, '');
  182.  
  183.         // Return single lowercase words (e.g. file names) unparsed -- per Cleanup Week request
  184.  
  185.         if ((filename.split(" ").length == 1) && (/^[a-z]/.test(filename))) {
  186.             return filename;
  187.         }
  188.  
  189.         // Things that precede or start a word (delimiters)
  190.         // - whitespace (including beginning of line)
  191.         // - capital(s)
  192.         // - dash or underscore
  193.         // - numeral(s)
  194.         //
  195.         // The basic strategy is to look for obvious breaks between words then see if any
  196.         // of those matches should be split down further
  197.  
  198.         var wordBreak = new RegExp('[^ _-]+', 'g');
  199.         var wordStart = new RegExp('([A-Z]+|[0-9][0-9\\.]*)', 'g');
  200.         var containsAlphaNumeric = new RegExp('\\w');
  201.  
  202.         var words = [];
  203.  
  204.         var compoundMatch;
  205.         while (compoundMatch = wordBreak.exec(filename)) {
  206.             var compoundWord = compoundMatch[0];
  207.  
  208.             /* Break up compound word */
  209.             var breakMatch;
  210.             var startIndex = 0;
  211.             while (breakMatch = wordStart.exec(compoundWord)) {
  212.                 if (breakMatch.index == 0) {
  213.                     continue;
  214.                 }
  215.  
  216.                 // Check we have any "normal" characters, if not combine with next match
  217.                 if (containsAlphaNumeric.test(breakMatch[0])) {
  218.                     // Capitalize and add to list
  219.                     words.push(compoundWord.substr(startIndex, 1).toUpperCase() + compoundWord.substring(startIndex + 1, breakMatch.index));
  220.                     startIndex = breakMatch.index;
  221.                 }
  222.             }
  223.  
  224.             /* Last or only word */
  225.             words.push(compoundWord.substr(startIndex, 1).toUpperCase() + compoundWord.substring(startIndex + 1, compoundWord.length));
  226.         }
  227.  
  228.         return words.join(' ');
  229.     };
  230.  
  231.     /* Now handled on server side
  232.     ia_uploader.titleToIdentifier = function(title) {
  233.         var wordPattern = new RegExp('[A-Za-z0-9]+','g');
  234.         var words = [];
  235.         while (wordMatch = wordPattern.exec(title)) {
  236.             words.push(ia_uploader.capitalize(wordMatch[0]));
  237.         }
  238.         return words.join('');
  239.     }
  240.     */
  241.  
  242.     ia_uploader.getItemStatus = function(identifier, callback) {
  243.         // XXX proper url encoding
  244.         var statusUrl = '/catalog_status.php?identifier=' + identifier;
  245.         //var statusUrl = "status.xml"; // testing
  246.         var theCallback = callback; // closure
  247.         $.ajax({
  248.             type: "GET",
  249.             url: statusUrl,
  250.             dataType: "xml",
  251.             success: function(xml) {
  252.                 // console.log(xml);
  253.                 var queued = $(xml).find('wait_admin0').text(); // Green
  254.                 var running = $(xml).find('wait_admin1').text(); // Blue
  255.                 var stuck = $(xml).find('wait_admin2').text(); // Red
  256.                 var skipped = $(xml).find('wait_admin9').text(); // Brown
  257.                 // console.log(queued + ' - ' + running + ' - ' + stuck + ' - ' + skipped);
  258.                 theCallback({identifier: identifier,
  259.                              queued: parseInt(queued) || 0, running: parseInt(running) || 0,
  260.                              stuck: parseInt(stuck) || 0, skipped: parseInt(skipped) || 0});
  261.             }
  262.             // $$$ failure handling
  263.         });
  264.     };
  265.  
  266.     ia_uploader.identifierAvailable = function(identifier, findUnique, successCallback, errorCallback) {
  267.         $.ajax({
  268.             type: "POST",
  269.             url: this.apiEndpoint,
  270.             data: {'name':'identifierAvailable', 'identifier':identifier, 'findUnique': findUnique },
  271.             //data: {'name':'returnStatus', 'status': 504 }, // For testing
  272.             dataType: "jsonp",
  273.             success: successCallback,
  274.             error: errorCallback
  275.         });
  276.     }
  277.  
  278.  
  279.     ia_uploader.countUpload = function(fileCount, totalBytes) {
  280.         $.ajax({
  281.             type: "POST",
  282.             url: this.apiEndpoint,
  283.             data: { name: 'countUpload', fileCount: fileCount, totalBytes: totalBytes },
  284.             dataType: "jsonp"
  285.         });
  286.  
  287.         var upload_end_time = new Date().getTime();
  288.         var upload_time = Math.round((upload_end_time-this.upload_start_time)/1000.0);
  289.  
  290.         var values = {
  291.             'uploader': 1,
  292.             'success': 1,
  293.             'id': this.identifier,
  294.             'bytes': totalBytes,
  295.             'files': fileCount,
  296.             'seconds': upload_time,
  297.             'referrer': 'https://archive.org/upload'
  298.         };
  299.         if (typeof(archive_analytics) != 'undefined') {
  300.             archive_analytics.send_ping(values);
  301.         }
  302.     }
  303.  
  304.  
  305.     ia_uploader.countError = function(httpStatus) {
  306.         $.ajax({
  307.             type: "POST",
  308.             url: this.apiEndpoint,
  309.             data: { name: 'countError', httpStatus: httpStatus },
  310.             dataType: "jsonp"
  311.         });
  312.     }
  313.  
  314.     // Replace form elements with their values
  315.     ia_uploader.replaceFormElementsWithValue = function(elements) {
  316.         $.each(elements, function(index, elem) {
  317.             var replacement;
  318.  
  319.             if ($(elem).is('input')) {
  320.                 if ($(elem).hasClass('ui-autocomplete-input') || $(elem).is('[type=submit]')) {
  321.                      return true;
  322.                 }
  323.  
  324.                 if ($(elem).is('input[type=checkbox]')) {
  325.                     $(elem).attr('disabled', 'disabled');
  326.                 } else {
  327.                     replacement = $('<span>' + $(elem).val() + '</span>');
  328.                     $(elem).after(replacement).hide();
  329.                 }
  330.  
  331.             } else if ($(elem).is('select')) {
  332.                 if ($(elem).data('combobox')) {
  333.                     $(elem).combobox('destroy');
  334.                 }
  335.                 replacement = $('<span>' + $(elem).find('option:selected').text() + '</span>');
  336.                 $(elem).after(replacement).hide();
  337.             } else if ($(elem).is('textarea')) {
  338.                 if ($(elem).data('wysiwyg')) {
  339.                     $(elem).wysiwyg('save').wysiwyg('destroy');
  340.                 }
  341.                 replacement = $('<span>' + $(elem).val() + '</span>');
  342.                 $(elem).after(replacement).hide();
  343.             } else {
  344.                 if (console && console.log) {
  345.                     console.log("Don't know how to replace element with value");
  346.                     console.log(elem);
  347.                 }
  348.             }
  349.  
  350.             return true;
  351.         });
  352.     }
  353.  
  354.     // Return bytes formatted as string.  Include a single decimal if it will be > 1% of the value.
  355.     // e.g. 1.2KB or 11KB
  356.     ia_uploader.formatFileSize = function(bytes) {
  357.         var val = bytes;
  358.         var units = ['KB', 'MB', 'GB', 'TB', 'PB'];
  359.  
  360.         if (bytes < 1024) {
  361.             return $.sprintf("%d bytes", val);
  362.         }
  363.  
  364.         for (var i = 0; i < units.length; i++) {
  365.             val /= 1024;
  366.             if (val < 1024) {
  367.                 if (val < 10) {
  368.                     return $.sprintf("%.1f %s", val, units[i]);
  369.                 } else {
  370.                     return $.sprintf("%d %s", val, units[i]);
  371.                 }
  372.             }
  373.         }
  374.  
  375.         // Reached end - BIG DATA
  376.         return $.sprintf("%d %s", val, units[units.length - 1]);
  377.     }
  378.  
  379.     // Return an IA mediatype guessed from mimeType
  380.     // $$$ should also pass in the file name, e.g. foo_images.zip should be "texts"
  381.     ia_uploader.mediaTypeForMimeType = function(file) {
  382.         var mimeType = file.type;
  383.  
  384.         // Ref http://en.wikipedia.org/wiki/Internet_media_type
  385.  
  386.         // Specific mime types - exact match is preferred over regexs below
  387.         switch (mimeType) {
  388.             case 'application/pdf':
  389.             case 'application/epub+zip':
  390.             case 'application/postscript':
  391.             case 'application/msword':
  392.             case 'application/vnd.oasis.opendocument.text':
  393.             case 'application/vnd.oasis.opendocument.presentation':
  394.             case 'application/vnd.ms-powerpoint':
  395.             case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
  396.             case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
  397.                 return 'texts';
  398.  
  399.             case 'application/ogg':
  400.                 return 'video';
  401.  
  402.             case '':
  403.                 if (file.name.match(/\.cdr$/)) {
  404.                     return 'video';
  405.                 } else if (file.name.match(/\.mov$/)) {
  406.                     return 'video';
  407.                 }
  408.         }
  409.  
  410.         if (mimeType.match(/^text/)) {
  411.             return 'texts';
  412.         }
  413.  
  414.         if (mimeType.match(/^audio/)) {
  415.             return 'audio';
  416.         }
  417.  
  418.         if (mimeType.match(/^video/)) {
  419.             return 'video';
  420.         }
  421.  
  422.         if (mimeType.match(/^image/)) {
  423.             return 'images';
  424.         }
  425.  
  426.         // Generally _images.zip and/or _images.tar files contains images for a book
  427.         // See here: https://webarchive.jira.com/browse/WEBDEV-2580
  428.         if (file.name.match(/(_images.zip|_images.tar)$/)) {
  429.             return 'texts';
  430.         }
  431.  
  432.         return ''; // unknown mediatype
  433.  
  434.     }
  435.  
  436.     // For a set of mime types (from multiple files) guess the best mediatype
  437.     // for the item as a whole
  438.     ia_uploader.mediaTypeForMimeTypes = function(largest_file) {
  439.         var count = {};
  440.         // Choose mediatype based on largest file
  441.         /*
  442.         $.each(this.files_array, function(index, file) {
  443.             var mediaType = ia_uploader.mediaTypeForMimeType(file);
  444.             if (!count.hasOwnProperty(mediaType)) {
  445.                 count[mediaType] = 1;
  446.             } else {
  447.                 count[mediaType]++;
  448.             }
  449.         });
  450.         */
  451.         var mediaType = ia_uploader.mediaTypeForMimeType(largest_file);
  452.         count[mediaType] = 1;
  453.  
  454.         if (count['video']) {
  455.             return 'movies';
  456.         }
  457.  
  458.         if (count['audio']) {
  459.             return 'audio'; // if there's audio and text, text likely goes with the audio
  460.         }
  461.  
  462.         if (count['texts']) {
  463.             return 'texts';
  464.         }
  465.  
  466.         if (count['images']) {
  467.             return 'image';
  468.         }
  469.  
  470.         return ''; // unfortunately there's no "data" or "mixed" or "unknown"
  471.  
  472.     }
  473.  
  474.     ia_uploader.defaultCollectionForMediaType = function(mediaType, defaultCollections) {
  475.         if (defaultCollections.hasOwnProperty(mediaType)) {
  476.             var defaultCollection = defaultCollections[mediaType];
  477.             var text = mediaType + ':' + defaultCollection;
  478.             return text;
  479.         } else {
  480.             var defaultCollection = defaultCollections['data'];
  481.             var text = 'texts:' + defaultCollection;
  482.             return text;
  483.         }
  484.     }
  485.  
  486.     ia_uploader.addMetaField = function(div) {
  487.         var keynum = ($('.additional_meta_key').length +1).toString();
  488.         var str = $.sprintf('<div class="additional_meta"><input type="text" name="additional_meta_key_%s" class="additional_meta_key replace required metakey_regex metakey_limit" placeholder="key"/>: <input type="text" name="additional_meta_value_%s" class="additional_meta_value replace required" placeholder="value" /> <a href="javascript:void(0);" onclick="$(this).parent().remove();" class="additional_meta_remove_link">(remove)</a></div>', keynum, keynum);
  489.         div.append(str);
  490.     }
  491.  
  492.     ia_uploader.getS3MetaHeader = function(key) {
  493.         if (key in this.meta_index) {
  494.             this.meta_index[key]++;
  495.         } else {
  496.             this.meta_index[key] = 1;
  497.         }
  498.  
  499.         var key_index = this.meta_index[key].toString();
  500.         if (1 == key_index.length) {
  501.             key_index = "0" + key_index;
  502.         }
  503.  
  504.         return "x-archive-meta"+key_index+"-"+key;
  505.     }
  506.  
  507.  
  508.     // check_id()
  509.     //____________________________________________________________________________________
  510.     ia_uploader.check_id = function(identifier) {
  511.         var self = this;
  512.  
  513.         //remove green check
  514.         self.validate_element($("#item_id")[0], '');
  515.  
  516.         this.identifierAvailable(identifier, true,
  517.             // Success callback
  518.             function(response) {
  519.                 if (response.success) {
  520.                      // Hide other than success messages
  521.                     //$('.createIdentifierMessage').not('.createIdentifierFound').hide();
  522.  
  523.                     // Show success message
  524.                     //$('.createItemIdentifier').text(response.identifier);
  525.                     //$('.createIdentifierFound').show();
  526.  
  527.                     // Allow uploading
  528.                     //$('.createUploadButton').val('Upload').removeAttr('disabled');
  529.                     self.identifier = response.identifier;
  530.                     $("#item_id").text(response.identifier).show();
  531.                     $("#page_url").show();
  532.                     $("#create_id_checking").hide();
  533.                     $('#upload_button').text('Upload and Create Your Item').removeClass('btn-default').addClass('btn-primary').attr('disabled', false);
  534.                     self.validate_element($("#item_id")[0], response.identifier);
  535.                 } else {
  536.                     // Show error message
  537.                     //$('.createIdentifierMessage').not('.createIdentifierError').hide();
  538.                     //$('.createIdentifierError').show();
  539.                     //$('.createUploadButton').val('Upload');
  540.                     console.log('identifier lookup returned ' + response.success);
  541.                 }
  542.             },
  543.  
  544.             // Error callback
  545.             function(jqXHR, textStatus, errorThrown) {
  546.                 // Show error message
  547.                 //$('.createIdentifierMessage').not('.createIdentifierSystemError').hide();
  548.                 //$('.createIdentifierSystemError').show();
  549.                 //$('.createUploadButton').val('Upload');
  550.                 console.log('error in identifier lookup');
  551.             }
  552.  
  553.         );
  554.     }
  555.  
  556.  
  557.     // choose_files_finish()
  558.     //____________________________________________________________________________________
  559.     ia_uploader.choose_files_finish = function() {
  560.         $("#file_drop").hide();
  561.  
  562.         $("#upload_button").show();
  563.         if ('add' === this.mode) {
  564.             $("#upload_button").text("Add files to existing item").removeClass('btn-default').addClass('btn-primary').attr('disabled', false);
  565.         } else {
  566.             var title = $("#page_title").text();
  567.             if (('' === title) || ('Click to edit' === title) || ($('#page_title').attr('title') === title)) {
  568.                 var largest_file = this.files_array[this.largest_file.index];
  569.                 title = IA_UPLOADER.filenameToTitle(largest_file.name);
  570.                 $("#page_title").text(title);
  571.             } else {
  572.                 //console.log('title ', title);
  573.             }
  574.  
  575.             if (this.suggested_identifier) {
  576.                 var desiredIdentifier = this.suggested_identifier;
  577.             } else {
  578.                 var desiredIdentifier = title;
  579.             }
  580.  
  581.             this.check_id(desiredIdentifier);
  582.         }
  583.  
  584.         this.auto_select_collection();
  585.  
  586.         $("#file_info").show();
  587.         $("#preset_link").show();
  588.  
  589.         // Prevent the window from being accidentally closed during upload.
  590.         // Since this page contains an iframe, firefox will show the warning twice:
  591.         //  https://bugzilla.mozilla.org/show_bug.cgi?id=636374
  592.         window.onbeforeunload = function(e) {
  593.             return 'Closing this window will cancel the upload.';
  594.         };
  595.  
  596.         IA_UPLOADER.validate(false); //set green checkmarks
  597.     }
  598.  
  599.  
  600.     // add_files()
  601.     //____________________________________________________________________________________
  602.     ia_uploader.add_files = function(file_list) {
  603.         for (var i=0; i < file_list.length; i++) {
  604.             var file_name = '/'+file_list[i].name;
  605.             for (var j=0; j<this.files_array.length; j++) {
  606.                 var existing_file_name = this.files_array[j].s3path;
  607.                 if (file_name === existing_file_name) {
  608.                     this.overlay_alert('You can not add multiple files with the same name.', 'A file with the name "'+file_name+'" already exists in the upload list. Please try adding files again.');
  609.                     return;
  610.                 }
  611.             }
  612.         }
  613.  
  614.         for (var file_num = 0; file_num < file_list.length; file_num++) {
  615.             this.num_entries_queued++;
  616.             var f = file_list[file_num];
  617.  
  618.             var file_entry  = null;
  619.             var parent_node = null;
  620.             var add_mode    = true;
  621.             this.process_file(f, file_entry, parent_node, add_mode);
  622.         }
  623.  
  624.     }
  625.  
  626.  
  627.     // add_items()
  628.     //____________________________________________________________________________________
  629.     ia_uploader.add_items = function(items, files) {
  630.         for (var i=0; i < items.length; i++) {
  631.             var entry = items[i].webkitGetAsEntry();
  632.             if (null === entry) {
  633.                  //Chrome on Windows somtimes adds an item to the DataTransferItemList of
  634.                  //kind=string and type:text-uri-list. We should skip it.
  635.                 continue;
  636.             }
  637.             var file_name = entry.fullPath;
  638.             //console.log('checking', file_name);
  639.             for (var j=0; j<this.files_array.length; j++) {
  640.                 var existing_file_name = this.files_array[j].s3path;
  641.                 if (file_name === existing_file_name) {
  642.                     this.overlay_alert('You can not add multiple files with the same name.', 'A file with the name "'+file_name+'" already exists in the upload list. Please try adding files again.');
  643.                     return;
  644.                 } else if (existing_file_name.lastIndexOf(file_name, 0) === 0) {
  645.                     this.overlay_alert('You can not add multiple directories with the same name.', 'Please try adding files again.');
  646.                     return;
  647.                 }
  648.             }
  649.         }
  650.  
  651.         var self = this;
  652.         var add_mode = true;
  653.         this.choose_items(items, add_mode);
  654.     }
  655.  
  656.  
  657.     // make_dir_row()
  658.     //____________________________________________________________________________________
  659.     ia_uploader.make_dir_row = function(dir_path, parent_node) {
  660.         var id;
  661.  
  662.         // There is a treetable bug when using dotted data-tt-ids, such as '1.200'
  663.         // data-tt-ids are now numeric strings, generated by incrementing a global counter
  664.         /*
  665.         if (null !== parent_node) {
  666.             id = $.sprintf('%s.%d', parent_node.id, dir_num);
  667.         } else {
  668.             id = dir_num.toString();
  669.         }
  670.         */
  671.         id = (this.treetable_id_counter++).toString();
  672.  
  673.         var name = dir_path.split('/').slice(-1)[0];
  674.  
  675.         var row = $('<tr>').attr('data-tt-id', id);
  676.         if (null !== parent_node) {
  677.             row.attr('data-tt-parent', parent_node.id);
  678.         }
  679.  
  680.         var size = '--';
  681.  
  682.         row.append($('<td>').append($('<span>').addClass('folder').text(name)));
  683.         row.append($('<td>').text(size));
  684.         row.append($('<td>').append($('<a href="#" onclick=\'IA_UPLOADER.remove_item("'+id+'"); return false;\'><img src="img/removered.png"/></a>')));
  685.         return row;
  686.     }
  687.  
  688.  
  689.     // add_dir_row()
  690.     //____________________________________________________________________________________
  691.     ia_uploader.add_dir_row = function(dir_path, parent_node) {
  692.         var dir = this.make_dir_row(dir_path, parent_node);
  693.         //console.log('dir num', dir_num);
  694.         var id = dir.attr('data-tt-id');
  695.         if (null !== parent_node) {
  696.             dir.attr('data-tt-parent-id', parent_node.id);
  697.         }
  698.         $('#file_table').treetable('loadBranch', parent_node, dir);
  699.  
  700.         var dir_node = $('#file_table').treetable('node', id);
  701.  
  702.         dir_node.path = dir_path; //use expando to hold path in TreeTable.Node object
  703.  
  704.         return dir_node;
  705.     }
  706.  
  707.  
  708.     // make_file_row()
  709.     //____________________________________________________________________________________
  710.     ia_uploader.make_file_row = function(file, parent_node, id) {
  711.  
  712.         var name;
  713.         if (file.s3path !== undefined) {
  714.             name = file.s3path;
  715.         } else {
  716.             name = file.name;
  717.         }
  718.  
  719.         name = name.split('/').slice(-1)[0];
  720.  
  721.         var row = $('<tr>').attr('data-tt-id', id);
  722.         if (null !== parent_node) {
  723.             row.attr('data-tt-parent-id', parent_node.id);
  724.         }
  725.  
  726.         var size = this.formatFileSize(file.size);
  727.  
  728.         row.append($('<td>').append($('<span>').addClass('file').text(name)));
  729.         row.append($('<td>').text(size));
  730.         row.append($('<td>').append($('<a href="#" onclick=\'IA_UPLOADER.remove_item("'+id+'"); return false;\'><img src="img/removered.png"/></a>')));
  731.         return row;
  732.     }
  733.  
  734.  
  735.     // process_file()
  736.     //  - add_mode should be false the first time files are chosen, and true when
  737.     //    additional files are added to the file list.
  738.     //____________________________________________________________________________________
  739.     ia_uploader.process_file = function(f, entry, parent_node, add_mode) {
  740.         //use expando property to hold full path
  741.         if (null !== entry) {
  742.             f.s3path = entry.fullPath;
  743.         } else {
  744.             f.s3path = '/'+f.name;
  745.         }
  746.  
  747.         var id;
  748.         // There is a treetable bug when using dotted data-tt-ids, such as '1.200'
  749.         // data-tt-ids are now numeric strings, generated by incrementing a global counter
  750.         /*
  751.         if (null !== parent_node) {
  752.             id = $.sprintf('%s.%d', parent_node.id, file_num);
  753.         } else {
  754.             id = file_num.toString();
  755.         }
  756.         */
  757.         id = (this.treetable_id_counter++).toString();
  758.  
  759.         //use expando to hold treetable id
  760.         f.id = id;
  761.  
  762.         this.files_array.push(f);
  763.  
  764.         var row = this.make_file_row(f, parent_node, id);
  765.  
  766.         $('#file_table').treetable('loadBranch', parent_node, row);
  767.  
  768.         var file_node = $('#file_table').treetable('node', id);
  769.         file_node.path = f.s3path; //use expando to hold path in TreeTable.Node object
  770.  
  771.         if (f.size > this.largest_file.size) {
  772.             this.largest_file.size = f.size;
  773.             this.largest_file.index =  this.files_array.length-1;
  774.         }
  775.  
  776.         this.num_entries_processed++;
  777.         //console.log('queued=', this.num_entries_queued, ' processed=', this.num_entries_processed);
  778.  
  779.         if (this.num_entries_queued === this.num_entries_processed) {
  780.             $('#overlay_alert').remove();
  781.             $('#overlay').remove();
  782.             if (false === add_mode) {
  783.                 //console.log('finished processing initial file list:');
  784.                 //console.log(this.files_array);
  785.                 this.choose_files_finish();
  786.             }
  787.         }
  788.  
  789.     }
  790.  
  791.  
  792.     // get_file_from_fileentry()
  793.     //____________________________________________________________________________________
  794.     ia_uploader.get_file_from_fileentry = function(entry, parent_node, add_mode) {
  795.         var self = this;
  796.         this.num_entries_queued++;
  797.         entry.file(function(f) {
  798.             self.process_file(f, entry, parent_node, add_mode);
  799.         });
  800.     }
  801.  
  802.  
  803.     // get_files_from_direntry()
  804.     //____________________________________________________________________________________
  805.     ia_uploader.get_files_from_direntry = function(entry, parent_node, add_mode) {
  806.         var reader = entry.createReader();
  807.         var self = this;
  808.  
  809.         var dir_path = entry.fullPath;
  810.         var dir_node = this.add_dir_row(dir_path, parent_node);
  811.  
  812.         reader.readEntries(function(results) {
  813.             for (var i = 0; i < results.length; i++) {
  814.                 var entry = results[i];
  815.                 if (entry.isDirectory) {
  816.                     self.get_files_from_direntry(entry, dir_node, add_mode);
  817.                 } else if (entry.isFile) {
  818.                     self.get_file_from_fileentry(entry, dir_node, add_mode);
  819.                 }
  820.             }
  821.         });
  822.     }
  823.  
  824.  
  825.     // choose_items()
  826.     //____________________________________________________________________________________
  827.     ia_uploader.choose_items = function(items, add_mode) {
  828.         this.overlay_loading();
  829.  
  830.         for (var i = 0; i < items.length; i++) {
  831.             var entry = items[i].webkitGetAsEntry();
  832.             if (null === entry) {
  833.                  //Chrome on Windows adds an item to the DataTransferItemList of
  834.                  //kind=string and type:text-uri-list. We should skip it.
  835.                 continue;
  836.             }
  837.             var parent_node = null;
  838.             if (entry.isDirectory) {
  839.                 this.get_files_from_direntry(entry, parent_node, add_mode);
  840.             } else if (entry.isFile) {
  841.                 this.get_file_from_fileentry(entry, parent_node, add_mode);
  842.             }
  843.             //console.log('parsing top-level entry:')
  844.             //console.log(entry);
  845.         }
  846.     }
  847.  
  848.     // choose_files()
  849.     //____________________________________________________________________________________
  850.     ia_uploader.choose_files = function(file_list) {
  851.         this.num_entries_queued = file_list.length;
  852.         for (var file_num = 0; file_num < file_list.length; file_num++) {
  853.             var f = file_list[file_num];
  854.             var file_entry  = null;
  855.             var parent_node = null;
  856.             var add_mode    = false;
  857.             this.process_file(f, file_entry, parent_node, add_mode);
  858.         }
  859.  
  860.     }
  861.  
  862.  
  863.     // remove_item()
  864.     //____________________________________________________________________________________
  865.     ia_uploader.remove_item = function(id) {
  866.  
  867.         var tt_node = $('#file_table').treetable('node', id);
  868.         var path_to_rm = tt_node.path; //expando
  869.  
  870.         //console.log('removing', id, path_to_rm);
  871.  
  872.         for(var i=this.files_array.length-1; i>=0; i--) {
  873.             if(id === this.files_array[i].id) {
  874.                 //console.log('  removing index', this.files_array[i]);
  875.                 this.files_array.splice(i, 1);
  876.             } else if (this.files_array[i].s3path.lastIndexOf(path_to_rm+'/', 0) === 0) {
  877.                 //a directory being removed
  878.                 //console.log('  removing from dir', this.files_array[i]);
  879.                 this.files_array.splice(i, 1);
  880.             }
  881.         }
  882.  
  883.         $('#file_table').treetable('removeNode', id);
  884.     }
  885.  
  886.  
  887.     // auto_select_collection()
  888.     //____________________________________________________________________________________
  889.     ia_uploader.auto_select_collection = function() {
  890.         var jSelect = $('[name=mediatypecollection]');
  891.         var isLMA = jSelect.val().startsWith('etree:');
  892.         var element = jSelect[0];
  893.         // defaultCollections is always attached to DOM
  894.         var defaultCollections = JSON.parse(element.getAttribute('data-default-collections'));
  895.  
  896.         // Choose collection based on largest file
  897.         var largest_file = this.files_array[this.largest_file.index];
  898.         // Try to autoselect the mediatype/collection
  899.         // $$$ probably instead we should send the filename to the
  900.         // petabox and check against the suffixes in Edit.inc
  901.         // Also see share.php function setupMediatypeCollection
  902.         var fileType = this.mediaTypeForMimeTypes(largest_file);
  903.         var autoCollection = this.defaultCollectionForMediaType(fileType, defaultCollections);
  904.         var splitVal = autoCollection.split(/:/g);
  905.         var autoChosenType = splitVal[0];
  906.         var collectionPlaceholderText = 'Community ' + autoChosenType;
  907.  
  908.  
  909.         var isSelector = $(element).is('select');
  910.         var isInput = $(element).is('input');
  911.  
  912.  
  913.         if (isInput) {
  914.             if (this.primary_collection) {
  915.                 // display & capture identifier in the autcollection field
  916.                 $(element).attr('data-chosen-value', autoChosenType + ':' + this.primary_collection);
  917.                 $(element).attr('data-chosen-label', this.primary_collection);
  918.                 collectionPlaceholderText = this.primary_collection;
  919.             } else {
  920.                 $(element).attr('data-chosen-value', autoCollection);
  921.                 $(element).attr('data-chosen-label', collectionPlaceholderText);
  922.             }
  923.             $(element).val(collectionPlaceholderText);
  924.             $(element).hide();
  925.         }
  926.  
  927.         if (!isLMA && isSelector) {
  928.            if (this.primary_collection !== null) {
  929.                 var selector = 'option[value$=":' + this.primary_collection + '"]';
  930.             } else {
  931.                 var selector = 'option[value="' + autoCollection + '"]';
  932.             }
  933.             var options = jSelect.find(selector);
  934.  
  935.             if (options.length > 0) {
  936.                 // Option exists
  937.                 jSelect.val(options[0].value);
  938.             } else {
  939.                 var placeholderSelector = 'option[value=""]';
  940.                 var placeholderOption = jSelect.find(placeholderSelector);
  941.                 jSelect.val(placeholderOption[0].value);
  942.             }
  943.             collectionPlaceholderText = jSelect.find("option:selected").text();
  944.         }
  945.  
  946.         $('#collection').text(collectionPlaceholderText).removeClass('placeholder');
  947.     }
  948.  
  949.  
  950.     // set_license_text()
  951.     //____________________________________________________________________________________
  952.     ia_uploader.set_license_text = function() {
  953.         //set this.license_url, and return the text name of the license for display
  954.         var license_val = $('input[name=license_radio]:checked').val();
  955.         var text = "No license selected";
  956.         this.license_url = "";
  957.         if ("CC0" === license_val) {
  958.             text = "CC0";
  959.             this.license_url = "http://creativecommons.org/publicdomain/zero/1.0/";
  960.         } else if ("PD" === license_val) {
  961.             text = "Public Domain";
  962.             this.license_url = "http://creativecommons.org/publicdomain/mark/1.0/";
  963.         } else if ("CC" === license_val) {
  964.             var remix      = $('#cc_remix').is(':checked')
  965.             var noncom     = $('#cc_noncom').is(':checked')
  966.             var sharealike = $('#cc_sharealike').is(':checked')
  967.  
  968.             if (remix) {
  969.                 if (noncom && sharealike) {
  970.                     text = "Creative Commons Attribution-NonCommercial-ShareAlike";
  971.                     this.license_url = 'https://creativecommons.org/licenses/by-nc-sa/' + this.CREATIVE_COMMONS_LICENSE_VERSION + '/';
  972.                 } else if (noncom) {
  973.                     text = "Creative Commons Attribution-NonCommercial";
  974.                     this.license_url = 'https://creativecommons.org/licenses/by-nc/' + this.CREATIVE_COMMONS_LICENSE_VERSION + '/';
  975.                 } else if (sharealike) {
  976.                     text = "Creative Commons Attribution-ShareAlike";
  977.                     this.license_url = 'https://creativecommons.org/licenses/by-sa/' + this.CREATIVE_COMMONS_LICENSE_VERSION + '/';
  978.                 } else {
  979.                     text = "Creative Commons Attribution";
  980.                     this.license_url = 'https://creativecommons.org/licenses/by/' + this.CREATIVE_COMMONS_LICENSE_VERSION + '/';
  981.                 }
  982.             } else if (noncom) {
  983.                 text = "Creative Commons Attribution-NonCommercial-NoDerivs";
  984.                 this.license_url = 'https://creativecommons.org/licenses/by-nc-nd/' + this.CREATIVE_COMMONS_LICENSE_VERSION + '/';
  985.             } else {
  986.                 text = "Creative Commons Attribution-NoDerivs";
  987.                 this.license_url = 'https://creativecommons.org/licenses/by-nd/' + this.CREATIVE_COMMONS_LICENSE_VERSION + '/';
  988.             }
  989.         }
  990.         return text;
  991.     }
  992.  
  993.  
  994.     // set_date()
  995.     //____________________________________________________________________________________
  996.     ia_uploader.set_date = function() {
  997.         var date_text = $('#date_text').attr('title');
  998.         var got_date = false;
  999.         var year = $('#date_year').val();
  1000.         if ("" !== year) {
  1001.             got_date = true;
  1002.             date_text = year;
  1003.             var month = $('#date_month').val();
  1004.             if ("" !== month) {
  1005.                 date_text += '-' + month;
  1006.                 var day = $('#date_day').val();
  1007.                 if ("" !== day) {
  1008.                     date_text += '-' + day;
  1009.                 }
  1010.             }
  1011.         }
  1012.         $('#date_text').text(date_text);
  1013.         if (got_date) {
  1014.             $('#date_text').removeClass('placeholder');
  1015.         } else {
  1016.             $('#date_text').addClass('placeholder');
  1017.         }
  1018.         $('#date_text').show();
  1019.         $('#date_picker').hide();
  1020.  
  1021.         IA_UPLOADER.validate_date();
  1022.     }
  1023.  
  1024.     // collections_autocomplete
  1025.     ia_uploader.collections_autocomplete = function(selector) {
  1026.         var $collectionsInput = $(selector);
  1027.         var $spinnerContainer = $collectionsInput.siblings('.js-spinner-placeholder');
  1028.  
  1029.         var makeDefaultList = function makeDefaultCollectionsList() {
  1030.             var listToReturn = [];
  1031.             var defaultCollections = JSON.parse($("[name='mediatypecollection']").attr('data-default-collections'));
  1032.             var mediatypes = Object.keys(defaultCollections);
  1033.             mediatypes.forEach(function makeInputValue(mediatype) {
  1034.                 var collectionId = defaultCollections[mediatype];
  1035.                 var value = [mediatype, collectionId].join(':');
  1036.                 var label = 'Community ' + mediatype;
  1037.                 listToReturn.push({ label: label, value: value });
  1038.             })
  1039.             return listToReturn;
  1040.         };
  1041.  
  1042.         $collectionsInput.autocomplete({
  1043.             appendTo: $('.js-uploader-autocomplete-results'),
  1044.             minLength: 1,
  1045.             delay: 600,
  1046.             source: function collectionsAutocompleteCB (request, response) {
  1047.               var term = request.term;
  1048.               if (term.length < 1) {
  1049.                   return response([]);
  1050.               }
  1051.  
  1052.               var encodedTerm = encodeURIComponent(term);
  1053.               var url = '/services/collections/?filter_by=user_can_add_to&ui_context=upload_form&term=' + encodedTerm;
  1054.  
  1055.               // add spinner
  1056.               $spinnerContainer.html('<img src="/images/loading.gif">');
  1057.               $.ajax({
  1058.                 method: 'GET',
  1059.                 url: url
  1060.               })
  1061.               .success(function autcompleteFetch200(res) {
  1062.                 var availableCollections = res.available_collections || {};
  1063.                 var collections = Object.keys(availableCollections);
  1064.                 // remove spinner before serving response
  1065.                 $spinnerContainer.html('');
  1066.  
  1067.                 if (!collections.length) {
  1068.                     var mainPlaceholder = {
  1069.                         label: 'Nothing found. Try these: ',
  1070.                         title: '',
  1071.                         value: ''
  1072.                     };
  1073.                     var defaultCollections = makeDefaultList();
  1074.                     defaultCollections.unshift(mainPlaceholder);
  1075.                     return response(defaultCollections);
  1076.                 }
  1077.  
  1078.                 var results = collections.map(function mapResults(identifier) {
  1079.                     var privs = $collectionsInput.attr('data-priv');
  1080.                     var value = identifier;
  1081.                     var title = availableCollections[identifier];
  1082.                     var label =  privs === 'slash' ? title + ' (' + value + ')' : title;
  1083.                     return { value: value, label: label, title: title };
  1084.                 });
  1085.                 response(results);
  1086.               })
  1087.               .error(function autocompleteFetchFail(res) {
  1088.                 var errorPlaceholder = {
  1089.                     label: 'Something\'s amiss.  Please try again, or select from these: ',
  1090.                     value: '',
  1091.                     title: ''
  1092.                 };
  1093.                 var defaultCollections = makeDefaultList();
  1094.                 defaultCollections.unshift(errorPlaceholder);
  1095.                 // remove spinner before serving response
  1096.                 $spinnerContainer.html('');
  1097.                 return response(defaultCollections);
  1098.               })
  1099.             },
  1100.             focus: function autocomplete_focus() { return false }, // prevent value inserted on focus
  1101.             select: function autocomplete_select(event, ui) {
  1102.                 var collectionId = ui.item.value;
  1103.                 var label = ui.item.label;
  1104.                 var title = ui.item.title;
  1105.                 $("input[name='mediatypecollection']").attr('data-chosen-value', collectionId);
  1106.                 $("input[name='mediatypecollection']").attr('data-chosen-label', title);
  1107.                 this.value = title;
  1108.                 $('#collection').val(title);
  1109.                 return false;
  1110.             },
  1111.             change: function autocomplete_change(event, ui) {
  1112.                 if (!ui.item) {
  1113.                     var label = $("input[name='mediatypecollection']").attr('data-chosen-label');
  1114.                     $("input[name='mediatypecollection']").val(label);
  1115.                 }
  1116.             }
  1117.           })
  1118.     }
  1119.  
  1120.     // edit_value()
  1121.     //____________________________________________________________________________________
  1122.     ia_uploader.edit_value = function(parent_row) {
  1123.         var row_id = parent_row.attr('id');
  1124.  
  1125.         if ('collection_row' !== row_id) {
  1126.             // collection row has a helper label for new autocomplete input
  1127.             // make sure it hides when row isn't selected
  1128.             $('.js-collection-input-label').hide();
  1129.         }
  1130.  
  1131.         if ('description_row' === row_id) {
  1132.             $('#description').hide();
  1133.             $('#description_editor_div').show();
  1134.             $('#description_editor').wysiwyg('focus');
  1135.         } else if ('date_row' === row_id) {
  1136.             $('#date_text').hide();
  1137.             $('#date_picker').show();
  1138.             $('#date_year').focus();
  1139.         } else if ('collection_row' === row_id) {
  1140.             var $collectionDropdown = $('[name=mediatypecollection]');
  1141.             if ($collectionDropdown.is('input')) {
  1142.                 $('[name=mediatypecollection]').val('');
  1143.                 ia_uploader.collections_autocomplete('[name=mediatypecollection]');
  1144.                 $('[name=mediatypecollection]').focus();
  1145.                 $('.js-collection-input-label').show();
  1146.             }
  1147.             $('[name=mediatypecollection]').show();
  1148.             $('#collection').hide();
  1149.         } else if ('test_item_row' === row_id) {
  1150.             $('#test_item_select').show();
  1151.             $('#test_item').hide();
  1152.             this.validate_element($('#test_item')[0], true); //actual value doesn't matter
  1153.         } else if ('language_row' === row_id) {
  1154.             $('#language').hide();
  1155.             $('#language_select').show();
  1156.         } else if ('license_picker_row' === row_id) {
  1157.             $('#license_text').addClass('placeholder');
  1158.             $('#license_picker').show();
  1159.         } else if ('more_options_row' === row_id) {
  1160.             //nothing to do
  1161.         } else {
  1162.             var value_span = parent_row.find('.edit_text');
  1163.             if (value_span.hasClass('placeholder')) {
  1164.                 var old_value = '';
  1165.             } else {
  1166.                 var old_value = value_span.text();
  1167.             }
  1168.             var input_element = $('<input type="text"></input>').addClass('input_field').attr('placeholder', value_span.attr('title')).val(old_value);
  1169.             var self = this;
  1170.             input_element.keyup(function(event){
  1171.                 if(event.keyCode == self.RETURN_KEYCODE){
  1172.                     self.save_value(parent_row); //typing return in a text box calls save_value()
  1173.                 }
  1174.             });
  1175.             value_span.after(input_element);
  1176.             value_span.hide();
  1177.             input_element.focus();
  1178.         }
  1179.     }
  1180.  
  1181.  
  1182.     // save_value()
  1183.     //____________________________________________________________________________________
  1184.     ia_uploader.save_value = function(selected_rows) {
  1185.         //there should only be one row selected at a time, but handle more
  1186.         var self = this;
  1187.         selected_rows.each(function(i, e) {
  1188.             //console.log('saving ', e);
  1189.             var row = $(e);
  1190.             var row_id = row.attr('id');
  1191.             if ('page_url_row' === row_id) {
  1192.                 var input_element = row.find('.input_field');
  1193.                 var new_value = input_element.val();
  1194.                 input_element.remove();
  1195.                 if (new_value !== self.identifier) {
  1196.                     $("#create_id_checking").show();
  1197.                     $("#page_url").hide();
  1198.                     $('#upload_button').attr('disabled', true).text('Checking identifier...').removeClass('btn-primary').addClass('btn-default');
  1199.                     $('#item_id').text('').show(); //real value filled in by check_id callback
  1200.                     self.check_id(new_value);
  1201.                 } else {
  1202.                     var value_span = row.find('.edit_text');
  1203.                     value_span.show();
  1204.                 }
  1205.             } else if ('description_row' === row_id) {
  1206.                 //The wysiwyg editor leaves empty span elements if the user deletes all content.
  1207.                 //Validate against the text content, not the HTML. Also, if the text is blank,
  1208.                 //reset the value to the default 'Click to edit', otherwise users will have
  1209.                 //a hard time enabling the editor.
  1210.                 var div = document.createElement("div");
  1211.                 var value = $('#description_editor').wysiwyg('getContent');
  1212.                 div.innerHTML = value;
  1213.                 var text = $(div).text();
  1214.                 if ('' == text) {
  1215.                     value = $('#description').attr('title');
  1216.                     $('#description').addClass('placeholder');
  1217.                 } else {
  1218.                     $('#description').removeClass('placeholder');
  1219.                 }
  1220.                 self.validate_element($('#description')[0], text);
  1221.                 $('#description').html(value).show();
  1222.                 $('#description_editor_div').hide();
  1223.             } else if ('date_row' === row_id) {
  1224.                 self.set_date();
  1225.             } else if ('collection_row' === row_id) {
  1226.                 var collectionEl = $('[name=mediatypecollection]')[0];
  1227.                 var isSelect = $(collectionEl).is('select');
  1228.                 var isInput = $(collectionEl).is('input');
  1229.  
  1230.                 var collectionPlaceholder = '';
  1231.                 if (isSelect) {
  1232.                     collectionPlaceholder = $(collectionEl).find("option:selected").text();
  1233.                     if ($(collectionEl).val() === '') {
  1234.                         $('#collection').addClass('placeholder');
  1235.                         collectionPlaceholder = $('#collection').attr('title');
  1236.                     } else {
  1237.                         $('#collection').removeClass('placeholder');
  1238.                     }
  1239.                 }
  1240.                 if (isInput) {
  1241.                     collectionPlaceholder = $(collectionEl).attr('data-chosen-label');
  1242.                 }
  1243.  
  1244.                 $('#collection').text(collectionPlaceholder).show();
  1245.                 $(collectionEl).hide();
  1246.                 self.validate_element(collectionEl, $(collectionEl).val());
  1247.             } else if ('test_item_row' === row_id) {
  1248.                 var value = $('#test_item_select').val();
  1249.                 /*
  1250.                 if (0 === $('#test_item_select').prop('selectedIndex')) {
  1251.                     $('#test_item').addClass('placeholder');
  1252.                     value = 'Is this a test item?';
  1253.                     self.validate_element($('#test_item')[0], '');
  1254.  
  1255.                 } else {
  1256.                     $('#test_item').removeClass('placeholder');
  1257.                     self.validate_element($('#test_item')[0], value);
  1258.                 }
  1259.                 */
  1260.                 $('#test_item').text(value).show();
  1261.                 $('#test_item_select').hide();
  1262.             } else if ('language_row' === row_id) {
  1263.                 var value = $('#language_select').val();
  1264.                 self.validate_element($('#language')[0], value);
  1265.                 if ('' === value) {
  1266.                     $('#language').addClass('placeholder');
  1267.                     value = $('#language').attr('title');
  1268.                 } else {
  1269.                     $('#language').removeClass('placeholder');
  1270.                     value = $("#language_select option:selected").text();
  1271.                 }
  1272.                 $('#language').text(value).show();
  1273.                 $('#language_select').hide();
  1274.             } else if ('license_picker_row' === row_id) {
  1275.                 $('#license_picker').hide();
  1276.                 if ("No license selected" !== $('#license_text').text()) {
  1277.                     $('#license_text').removeClass('placeholder');
  1278.                 }
  1279.             } else if ('more_options_row' === row_id) {
  1280.                 //nothing to do
  1281.             } else {
  1282.                 var value_span = row.find('.edit_text');
  1283.                 var input_element = row.find('.input_field');
  1284.                 var new_value = input_element.val();
  1285.                 self.validate_element(value_span[0], new_value);
  1286.                 if ("" == new_value) {
  1287.                     new_value = value_span.attr('title');
  1288.                     value_span.addClass('placeholder');
  1289.                 } else {
  1290.                     value_span.removeClass('placeholder');
  1291.                 }
  1292.                 value_span.text(new_value);
  1293.                 value_span.show();
  1294.                 input_element.remove();
  1295.             }
  1296.         });
  1297.         selected_rows.removeClass('selected');
  1298.     }
  1299.  
  1300.  
  1301.     // remove_overlay()
  1302.     //____________________________________________________________________________________
  1303.     ia_uploader.remove_overlay = function() {
  1304.         $('#overlay').remove();
  1305.         $('#overlay_alert').remove();
  1306.         $('#upload_button').attr('disabled', false);
  1307.     }
  1308.  
  1309.  
  1310.     // set_total_bytes()
  1311.     //____________________________________________________________________________________
  1312.     ia_uploader.set_total_bytes = function() {
  1313.         var total_bytes = 0;
  1314.  
  1315.         $.each(this.files_array, function(index, file) {
  1316.             total_bytes += file.size;
  1317.         });
  1318.  
  1319.         this.total_bytes = total_bytes;
  1320.     }
  1321.  
  1322.  
  1323.     // overlay_alert()
  1324.     //____________________________________________________________________________________
  1325.     ia_uploader.overlay_alert = function(msg, desc) {
  1326.         $('body').append('<div id="overlay_alert"><div class="alert_msg">'+msg+'</div><div class="alert_desc">'+desc+'</div><button class="blue_button font14" onclick="IA_UPLOADER.remove_overlay();">Back</button></div>');
  1327.         var left = ($(window).width() - $('#overlay_alert').outerWidth()) * 0.5;
  1328.         var t = ($(window).height() - $('#overlay_alert').outerHeight()) * 0.5; //top is a global
  1329.         $('#overlay_alert').css({top: t, left: left});
  1330.     }
  1331.  
  1332.  
  1333.     // overlay_progress()
  1334.     //____________________________________________________________________________________
  1335.     ia_uploader.overlay_progress = function() {
  1336.         $('body').append('<div id="overlay_alert"><div id="progress_msg">Please wait while your page is being created</div><div id="progress_bar"></div><div id="progress_file"><span id="progress_file_span"></span><span id="progress_size"><span id="progress_file_size">0</span>/<span id="progress_file_total"></span></span></div></div>');
  1337.         var left = ($(window).width() - $('#overlay_alert').outerWidth()) * 0.5;
  1338.         var t = ($(window).height() - $('#overlay_alert').outerHeight()) * 0.5; //top is a global
  1339.  
  1340.         this.set_total_bytes();
  1341.  
  1342.         var percent = 100.0 * this.total_sent / this.total_bytes;
  1343.         percent = Math.max(percent, 10.0);
  1344.         $("#progress_bar").progressbar({ value: percent });
  1345.         $('#overlay_alert').css({top: t, left: left});
  1346.         $('#progress_file_total').text(this.formatFileSize(this.total_bytes));
  1347.     }
  1348.  
  1349.  
  1350.     // overlay_finish_msg()
  1351.     //____________________________________________________________________________________
  1352.     ia_uploader.overlay_finish_msg = function(msg, extra) {
  1353.         $('#progress_msg').text(msg);
  1354.         $('#progress_bar').remove();
  1355.         $('#progress_file').remove();
  1356.         $('#overlay_alert').append(extra);
  1357.  
  1358.         var left = ($(window).width() - $('#overlay_alert').outerWidth()) * 0.5;
  1359.         $('#overlay_alert').css('left', left);
  1360.     }
  1361.  
  1362.  
  1363.     /**
  1364.      * Displays a finishing message for case where we coudln't get extra status info
  1365.      * @param {string} identifier - Archive item identifier
  1366.      */
  1367.     ia_uploader.overlay_finish_no_status_for_identifier = function(identifier) {
  1368.       var url = 'https://archive.org/details/' + identifier;
  1369.       this.overlay_finish_msg(
  1370.         'Upload complete',
  1371.         '<button class="blue_button font14" onclick="window.location.href = \'' + url + '\';">Go to your page</button><div>Your files were uploaded, but we were unable to query the server to see if your page was ready. It may take a few minutes for all of your files to appear.</div>'
  1372.       );
  1373.     }
  1374.  
  1375.  
  1376.     // overlay_loading()
  1377.     //____________________________________________________________________________________
  1378.     ia_uploader.overlay_loading = function() {
  1379.         $('body').append('<div id="overlay"></div><div id="overlay_alert"><div id="progress_msg">Please wait while your files are loaded</div><div id="progress_bar"></div></div>');
  1380.  
  1381.         $('#progress_bar').html('&nbsp;')
  1382.         $('#progress_bar').css('background', 'url(/upload/img/indeterminate_progress.gif) no-repeat center');
  1383.  
  1384.         var left = ($(window).width() - $('#overlay_alert').outerWidth()) * 0.5;
  1385.         var t = ($(window).height() - $('#overlay_alert').outerHeight()) * 0.5; //top is a global
  1386.         $('#overlay_alert').css({top: t, left: left});
  1387.  
  1388.     }
  1389.  
  1390.  
  1391.  
  1392.     // check_status()
  1393.     //____________________________________________________________________________________
  1394.     ia_uploader.check_status = function() {
  1395.         window.onbeforeunload = null; //it is now safe to leave the page, even if the archive.php task has not yet completed.
  1396.  
  1397.         this.countUpload(this.files_array.length, this.total_bytes);
  1398.  
  1399.         $('#progress_bar').progressbar('destroy');
  1400.         $('#progress_bar').html('&nbsp;')
  1401.         $('#progress_bar').css('background', 'url(/upload/img/indeterminate_progress.gif) no-repeat center');
  1402.  
  1403.         var self = this;
  1404.  
  1405.         var checkStatus = function() {
  1406.             //console.log('status timer!');
  1407.             $.ajax({
  1408.                 type: "GET",
  1409.                 url: '/upload/app/upload_api.php',
  1410.                 // IE requires random param to defeat cache
  1411.                 data: { name: 'catalogRows', identifier: self.identifier, random: Math.random() },
  1412.                 dataType: "json",
  1413.                 success: function(json) {
  1414.                     // Check if have archived
  1415.                     var archiving = false;
  1416.                     var redrow    = false;
  1417.                     if (!json.success) {
  1418.                       self.overlay_finish_no_status_for_identifier(self.identifier);
  1419.                       return;
  1420.                     }
  1421.                     for (var i = 0; i < json.rows.length; i++) {
  1422.                         if (json.rows[i].cmd == 'archive.php') {
  1423.                             archiving = true;
  1424.                             if (json.rows[i].wait_admin == '2') {
  1425.                                 redrow = true;
  1426.                             }
  1427.                         }
  1428.                         //console.log('Task running[' + i + ']: ' + json.rows[i].cmd);
  1429.                     }
  1430.  
  1431.                     // clear the analytics timer we started after the upload was complete
  1432.                     if (self.analytics_timer) {
  1433.                         clearTimeout(self.analytics_timer);
  1434.                         self.analytics_timer = undefined;
  1435.                     }
  1436.  
  1437.                     if (redrow) {
  1438.                         var error_msg = 'There was an error creating your page';
  1439.                         var url = 'https://archive.org/create/';
  1440.                         var retry_msg = '<button class="btn btn-default font14" onclick="window.location.href = \''+url+'\';">Click here to try your upload again</button>';
  1441.                         if (self.is_admin) {
  1442.                             var catalog_url = 'https://catalogd.archive.org/catalog.php?history=1&identifier='+self.identifier;
  1443.                             retry_msg += '<div>As an admin, you can view the item <a href="'+catalog_url+'">history</a></div>';
  1444.                         }
  1445.                         self.overlay_finish_msg(error_msg, retry_msg);
  1446.                     } else if (archiving) {
  1447.                         //console.log('Still archiving...');
  1448.                         $('#progress_msg').text('Please wait while your page is being created');
  1449.                         $('#progress_file').text('Finishing upload... please be patient');
  1450.                         var left = ($(window).width() - $('#overlay_alert').outerWidth()) * 0.5;
  1451.                         $('#overlay_alert').css('left', left);
  1452.                         window.setTimeout(checkStatus, 2000); // reschedule ourselves
  1453.                     } else {
  1454.                         //console.log('No archive.php tasks running');
  1455.                         //parent.find('.createItemPreviewLink').hide();
  1456.                         var url = 'https://archive.org/details/'+self.identifier;
  1457.                         self.overlay_finish_msg('Your item is ready!', '<button class="blue_button font14" onclick="window.location.href = \''+url+'\';">Go to your page</button><div>or wait <span id="redirect_seconds">3</span> seconds for a redirect</div>');
  1458.  
  1459.                         var redirect_seconds = 10;
  1460.                         var countdown_to_redirect = function() {
  1461.                             if (redirect_seconds == 0) {
  1462.                                 window.location.href = url;
  1463.                             } else {
  1464.                                 $('#redirect_seconds').text(redirect_seconds);
  1465.                                 redirect_seconds--;
  1466.                                 window.setTimeout(countdown_to_redirect, 1000);
  1467.                             }
  1468.                         };
  1469.                         countdown_to_redirect();
  1470.                     }
  1471.  
  1472.                 },
  1473.                 error: function(xhr, status, error) {
  1474.                   self.overlay_finish_no_status_for_identifier(self.identifier);
  1475.                 }
  1476.             });
  1477.         };
  1478.  
  1479.         checkStatus();
  1480.  
  1481.     }
  1482.  
  1483.  
  1484.     // validate_element()
  1485.     //____________________________________________________________________________________
  1486.     ia_uploader.validate_element = function(e, val) {
  1487.         //console.log('in validate_element');
  1488.         //console.log('element ', e);
  1489.         //console.log('val:', val);
  1490.  
  1491.         var row = $(e).closest('.metadata_row');
  1492.  
  1493.         //if ((val === "Click to edit") || (val === "")) {
  1494.         if (val === "") {
  1495.             row.find('.checkmark').removeClass('check_green').addClass('check_gray');
  1496.             if ($(e).hasClass('required')) {
  1497.                 row.children('.mdata_key').css('color', 'red');
  1498.                 return false;
  1499.             } else {
  1500.                 //if the element is not required, set the check to gray but return true
  1501.                 return true;
  1502.             }
  1503.         } else {
  1504.             row.find('.checkmark').removeClass('check_gray').addClass('check_green');
  1505.             if ($(e).hasClass('required')) {
  1506.                 row.children('.mdata_key').css('color', 'black');
  1507.             }
  1508.             return true;
  1509.         }
  1510.     }
  1511.  
  1512.  
  1513.     // get_future_date_error()
  1514.     //____________________________________________________________________________________
  1515.     ia_uploader.get_future_date_error = function(year, month, day) {
  1516.         var now = new Date();
  1517.         //javascript's Date method takes zero-based months but one-based days
  1518.         var date = new Date(year, month-1, day);
  1519.  
  1520.         if (date > now) {
  1521.             return "The date must not be in the future";
  1522.         } else {
  1523.             return null;
  1524.         }
  1525.     }
  1526.  
  1527.     // get_date_error()
  1528.     //____________________________________________________________________________________
  1529.     ia_uploader.get_date_error = function() {
  1530.         var year = $('#date_year').val();
  1531.         if ("" === year) {
  1532.             return null; //no date, which is fine
  1533.         }
  1534.  
  1535.         //check the year
  1536.         if (!/^\d{4}$/.test(year)) {
  1537.             return "The year must be a four digit number";
  1538.         }
  1539.  
  1540.         var year_int = parseInt(year, 10);
  1541.  
  1542.         //year is ok, check the month
  1543.         var month = $('#date_month').val();
  1544.         if ("" === month) {
  1545.             //got year but no month, which is fine
  1546.             return this.get_future_date_error(year_int, 1, 1);
  1547.         }
  1548.  
  1549.         if (!/^\d{2}$/.test(month)) {
  1550.             return "The month must be a two digit number between 01 and 12";
  1551.         }
  1552.  
  1553.         var month_int = parseInt(month, 10);
  1554.  
  1555.         if ((month_int < 1) || (month_int>12)) {
  1556.             return "The month must be a two digit number between 01 and 12";
  1557.         }
  1558.  
  1559.         //month is ok, check the day
  1560.         var day = $('#date_day').val();
  1561.         if ("" === day) {
  1562.             //got year and month, but no day, which is fine
  1563.             return this.get_future_date_error(year_int, month_int, 1);
  1564.         }
  1565.  
  1566.         if (!/^\d{2}$/.test(day)) {
  1567.             return "The day must be a two digit number between 01 and 12";
  1568.         }
  1569.  
  1570.         var day_int = parseInt(day, 10);
  1571.         if ((day_int < 1) || (day_int>31)) {
  1572.             return "The day must be a two digit number between 01 and 31";
  1573.         }
  1574.  
  1575.         //all good. ensure date is not in the future
  1576.         return this.get_future_date_error(year_int, month_int, day_int);
  1577.     }
  1578.  
  1579.  
  1580.     // validate_date()
  1581.     //____________________________________________________________________________________
  1582.     ia_uploader.validate_date = function() {
  1583.         var error = this.get_date_error();
  1584.         if (null === error) {
  1585.             if ("" !== $('#date_year').val()) {
  1586.                 $('#date_row').find('.checkmark').removeClass('check_gray').addClass('check_green');
  1587.             }
  1588.             $('#date_row').children('.mdata_key').css('color', 'black');
  1589.         } else {
  1590.             $('#date_row').find('.checkmark').removeClass('check_green').addClass('check_gray');
  1591.             $('#date_row').children('.mdata_key').css('color', 'red');
  1592.         }
  1593.  
  1594.         return error;
  1595.     }
  1596.  
  1597.     // validate()
  1598.     //____________________________________________________________________________________
  1599.     ia_uploader.validate = function(show_overlay) {
  1600.         if (undefined === show_overlay) {
  1601.             show_overlay = true;
  1602.         }
  1603.  
  1604.         if ((this.files_array.length === 0) && show_overlay) {
  1605.             this.overlay_alert('There are no files to upload', 'Please drag some files into the gray file list area.');
  1606.             return false;
  1607.         }
  1608.  
  1609.         for (var i=0; i<this.files_array.length; i++) {
  1610.             if (this.files_array[i].size === 0) {
  1611.                 var str1 = 'You cannot upload empty files';
  1612.                 var str2 = 'Please remove zero-byte files from the upload list.';
  1613.                 //Directories show up as zero-byte files in Firefox
  1614.                 str2 += ' Please note that directories show up as zero-byte files in Firefox, and directory uploads are only supported in Chrome.';
  1615.                 this.overlay_alert(str1, str2);
  1616.                 return false;
  1617.             }
  1618.         }
  1619.  
  1620.         if ('add' === this.mode) {
  1621.             //We don't show the metadata editor in add mode, so do not validate these fields
  1622.             return true;
  1623.         }
  1624.  
  1625.         var self = this;
  1626.         var ret = true;
  1627.  
  1628.         $('#page_title, #description, #subjects, #creator, [name=mediatypecollection], #test_item, #language').each(function(index, element) {
  1629.             //console.log('validating ', element);
  1630.             var elemValue;
  1631.             if ($(element).hasClass('placeholder')) {
  1632.                 elemValue = '';
  1633.             } else if ($(element).is("input") && $(element).hasClass('mediatypecollection')) {
  1634.                 // special collections input with autocomplete has its own behavior
  1635.                 // get actual value from attribute = `data-chosen-value`
  1636.                 elemValue = $(element).attr('data-chosen-value');
  1637.             } else if ($(element).is("input") || $(element).is("textarea") || $(element).is("select")) {
  1638.                 elemValue = $(element).val();
  1639.             } else {
  1640.                 elemValue = $(element).text();
  1641.             }
  1642.  
  1643.             if (!self.validate_element(element, elemValue)) {
  1644.                 ret = false;
  1645.                 //console.log('  setting ret=false');
  1646.             }
  1647.         });
  1648.  
  1649.         if ((false === ret) && show_overlay) {
  1650.             self.overlay_alert('Please complete the required fields highlighted in red.', '(it would be even better to complete as many fields as possible)');
  1651.             return ret;
  1652.         }
  1653.  
  1654.         //validate date
  1655.         var date_error = this.validate_date(); //returns either an error string or null
  1656.         if ((null !== date_error) && show_overlay) {
  1657.             this.overlay_alert('Please complete the required fields highlighted in red.', date_error);
  1658.             return false;
  1659.         }
  1660.  
  1661.         //validate identifier
  1662.         if (('' == $('#item_id').text()) && show_overlay) {
  1663.             //We've come too far to give up who we are...
  1664.             this.overlay_alert('We are still checking for an available identifier', 'Please wait for the identifier check to complete before uploading');
  1665.             return false;
  1666.         }
  1667.  
  1668.         //validate identifier length
  1669.         if (this.max_id_length > 0) {
  1670.             var id_length = $('#item_id').text().length; //this.identifier not set yet since we are not in add mode
  1671.             if ((id_length > this.max_id_length) && show_overlay) {
  1672.                 this.overlay_alert('The Item Identifier is too long.', 'The Item Identifier must be less than ' + this.max_id_length + ' characters');
  1673.                 $('#page_url_row > .mdata_key').css('color', 'red');
  1674.                 return false;
  1675.             } else {
  1676.                 $('#page_url_row > .mdata_key').css('color', 'black');
  1677.             }
  1678.         }
  1679.  
  1680.         //validate additional metadata
  1681.         $.each($('.additional_meta_key, .additional_meta_value'), function(i, e) {
  1682.             var val = $(e).val();
  1683.             if (val === "") {
  1684.                 $(e).css('border-color', 'red');
  1685.                 ret = false;
  1686.             } else {
  1687.                 $(e).css('border-color', '');
  1688.             }
  1689.         });
  1690.  
  1691.         if ((false === ret) && show_overlay) {
  1692.             $('#more_options_text').css('color', 'red');
  1693.             self.overlay_alert('Please complete the required fields highlighted in red.', 'Additional metadata keys and values are required.');
  1694.             return ret;
  1695.         } else {
  1696.             $('#more_options_text').css('color', 'black');
  1697.         }
  1698.  
  1699.         $.each($('.additional_meta_key'), function(i, e) {
  1700.             var val = $(e).val();
  1701.             if (!/^[a-z][a-z\d_-]*$/.test(val)) {
  1702.                 $(e).css('border-color', 'red');
  1703.                 ret = false;
  1704.             } else {
  1705.                 $(e).css('border-color', '');
  1706.             }
  1707.         });
  1708.  
  1709.         if ((false === ret) && show_overlay) {
  1710.             $('#more_options_text').css('color', 'red');
  1711.             self.overlay_alert('Invalid metadata key.', 'Metadata key should start with a letter, and only lowercase letters, digits, hypens, and underscores are allowed.');
  1712.             return ret;
  1713.         } else {
  1714.             $('#more_options_text').css('color', 'black');
  1715.         }
  1716.  
  1717.         var invalid_keys = ['identifier', 'title', 'description', 'mediatype', 'test_item', 'licenseurl',   //these override metadata
  1718.                             'dir', 'prevtask', 'next_cmd', 'tester', //these are from ModifyXML::IGNORED_TAGS
  1719.                             'key', 'search', 'currentFieldNum', 'multi',
  1720.                             'scribe', 'nre',
  1721.                             'admincmd', 'adminuser', 'admintime', 'admincont',
  1722.                             'stub_files'
  1723.         ];
  1724.  
  1725.         $.each($('.additional_meta_key'), function(i, e) {
  1726.             var val = $(e).val();
  1727.             if (-1 !== $.inArray(val, invalid_keys)) {
  1728.                 $(e).css('border-color', 'red');
  1729.                 ret = false;
  1730.             } else {
  1731.                 $(e).css('border-color', '');
  1732.             }
  1733.         });
  1734.  
  1735.         if ((false === ret) && show_overlay) {
  1736.             $('#more_options_text').css('color', 'red');
  1737.             self.overlay_alert('Invalid metadata key.', 'The metadata key highlighted in red is not allowed.');
  1738.             return ret;
  1739.         } else {
  1740.             $('#more_options_text').css('color', 'black');
  1741.         }
  1742.  
  1743.  
  1744.         return true;
  1745.     }
  1746.  
  1747.  
  1748.     // uri_encode()
  1749.     //____________________________________________________________________________________
  1750.     ia_uploader.uri_encode = function(value) {
  1751.         //URI Encode metadata in all browsers to allow unicode.
  1752.         //In Safari, we need to do this because Safari drops non-ascii characters
  1753.         //In Firefox, we need to do this because when passing utf-8 bytes to setReqeuestHeader,
  1754.         //Firefox re-encodes utf-8 bytes as latin-1
  1755.         return 'uri(' + encodeURIComponent(value) + ')';
  1756.     }
  1757.  
  1758.  
  1759.     // add_meta_headers_from_class()
  1760.     //____________________________________________________________________________________
  1761.     ia_uploader.add_meta_headers_from_class = function(xhr) {
  1762.         // Find elements marked as item metadata and pass them to uploader
  1763.         var metaPattern = new RegExp(' *x-archive-meta-([^ ]+)');
  1764.         var self = this;
  1765.  
  1766.         // Find elements marked with class x-archive-meta-*
  1767.         $('#metadata').find('[class*=x-archive-meta]').each( function(index, elem) {
  1768.             //console.log($(elem).attr('class'), $(elem).text());
  1769.             var match = metaPattern.exec($(elem).attr('class'));
  1770.             var title = $.trim($(elem).attr('title'));
  1771.  
  1772.             if (match != null) {
  1773.                 var elemValue;
  1774.                 if ($(elem).is("input") || $(elem).is("textarea") || $(elem).is("select")) {
  1775.                     elemValue = $.trim($(elem).val());
  1776.                 } else {
  1777.                     elemValue = $.trim($(elem).text());
  1778.                 }
  1779.  
  1780.                 if ((elemValue === 'Click to edit') || (elemValue === '') || (elemValue === title)) {
  1781.                     return true; //continue
  1782.                 }
  1783.  
  1784.                 var xclass = self.getS3MetaHeader(match[1]);
  1785.                 xhr.setRequestHeader(xclass, self.uri_encode(elemValue));
  1786.             }
  1787.         });
  1788.     }
  1789.  
  1790.  
  1791.     // set_xhr_headers()
  1792.     //____________________________________________________________________________________
  1793.     ia_uploader.set_xhr_headers = function(xhr, file, number) {
  1794.         //getS3MetaHeader() uses the meta_index object to calculate the key index
  1795.         //for a particular header. Since headers are set for every file, we need to
  1796.         //reset this object for every file.
  1797.         this.meta_index = {};
  1798.  
  1799.         var self = this;
  1800.         xhr.setRequestHeader("Content-Type", "multipart/form-data; charset=UTF-8");
  1801.  
  1802.         // Don't trigger derive unless last file
  1803.         if (number !== (this.files_array.length - 1)) {
  1804.             xhr.setRequestHeader('x-archive-queue-derive', "0");
  1805.         }
  1806.  
  1807.         // Set interactive priority flag that causes the underlying
  1808.         // archive.org catalog task to run faster.
  1809.         xhr.setRequestHeader("x-archive-interactive-priority", "1");
  1810.  
  1811.         xhr.setRequestHeader("Cache-Control", "no-cache");
  1812.         xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  1813.         xhr.setRequestHeader("X-File-Name", self.uri_encode(file.name));
  1814.         xhr.setRequestHeader("X-File-Size", file.size);
  1815.         xhr.setRequestHeader("x-archive-size-hint", this.total_bytes);
  1816.  
  1817.         xhr.setRequestHeader('x-amz-acl','bucket-owner-full-control');
  1818.         xhr.setRequestHeader("x-amz-auto-make-bucket", "1"); // Create new item
  1819.         xhr.setRequestHeader("authorization", "LOW " + this.s3_access_key + ":" + this.s3_secret_key);
  1820.  
  1821.         if ('add' === this.mode) {
  1822.             //do not set item metadata in add mode.
  1823.             return;
  1824.         }
  1825.  
  1826.         // Split subject field
  1827.         var subject_string = $('#subjects').text();
  1828.         var subjects = subject_string.split(',');
  1829.         $.each(subjects, function(index, value) {
  1830.             value = $.trim(value);
  1831.             if (value) {
  1832.                 xhr.setRequestHeader(self.getS3MetaHeader("subject"), self.uri_encode(value));
  1833.             }
  1834.         });
  1835.  
  1836.         // Handle mediatype and collection
  1837.         // value of select is e.g. "audio.adsr"
  1838.         var mediatype_collection = $('#collection_row').find('[name=mediatypecollection]');
  1839.         var isInput = $(mediatype_collection).is('input');
  1840.         var mediatypeIdCombo = isInput ? $(mediatype_collection).attr('data-chosen-value') : $(mediatype_collection).val();
  1841.         var splitMediatypeId = mediatypeIdCombo.split(':');
  1842.         if (splitMediatypeId.length > 1) {
  1843.             // a collection was selected
  1844.             xhr.setRequestHeader("x-archive-meta-mediatype", self.uri_encode(splitMediatypeId[0]));
  1845.             xhr.setRequestHeader(self.getS3MetaHeader('collection'), self.uri_encode(splitMediatypeId[1]));
  1846.         }
  1847.  
  1848.         // We are now uri encoding the description on all browsers.
  1849.         // Safari: silently drops unicode characters from xhr headers
  1850.         // Chrome and Firefox: Throw javascript errors when adding html tags with quoted attributes to xhr headers
  1851.         xhr.setRequestHeader(self.getS3MetaHeader('description'), self.uri_encode($('#description').html()));
  1852.  
  1853.         this.add_meta_headers_from_class(xhr);
  1854.  
  1855.         if ("No" !== $('#test_item').text()) {
  1856.             xhr.setRequestHeader(self.getS3MetaHeader('collection'), 'test_collection');
  1857.         }
  1858.  
  1859.         if (this.license_url !== "") {
  1860.             xhr.setRequestHeader(self.getS3MetaHeader('licenseurl'), self.uri_encode(this.license_url));
  1861.         }
  1862.  
  1863.         //additional key-val pairs, including secondary collections
  1864.         $.each($('.additional_meta'), function(index, div) {
  1865.             var key = $(div).find('.additional_meta_key').val();
  1866.             var val = $(div).find('.additional_meta_value').val();
  1867.             xhr.setRequestHeader(self.getS3MetaHeader(key), self.uri_encode(val));
  1868.         });
  1869.  
  1870.         //version number
  1871.         xhr.setRequestHeader(self.getS3MetaHeader('scanner'), self.uri_encode('Internet Archive HTML5 Uploader ' + this.version));
  1872.  
  1873.     }
  1874.  
  1875.  
  1876.     // add_to_dict()
  1877.     //____________________________________________________________________________________
  1878.     ia_uploader.add_to_dict = function(meta_dict, key, val) {
  1879.         if (key in meta_dict) {
  1880.             meta_dict[key].push(val);
  1881.         } else {
  1882.             meta_dict[key] = [val];
  1883.         }
  1884.     }
  1885.  
  1886.  
  1887.     // show_preset_link()
  1888.     //____________________________________________________________________________________
  1889.     ia_uploader.show_preset_link = function() {
  1890.         var map = {'subjects':    'subject',
  1891.                    'description': 'description',
  1892.                    'creator':     'creator',
  1893.                    'date_text':   'date'
  1894.         };
  1895.  
  1896.         var meta_dict = {};
  1897.         var self = this;
  1898.  
  1899.         $('#subjects, #description, #creator, #date_text').each(function(index, element) {
  1900.             var str = $(element).text();
  1901.             if (!$(element).hasClass('placeholder') && ('' != str)) {
  1902.                 self.add_to_dict(meta_dict, map[element.id], str);
  1903.             }
  1904.         });
  1905.  
  1906.         var mediatype_collection = $('#collection_row').find('[name=mediatypecollection]');
  1907.         var isInput = $(mediatype_collection).is('input');
  1908.         var mediatypeIdCombo = isInput ? $(mediatype_collection).attr('data-chosen-value') : $(mediatype_collection).val();
  1909.         var splitMediatypeId = mediatypeIdCombo.split(':');
  1910.         if (splitMediatypeId.length > 1) {
  1911.             this.add_to_dict(meta_dict, 'collection', splitMediatypeId[1]);
  1912.         }
  1913.  
  1914.         if ($('#test_item_select').prop('selectedIndex') === 1) {
  1915.             this.add_to_dict(meta_dict, 'test_item', '1');
  1916.         }
  1917.  
  1918.         var lang = $('#language_select').val();
  1919.         if ('' !== lang) {
  1920.             this.add_to_dict(meta_dict, 'language', lang);
  1921.         }
  1922.  
  1923.         if (this.license_url !== "") {
  1924.             this.add_to_dict(meta_dict, 'licenseurl', this.license_url);
  1925.         }
  1926.  
  1927.         $.each($('.additional_meta'), function(index, div) {
  1928.             var key = $(div).find('.additional_meta_key').val();
  1929.             var val = $(div).find('.additional_meta_value').val();
  1930.             self.add_to_dict(meta_dict, key, val);
  1931.         });
  1932.  
  1933.         var i = 0;
  1934.         var url = 'http://archive.org/upload/';
  1935.         $.each(meta_dict, function(key, vals) {
  1936.             if (1 == vals.length) {
  1937.                 url += ((0==i)? '?' : '&');
  1938.                 url += key+'='+encodeURIComponent(vals[0]);
  1939.                 i++;
  1940.             } else {
  1941.                 vals.map(function(val) {
  1942.                     url += ((0==i)? '?' : '&');
  1943.                     url += key+'[]='+encodeURIComponent(val);
  1944.                     i++;
  1945.                 });
  1946.             }
  1947.         });
  1948.  
  1949.         url = '<textarea class="preset_textarea">'+url+'</textarea>';
  1950.         this.overlay_alert('Use the link below to upload a new item using the same metadata', url);
  1951.     }
  1952.  
  1953.  
  1954.     // show_resume_msg()
  1955.     //____________________________________________________________________________________
  1956.     ia_uploader.show_resume_msg = function(xhr) {
  1957.         $('#progress_msg').text('There is a network problem');
  1958.         $('#progress_bar').progressbar('destroy');
  1959.         $('#progress_bar').remove();
  1960.         $('#progress_file').remove();
  1961.  
  1962.         $('#overlay_alert').append('<button class="blue_button font14" onclick="IA_UPLOADER.resume();">Resume Uploading</button>');
  1963.  
  1964.         //TODO: use a template
  1965.         if (xhr.status !== 0) {
  1966.             var error_code   = $('<span id="upload_error_code"></span>').text(xhr.status);
  1967.             var error_status = $('<span id="upload_error_status"></span>').text(xhr.statusText);
  1968.             var error_show   = $('<span><a id="upload_error_show_details" href="javascript:$(\'#upload_error_details\').toggle(); IA_UPLOADER.recenter_alert();">(details)</a></span>')
  1969.             var error_text   = $('<div id="upload_error_text"></div>').append(error_code).append(error_status).append(error_show);
  1970.  
  1971.             //from https://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript/18750001#18750001
  1972.             var encoded_string = xhr.responseText.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
  1973.                return '&#'+i.charCodeAt(0)+';';
  1974.             });
  1975.             var error_details = $('<div id="upload_error_details"></div>').append($('<pre>').append(encoded_string));
  1976.             var error_box = $('<div id="upload_error"></div>').append(error_text).append(error_details);
  1977.  
  1978.             $('#overlay_alert').append(error_box);
  1979.         }
  1980.  
  1981.         this.recenter_alert();
  1982.     }
  1983.  
  1984.  
  1985.     // recenter_alert()
  1986.     //____________________________________________________________________________________
  1987.     ia_uploader.recenter_alert = function() {
  1988.         var left = ($(window).width() - $('#overlay_alert').outerWidth()) * 0.5;
  1989.         var t = ($(window).height() - $('#overlay_alert').outerHeight()) * 0.5; //top is a global
  1990.         $('#overlay_alert').css({top: t, left: left});
  1991.     }
  1992.  
  1993.  
  1994.     // resume()
  1995.     //____________________________________________________________________________________
  1996.     ia_uploader.resume = function() {
  1997.         $("#overlay_alert").remove();
  1998.         this.overlay_progress();
  1999.         this.upload_file(this.current_file);
  2000.     }
  2001.  
  2002.  
  2003.     // upload_file()
  2004.     //____________________________________________________________________________________
  2005.     ia_uploader.upload_file = function(number) {
  2006.         if (number === this.files_array.length) {
  2007.             //console.log('uploaded all files');
  2008.             this.check_status();
  2009.             return;
  2010.         }
  2011.  
  2012.         //console.log(number);
  2013.         //console.log(this);
  2014.         var file = this.files_array[number];
  2015.         //console.log(file);
  2016.         $('#progress_file_span').text(file.name);
  2017.  
  2018.         var xhr = this.xhr;
  2019.         var self = this;
  2020.  
  2021.         var url;
  2022.         if (file.s3path !== undefined) {
  2023.             url = this.s3_base_url + this.identifier + encodeURIComponent(file.s3path);
  2024.         } else {
  2025.             url = this.s3_base_url + this.identifier + '/' + encodeURIComponent(file.name);
  2026.         }
  2027.         xhr.open('PUT', url, true);
  2028.  
  2029.         this.set_xhr_headers(xhr, file, number);
  2030.  
  2031.         //console.log(xhr);
  2032.         xhr.send(file);
  2033.     }
  2034.  
  2035.  
  2036.     // upload()
  2037.     //____________________________________________________________________________________
  2038.     ia_uploader.upload = function() {
  2039.         //console.log('starting upload');
  2040.         $('#upload_button').attr('disabled', true);
  2041.         $('body').append("<div id='overlay'></div>");
  2042.  
  2043.         //if editing a row, save the edit before continuing
  2044.         this.save_value($('.metadata_row.selected'));
  2045.  
  2046.         if (!this.validate()) {
  2047.             return;
  2048.         }
  2049.  
  2050.         //console.log('validation passed');
  2051.  
  2052.         if ('add' !== this.mode) {
  2053.             //identifier is already set in add mode.
  2054.             this.identifier = $('#item_id').text();
  2055.         }
  2056.  
  2057.         this.overlay_progress();
  2058.  
  2059.         var self = this;
  2060.         this.xhr.upload['onprogress'] = function(rpe) {
  2061.             //console.log('onprogress ');
  2062.             //console.log(self);
  2063.             var percent = 100.0 * (self.total_sent+rpe.loaded) / self.total_bytes;
  2064.             percent = Math.max(percent, 10.0);
  2065.             if (percent === 100) {
  2066.                 // Sam wants to track how many users have an elapsed wait
  2067.                 // time over 300s once the upload completes
  2068.                 self.analytics_timer = setTimeout(() => {
  2069.                     self.log_event('upload-300s-elapsed-post-completion')
  2070.                 }, 1000 * 300);
  2071.             }
  2072.            
  2073.             //console.log('percent ');
  2074.             //console.log(percent);
  2075.             $( "#progress_bar" ).progressbar( "option", "value", percent );
  2076.             $('#progress_file_size').text(self.formatFileSize(self.total_sent+rpe.loaded));
  2077.         };
  2078.  
  2079.         this.xhr.onload = function(load) {
  2080.             //console.log('onload');
  2081.             //console.log(self);
  2082.             //console.log(self.xhr.status);
  2083.             var file = self.files_array[self.current_file];
  2084.             //console.log(file);
  2085.             self.total_sent += file.size;
  2086.             var percent = 100.0 * self.total_sent / self.total_bytes;
  2087.             percent = Math.max(percent, 10.0);
  2088.             //console.log('percent ');
  2089.             //console.log(percent);
  2090.             $( "#progress_bar" ).progressbar( "option", "value", percent );
  2091.             $('#progress_file_size').text(self.formatFileSize(self.total_sent));
  2092.  
  2093.             if (self.xhr.status != 200) {
  2094.                 console.log('onload received error status');
  2095.                 console.log(self.xhr.status);
  2096.                 self.show_resume_msg(self.xhr);
  2097.                 self.log_event('xhr.statusNot200', self.xhr.status);
  2098.             } else {
  2099.                 self.current_file += 1;
  2100.                 self.upload_file(self.current_file);
  2101.             }
  2102.         };
  2103.  
  2104.         this.xhr.onerror = function(event) {
  2105.             console.log('error');
  2106.             console.log(event);
  2107.             console.log(self.xhr);
  2108.             self.show_resume_msg(self.xhr);
  2109.             self.log_error('xhr.onerror', event);
  2110.         };
  2111.  
  2112.         this.xhr.onabort = function(event) {
  2113.             console.log('abort');
  2114.             console.log(event);
  2115.             console.log(self.xhr);
  2116.             self.show_resume_msg(self.xhr);
  2117.             self.log_error('xhr.onabort', event);
  2118.         };
  2119.  
  2120.         this.xhr.ontimeout = function(event) {
  2121.             console.log('timeout');
  2122.             console.log(event);
  2123.             console.log(self.xhr);
  2124.             self.show_resume_msg(self.xhr);
  2125.             self.log_error('xhr.ontimeout', event);
  2126.         };
  2127.  
  2128.         if (0 == this.current_file) {
  2129.             this.upload_start_time = new Date().getTime();
  2130.             //start analytics
  2131.             var values = {
  2132.                 'uploader': 1,
  2133.                 'start': 1,
  2134.                 'id': this.identifier,
  2135.                 'files': this.files_array.length,
  2136.                 'bytes': this.total_bytes,
  2137.                 'referrer': 'https://archive.org/upload'
  2138.             };
  2139.             if (typeof(archive_analytics) != 'undefined') {
  2140.                 archive_analytics.send_ping(values);
  2141.             }
  2142.         }
  2143.  
  2144.         this.upload_file(this.current_file);
  2145.  
  2146.     }
  2147.  
  2148.     ia_uploader.log_error = function (type, event) {
  2149.       var label =
  2150.         event.type + '-' + this.xhr.status + '-' + this.xhr.statusText;
  2151.       this.log_event(type, label);
  2152.     };
  2153.  
  2154.     ia_uploader.log_event = function (action, label) {
  2155.         if (!window.archive_analytics) return;
  2156.         archive_analytics.send_event_no_sampling('IA_Uploader', action, label);
  2157.     };
  2158.  
  2159.     return ia_uploader;
  2160.  
  2161. }(jQuery));
  2162.  
Advertisement
Add Comment
Please, Sign In to add comment