733 строки
26 KiB
JavaScript
733 строки
26 KiB
JavaScript
/*
|
|
* addonUploader()
|
|
* Extends fileUploader()
|
|
* Also, this can only be used once per page. Or you'll have lots of issues with closures and scope :)
|
|
*/
|
|
|
|
(function ($) {
|
|
/* Normalize results */
|
|
function getErrors(results) {
|
|
var errors = [];
|
|
|
|
if (results.validation.messages) {
|
|
$.each(results.validation.messages, function (i, v) {
|
|
if (v.type == 'error') {
|
|
errors.push(v.message);
|
|
}
|
|
});
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
function checkTimeout(validation) {
|
|
var timeout_id = [
|
|
'validator',
|
|
'unexpected_exception',
|
|
'validation_timeout',
|
|
];
|
|
return _.some(validation.messages, function (message) {
|
|
return _.isEqual(message.id, timeout_id);
|
|
});
|
|
}
|
|
|
|
$.fn.addonUploader = function (options) {
|
|
var settings = {
|
|
filetypes: ['zip', 'xpi', 'crx'],
|
|
getErrors: getErrors,
|
|
cancel: $(),
|
|
maxSize: null, // Dynamically set by devhub.js
|
|
submissionsDisabled: false,
|
|
};
|
|
|
|
if (options) {
|
|
$.extend(settings, options);
|
|
}
|
|
|
|
function parseErrorsFromJson(response, statusCode) {
|
|
var json,
|
|
errors = [];
|
|
try {
|
|
json = JSON.parse(response);
|
|
} catch (err) {
|
|
errors = [gettext('There was a problem contacting the server.')];
|
|
}
|
|
if (!errors.length) {
|
|
errors = settings.getErrors(json);
|
|
}
|
|
return {
|
|
errors: errors,
|
|
json: json,
|
|
};
|
|
}
|
|
|
|
return $(this).each(function () {
|
|
var $upload_field = $(this),
|
|
file = {};
|
|
|
|
/* Add some UI */
|
|
|
|
var ui_parent = $('<div>', {
|
|
class: 'invisible-upload prominent cta',
|
|
id: 'upload-file-widget',
|
|
}),
|
|
ui_link = $('<a>', {
|
|
class: 'button prominent',
|
|
href: '#',
|
|
text: gettext('Select a file...'),
|
|
}),
|
|
ui_details = $('<div>', {
|
|
class: 'upload-details',
|
|
text: gettext('Your add-on should end with .zip, .xpi or .crx'),
|
|
});
|
|
|
|
ui_link.toggleClass('disabled', settings.submissionsDisabled);
|
|
$upload_field.prop('disabled', settings.submissionsDisabled);
|
|
|
|
$upload_field.wrap(ui_parent);
|
|
$upload_field.before(ui_link);
|
|
$upload_field.parent().after(ui_details);
|
|
|
|
if (!z.capabilities.fileAPI) {
|
|
$('.invisible-upload').addClass('legacy');
|
|
}
|
|
|
|
/* Get things started */
|
|
|
|
var upload_box,
|
|
upload_title,
|
|
upload_progress_outside,
|
|
upload_progress_inside,
|
|
upload_status,
|
|
upload_results,
|
|
upload_status_percent,
|
|
upload_status_progress,
|
|
upload_status_cancel;
|
|
|
|
$upload_field.fileUploader(settings);
|
|
|
|
function updateStatus(percentage, size) {
|
|
if (percentage) {
|
|
upload_status.show();
|
|
p = Math.round(percentage);
|
|
size = (p / 100) * size;
|
|
|
|
// L10n: {0} is the percent of the file that has been uploaded.
|
|
upload_status_percent.text(format(gettext('{0}% complete'), [p]));
|
|
|
|
// L10n: "{bytes uploaded} of {total filesize}".
|
|
upload_status_progress.text(
|
|
format(gettext('{0} of {1}'), [
|
|
formatFileSize(size),
|
|
formatFileSize(file.size),
|
|
]),
|
|
);
|
|
}
|
|
}
|
|
|
|
/* Bind the events */
|
|
|
|
$upload_field.on('upload_start', function (e, _file) {
|
|
file = _file;
|
|
|
|
/* Remove old upload box */
|
|
if (upload_box) {
|
|
upload_box.remove();
|
|
}
|
|
|
|
/* Remove old errors */
|
|
$upload_field.closest('form').find('.errorlist').remove();
|
|
|
|
/* Set defaults */
|
|
$('#id_is_manual_review').prop('checked', false);
|
|
|
|
/* Don't allow submitting */
|
|
// The create theme wizard button is actually a link,
|
|
// so it's pointless to set the disabled property on it,
|
|
// instead add the special "concealed" class.
|
|
$('.addon-create-theme-section .button').addClass('concealed');
|
|
$('.addon-upload-dependant').prop('disabled', true);
|
|
$('.addon-upload-failure-dependant').prop({
|
|
disabled: true,
|
|
checked: false,
|
|
});
|
|
|
|
/* Create elements */
|
|
upload_title = $('<strong>', { id: 'upload-status-text' });
|
|
upload_progress_outside = $('<div>', { id: 'upload-status-bar' });
|
|
upload_progress_inside = $('<div>').css('width', 0);
|
|
upload_status = $('<div>', { id: 'uploadstatus' }).hide();
|
|
upload_status_percent = $('<span>');
|
|
upload_status_progress = $('<span>');
|
|
upload_status_cancel_a = $('<a>', {
|
|
href: '#',
|
|
text: gettext('Cancel'),
|
|
});
|
|
upload_status_cancel = $('<span> · </span>');
|
|
upload_results = $('<div>', { id: 'upload-status-results' });
|
|
upload_box = $('<div>', { class: 'upload-status ajax-loading' }).hide();
|
|
|
|
/* Set up structure */
|
|
upload_box.append(upload_title);
|
|
upload_progress_outside.append(upload_progress_inside);
|
|
upload_box.append(upload_progress_outside);
|
|
upload_status.append(upload_status_percent);
|
|
upload_status.append(' <span> · </span> ');
|
|
upload_status.append(upload_status_progress);
|
|
upload_status.append(upload_status_cancel);
|
|
upload_status_cancel.append(upload_status_cancel_a);
|
|
|
|
upload_box.append(upload_status);
|
|
upload_box.append(upload_results);
|
|
|
|
/* Add to the dom and clean up upload_field */
|
|
ui_details.after(upload_box);
|
|
|
|
/* It's showtime! */
|
|
upload_title.html(
|
|
format(gettext('Uploading {0}'), [escape_(file.name)]),
|
|
);
|
|
upload_box.show();
|
|
|
|
upload_box.addClass('ajax-loading');
|
|
|
|
upload_status_cancel_a.click(
|
|
_pd(function () {
|
|
$upload_field.trigger('upload_action_abort');
|
|
}),
|
|
);
|
|
});
|
|
|
|
$upload_field.on('upload_progress', function (e, file, pct) {
|
|
upload_progress_inside.animate(
|
|
{ width: pct + '%' },
|
|
{
|
|
duration: 300,
|
|
step: function (i) {
|
|
updateStatus(i, file.size);
|
|
},
|
|
},
|
|
);
|
|
});
|
|
|
|
$upload_field.on('upload_errors', function (e, file, errors, results) {
|
|
var all_errors = $.extend([], errors); // be nice to other handlers
|
|
upload_progress_inside.stop().css({ width: '100%' });
|
|
|
|
if ($('input#id_upload').val()) {
|
|
$('.addon-upload-failure-dependant').prop({
|
|
disabled: false,
|
|
checked: false,
|
|
});
|
|
}
|
|
|
|
$('.addon-create-theme-section .button').removeClass('concealed');
|
|
$upload_field.val('').prop('disabled', false);
|
|
$upload_field.trigger('reenable_uploader');
|
|
|
|
upload_title.html(
|
|
format(gettext('Error with {0}'), [escape_(file.name)]),
|
|
);
|
|
|
|
upload_progress_outside.attr('class', 'bar-fail');
|
|
upload_progress_inside.fadeOut();
|
|
|
|
$('<a>')
|
|
.text(
|
|
gettext(
|
|
'Please make sure to report any linting related issues on GitHub',
|
|
),
|
|
)
|
|
.attr('href', 'https://github.com/mozilla/addons-linter/')
|
|
.attr('class', 'addons-linter-info')
|
|
.attr('target', '_blank')
|
|
.attr('rel', 'noopener noreferrer')
|
|
.appendTo(upload_results);
|
|
|
|
var error_message = format(
|
|
ngettext(
|
|
'Your add-on failed validation with {0} error.',
|
|
'Your add-on failed validation with {0} errors.',
|
|
all_errors.length,
|
|
),
|
|
[all_errors.length],
|
|
);
|
|
|
|
$('<strong>').text(error_message).appendTo(upload_results);
|
|
|
|
var errors_ul = $('<ul>', { id: 'upload_errors' });
|
|
|
|
$.each(all_errors.splice(0, 5), function (i, error) {
|
|
errors_ul.append($('<li>', { html: error }));
|
|
});
|
|
|
|
if (all_errors.length > 0) {
|
|
var message = format(
|
|
ngettext(
|
|
'…and {0} more',
|
|
'…and {0} more',
|
|
all_errors.length,
|
|
),
|
|
[all_errors.length],
|
|
);
|
|
errors_ul.append($('<li>', { html: message }));
|
|
}
|
|
|
|
upload_results.append(errors_ul).addClass('status-fail');
|
|
|
|
if (results && results.full_report_url) {
|
|
// There might not be a link to the full report
|
|
// if we get an early error like unsupported type.
|
|
upload_results.append(
|
|
$('<a>', {
|
|
href: results.full_report_url,
|
|
class: 'view-more',
|
|
target: '_blank',
|
|
rel: 'noopener noreferrer',
|
|
text: gettext('See full validation report'),
|
|
}),
|
|
);
|
|
}
|
|
});
|
|
|
|
$upload_field.on('upload_finished', function () {
|
|
upload_box.removeClass('ajax-loading');
|
|
upload_status_cancel.remove();
|
|
});
|
|
|
|
$upload_field.on('upload_success', function (e, file, results) {
|
|
upload_title.html(
|
|
format(gettext('Validating {0}'), [escape_(file.name)]),
|
|
);
|
|
|
|
var animateArgs = {
|
|
duration: 300,
|
|
step: function (i) {
|
|
updateStatus(i, file.size);
|
|
},
|
|
complete: function () {
|
|
$upload_field.trigger('upload_success_results', [file, results]);
|
|
},
|
|
};
|
|
|
|
upload_progress_inside.animate({ width: '100%' }, animateArgs);
|
|
});
|
|
|
|
$upload_field.on(
|
|
'upload_onreadystatechange',
|
|
function (e, file, xhr, aborted) {
|
|
var errors = [],
|
|
$form = $upload_field.closest('form'),
|
|
json = {},
|
|
errOb;
|
|
if (
|
|
xhr.readyState == 4 &&
|
|
xhr.responseText &&
|
|
(xhr.status == 200 || xhr.status == 304 || xhr.status == 400)
|
|
) {
|
|
errOb = parseErrorsFromJson(xhr.responseText, xhr.status);
|
|
errors = errOb.errors;
|
|
json = errOb.json;
|
|
|
|
if (
|
|
json &&
|
|
json.upload &&
|
|
(!json.validation ||
|
|
!_.some(_.pluck(json.validation.messages, 'fatal')))
|
|
) {
|
|
$form.find('input#id_upload').val(json.upload);
|
|
}
|
|
if (errors.length > 0) {
|
|
$upload_field.trigger('upload_errors', [file, errors, json]);
|
|
} else {
|
|
$upload_field.trigger('upload_success', [file, json]);
|
|
$upload_field.trigger('upload_progress', [file, 100]);
|
|
}
|
|
$upload_field.trigger('upload_finished', [file]);
|
|
} else if (xhr.readyState == 4 && !aborted) {
|
|
if (xhr.status == 413) {
|
|
errors.push(
|
|
format(
|
|
gettext('Your add-on exceeds the maximum size of {0}.'),
|
|
[formatFileSize(settings.maxSize)],
|
|
),
|
|
);
|
|
} else {
|
|
// L10n: first argument is an HTTP status code
|
|
errors.push(
|
|
format(
|
|
gettext(
|
|
'Received an empty response from the server; status: {0}',
|
|
),
|
|
[xhr.status],
|
|
),
|
|
);
|
|
}
|
|
|
|
$upload_field.trigger('upload_errors', [file, errors]);
|
|
}
|
|
},
|
|
);
|
|
|
|
$('#id_admin_override_validation')
|
|
.addClass('addon-upload-failure-dependant')
|
|
.change(function () {
|
|
if ($(this).prop('checked')) {
|
|
// TODO: Disable these when unchecked, or bounce
|
|
// between upload_errors and upload_success
|
|
// handlers? I think the latter would mostly be a
|
|
// bad idea, since failed validation might give us
|
|
// the wrong results, and admins overriding
|
|
// validation might need some additional leeway.
|
|
$('.addon-upload-dependant').prop('disabled', false);
|
|
} else {
|
|
$('.addon-upload-dependant').prop('disabled', true);
|
|
}
|
|
});
|
|
$('.addon-upload-failure-dependant').prop('disabled', true);
|
|
|
|
function setCompatibilityCheckboxesValidity() {
|
|
if (
|
|
compatibilityCheckboxes.length === 0 ||
|
|
!compatibilityCheckboxes.is(':visible') ||
|
|
compatibilityCheckboxes.is(':checked')
|
|
) {
|
|
// If there are no compatibility checkboxes or they aren't visible or
|
|
// at least one is checked: remove custom validity on them.
|
|
compatibilityCheckboxes.each(function (idx, item) {
|
|
item.setCustomValidity('');
|
|
});
|
|
} else {
|
|
// We need a least a visible checkbox checked to continue. Add an error
|
|
// message to the first one.
|
|
compatibilityCheckboxes[0].setCustomValidity(
|
|
gettext(
|
|
'Your extension has to be compatible with at least one application.',
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
var compatibilityCheckboxes = $('.compatible-apps input[type=checkbox]');
|
|
var $newForm = $('.new-addon-file');
|
|
var $channelChoice = $('input[name="channel"]');
|
|
|
|
compatibilityCheckboxes.on('change', setCompatibilityCheckboxesValidity);
|
|
setCompatibilityCheckboxesValidity(); // Initialize.
|
|
|
|
function isUnlisted() {
|
|
// True if there's radio input with 'name="channel"' with 'value="unlisted"' checked, or a
|
|
// 'addon-is-listed' data on the new file form that is true.
|
|
return (
|
|
(typeof $newForm.data('addon-is-listed') != 'undefined' &&
|
|
!$newForm.data('addon-is-listed')) ||
|
|
($channelChoice.length &&
|
|
$('input[name="channel"]:checked').val() != 'listed')
|
|
);
|
|
}
|
|
|
|
// is_unlisted checkbox: should the add-on be listed on AMO? If not,
|
|
// change the addon upload button's data-upload-url.
|
|
// If this add-on is unlisted, then tell the upload view so
|
|
// it'll run the validator with the "listed=False"
|
|
// parameter.
|
|
var $submitAddonProgress = $('.submit-addon-progress');
|
|
function updateListedStatus() {
|
|
if (!isUnlisted()) {
|
|
// It's a listed add-on.
|
|
// /!\ For some reason, $upload_field.data('upload-url', val)
|
|
// doesn't correctly set the value for the code that uses it
|
|
// (in upload-base.js), at least in this context, so it
|
|
// doesn't upload to the correct url. Using
|
|
// .attr('data-upload-url', val) instead fixes that.
|
|
$upload_field.attr(
|
|
'data-upload-url',
|
|
$upload_field.data('upload-url-listed'),
|
|
);
|
|
$submitAddonProgress.removeClass('unlisted');
|
|
} else {
|
|
// It's an unlisted add-on.
|
|
$upload_field.attr(
|
|
'data-upload-url',
|
|
$upload_field.data('upload-url-unlisted'),
|
|
);
|
|
$submitAddonProgress.addClass('unlisted');
|
|
}
|
|
/* Don't allow submitting, need to reupload/revalidate the file. */
|
|
$('.addon-upload-dependant').prop('disabled', true);
|
|
$('.addon-upload-failure-dependant').prop({
|
|
disabled: true,
|
|
checked: false,
|
|
});
|
|
$('.upload-status').remove();
|
|
}
|
|
$channelChoice.on('change', updateListedStatus);
|
|
if ($channelChoice.length) updateListedStatus();
|
|
|
|
$('#id_is_manual_review').on('change', function () {
|
|
$('.addon-upload-dependant').prop('disabled', !$(this).is(':checked'));
|
|
});
|
|
|
|
$upload_field.on('upload_success_results', function (e, file, results) {
|
|
if (results.error) {
|
|
// This shouldn't happen. But it might.
|
|
var error = gettext('Unexpected server error while validating.');
|
|
$upload_field.trigger('upload_errors', [file, [error]]);
|
|
return;
|
|
}
|
|
|
|
// Validation results? If not, fetch the json again.
|
|
if (!results.validation) {
|
|
upload_progress_outside.attr('class', 'progress-idle');
|
|
// Not loaded yet. Try again!
|
|
setTimeout(function () {
|
|
$.ajax({
|
|
url: results.url,
|
|
dataType: 'json',
|
|
success: function (r) {
|
|
$upload_field.trigger('upload_success_results', [file, r]);
|
|
},
|
|
error: function (xhr) {
|
|
var errOb = parseErrorsFromJson(xhr.responseText, xhr.status);
|
|
$upload_field.trigger('upload_errors', [
|
|
file,
|
|
errOb.errors,
|
|
errOb.json,
|
|
]);
|
|
$upload_field.trigger('upload_finished', [file]);
|
|
},
|
|
});
|
|
}, 1000);
|
|
} else {
|
|
// Remove hidden android compatibility input if present (it will
|
|
// be re-added if necessary)
|
|
$('#id_compatible_apps_hidden_android').remove();
|
|
if (results.addon_type != 1) {
|
|
// No source or compat app selection for themes/dicts/langpacks.
|
|
$('.binary-source').hide();
|
|
$('.compatible-apps').hide();
|
|
} else {
|
|
// Pre-check Android or not depending on what we detected in the
|
|
// manifest.
|
|
let $checkbox = $('#id_compatible_apps .android input');
|
|
$checkbox
|
|
.prop('checked', results.explicitly_compatible_with_android)
|
|
.prop('disabled', results.explicitly_compatible_with_android)
|
|
.parent()
|
|
.prop(
|
|
'title',
|
|
results.explicitly_compatible_with_android === true
|
|
? gettext(
|
|
'Explicitly marked as compatible with Firefox for Android in the manifest',
|
|
)
|
|
: '',
|
|
);
|
|
// In addition, if we automatically ticked and disabled the Android
|
|
// checkbox, the browser won't submit the value. It's fine if
|
|
// Firefox was also checked, but if not then we'd not submit
|
|
// anything and the validation would fail server-side, so we
|
|
// add/remove an hidden input to compensate.
|
|
if (results.explicitly_compatible_with_android === true) {
|
|
$checkbox
|
|
.clone()
|
|
.prop('id', 'id_compatible_apps_hidden_android')
|
|
.prop('disabled', false)
|
|
.prop('type', 'hidden')
|
|
.insertAfter($checkbox);
|
|
}
|
|
$('.binary-source').show();
|
|
$('.compatible-apps').show();
|
|
}
|
|
setCompatibilityCheckboxesValidity();
|
|
|
|
var errors = getErrors(results),
|
|
v = results.validation,
|
|
timeout = checkTimeout(v);
|
|
if (errors.length > 0 && !timeout) {
|
|
$upload_field.trigger('upload_errors', [file, errors, results]);
|
|
return;
|
|
}
|
|
|
|
$upload_field.val('').prop('disabled', false);
|
|
|
|
/* Allow submitting */
|
|
$('.addon-upload-dependant').prop('disabled', false);
|
|
$('.addon-upload-failure-dependant').prop({
|
|
disabled: true,
|
|
checked: false,
|
|
});
|
|
$('.addon-create-theme-section').hide();
|
|
|
|
upload_title.html(
|
|
format(gettext('Finished validating {0}'), [escape_(file.name)]),
|
|
);
|
|
|
|
var message = '';
|
|
var messageCount = v.warnings + v.notices;
|
|
|
|
if (timeout) {
|
|
message = gettext(
|
|
'Your add-on validation timed out, it will be manually reviewed.',
|
|
);
|
|
} else if (v.warnings > 0) {
|
|
message = format(
|
|
ngettext(
|
|
'Your add-on was validated with no errors and {0} warning.',
|
|
'Your add-on was validated with no errors and {0} warnings.',
|
|
v.warnings,
|
|
),
|
|
[v.warnings],
|
|
);
|
|
} else if (v.notices > 0) {
|
|
message = format(
|
|
ngettext(
|
|
'Your add-on was validated with no errors and {0} message.',
|
|
'Your add-on was validated with no errors and {0} messages.',
|
|
v.notices,
|
|
),
|
|
[v.notices],
|
|
);
|
|
} else {
|
|
message = gettext(
|
|
'Your add-on was validated with no errors or warnings.',
|
|
);
|
|
}
|
|
|
|
upload_progress_outside.attr('class', 'bar-success');
|
|
upload_progress_inside.fadeOut();
|
|
|
|
$upload_field.trigger('reenable_uploader');
|
|
|
|
upload_results.addClass('status-pass');
|
|
|
|
$('<strong>').text(message).appendTo(upload_results);
|
|
|
|
let checklistWarningsIds = [
|
|
'NO_DOCUMENT_WRITE',
|
|
'DANGEROUS_EVAL',
|
|
'NO_IMPLIED_EVAL',
|
|
'UNSAFE_VAR_ASSIGNMENT',
|
|
'MANIFEST_CSP',
|
|
],
|
|
mv3NoticeId = '_MV3_COMPATIBILITY',
|
|
checklistMessages = [],
|
|
mv3CompatibilityMessage,
|
|
// this.id is in the form ["abc_def_ghi', 'foo_bar', 'something'],
|
|
// we usually only match one of the elements.
|
|
matchId = function (id) {
|
|
return this.hasOwnProperty('id') && _.contains(this.id, id);
|
|
};
|
|
|
|
if (results.validation.messages) {
|
|
for (var i = 0; i < results.validation.messages.length; i++) {
|
|
let current = results.validation.messages[i];
|
|
|
|
if (current.extra) {
|
|
// We want to ignore messages that are not coming from the
|
|
// linter in the logic that decides whether or not to show the
|
|
// submission checklist box. Those are tagged with extra: true.
|
|
messageCount--;
|
|
}
|
|
|
|
// Check for warnings we want to higlight specifically.
|
|
let matched = _.find(checklistWarningsIds, matchId, current);
|
|
if (matched) {
|
|
checklistMessages.push(gettext(current.message));
|
|
// We want only once every possible warning hit.
|
|
checklistWarningsIds.splice(
|
|
checklistWarningsIds.indexOf(matched),
|
|
1,
|
|
);
|
|
if (!checklistWarningsIds.length) break;
|
|
}
|
|
|
|
// Manifest v3 warning is a custom one added by addons-server
|
|
// that should be added once, regardless of whether or not we're
|
|
// displaying the submission warning box.
|
|
if (_.find([mv3NoticeId], matchId, current)) {
|
|
let mv3CompatibilityBox = $('<div>')
|
|
.attr('class', 'submission-warning')
|
|
.appendTo(upload_results);
|
|
$('<h5>').text(current.message).appendTo(mv3CompatibilityBox);
|
|
// That description is split into several paragraphs and can
|
|
// contain HTML for links.
|
|
current.description.forEach(function (item) {
|
|
$('<p>').html(item).appendTo(mv3CompatibilityBox);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (messageCount > 0) {
|
|
// Validation checklist should be displayed if there is at least
|
|
// one message coming from the linter.
|
|
let checklist_box = $('<div>')
|
|
.attr('class', 'submission-warning')
|
|
.appendTo(upload_results),
|
|
checklist = [
|
|
gettext(
|
|
'Include detailed version notes (this can be done in the next step).',
|
|
),
|
|
gettext(
|
|
'If your add-on requires an account to a website in order to be fully tested, include a test username and password in the Notes to Reviewer (this can be done in the next step).',
|
|
),
|
|
];
|
|
|
|
$('<h5>')
|
|
.text(gettext('Add-on submission checklist'))
|
|
.appendTo(checklist_box);
|
|
$('<p>')
|
|
.text(
|
|
gettext(
|
|
'Please verify the following points before finalizing your submission. This will minimize delays or misunderstanding during the review process:',
|
|
),
|
|
)
|
|
.appendTo(checklist_box);
|
|
if (results.validation.metadata.contains_binary_extension) {
|
|
checklistMessages.push(
|
|
gettext(
|
|
'Minified, concatenated or otherwise machine-generated scripts (excluding known libraries) need to have their sources submitted separately for review. Make sure that you use the source code upload field to avoid having your submission rejected.',
|
|
),
|
|
);
|
|
}
|
|
var checklist_ul = $('<ul>');
|
|
$.each(checklist, function (i) {
|
|
$('<li>').text(checklist[i]).appendTo(checklist_ul);
|
|
});
|
|
checklist_ul.appendTo(checklist_box);
|
|
if (checklistMessages.length) {
|
|
$('<h6>')
|
|
.text(
|
|
gettext(
|
|
'The validation process found these issues that can lead to rejections:',
|
|
),
|
|
)
|
|
.appendTo(checklist_box);
|
|
var messages_ul = $('<ul>');
|
|
$.each(checklistMessages, function (i) {
|
|
// Note: validation messages can contain HTML, in the form of
|
|
// links or entities, because devhub.views.json_upload_detail()
|
|
// uses processed_validation with escapes and linkifies linter
|
|
// messages (and escape_all() on non-linter messages).
|
|
// So we need to use html() and not text() to display them.
|
|
$('<li>').html(checklistMessages[i]).appendTo(messages_ul);
|
|
});
|
|
messages_ul.appendTo(checklist_box);
|
|
}
|
|
|
|
if (results.full_report_url) {
|
|
// There might not be a link to the full report
|
|
// if we get an early error like unsupported type.
|
|
$('<a>')
|
|
.text(gettext('See full validation report'))
|
|
.attr('href', results.full_report_url)
|
|
.attr('target', '_blank')
|
|
.attr('rel', 'noopener noreferrer')
|
|
.appendTo(checklist_box);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
};
|
|
})(jQuery);
|