static/js/zamboni/devhub.js (1,231 lines of code) (raw):
import $ from 'jquery';
import _ from 'underscore';
import { initCharCount, show_slug_edit } from './global';
import { _pd } from '../lib/prevent-default';
import { format } from '../lib/format';
import { slugify, validateFileUploadSize } from '../zamboni/global';
import { annotateLocalizedErrors, refreshL10n } from './l10n';
import { template } from '../lib/format';
import { capabilities } from '../zamboni/capabilities';
import { render } from 'timeago.js';
$(document).ready(function () {
// Modals
let $modalFile, $modalDelete, $modalDisable;
// Edit Add-on
$('#edit-addon').exists(initEditAddon);
// Poll for suppressed email removal updates.
$('.verify-email#verification_pending').exists(function () {
setTimeout(function () {
window.location.reload();
}, 30_000);
});
//Ownership
$('#authors_confirmed').exists(function () {
initAuthorFields();
initLicenseFields();
});
// Edit Versions
$('.edit-version').exists(initEditVersions);
// View versions
$('#version-list').exists(initVersions);
// Submission process
$('.addon-submission-process').exists(function () {
initLicenseFields();
initCharCount();
initSubmit();
});
// Validate addon (standalone)
$('.validate-addon').exists(initSubmit);
// Submission > Source
$('#submit-source').exists(initSourceSubmitOutcomes);
// Submission > Describe
$('#submit-describe').exists(initCatFields);
$('#submit-describe').exists(initCCLicense);
// Submission > Media
$('#submit-media').exists(function () {
initUploadIcon();
initUploadPreview();
});
// Developer experience survey
$('#dev-survey-banner').exists(function () {
initSurveyBanner();
});
// disable buttons if submission is explicitly disabled
const submissionField = $('#submission-field');
const submissionsDisabled =
submissionField && submissionField.data('submissions-enabled') === false;
$('.submission-buttons .button').toggleClass('disabled', submissionsDisabled);
// Add-on uploader
let $uploadAddon = $('#upload-addon');
if ($('#upload-addon').length) {
let opt = {
cancel: $('.upload-file-cancel'),
maxSize: $uploadAddon.data('max-upload-size'),
submissionsDisabled,
};
opt.appendFormData = function (formData) {
if ($('#addon-compat-upload').length) {
formData.append('app_id', $('#id_application option:selected').val());
formData.append(
'version_id',
$('#id_app_version option:selected').val(),
);
}
// theme_specific is a django BooleanField, so the value will be the
// litteral string "True" or "False". That's what the upload() view
// expects.
formData.append('theme_specific', $('#id_theme_specific').val());
};
$uploadAddon.addonUploader(opt);
}
$('.invisible-upload a').click(_pd(function () {}));
// when to start and stop image polling
if (
$('#edit-addon-media').length &&
$('#edit-addon-media').attr('data-checkurl') !== undefined
) {
imageStatus.start();
}
$('#edit-addon-media').on('click', function () {
imageStatus.cancel();
});
// hook up various links related to current version status
if ($('#modal-delete').length) {
$modalDelete = $('#modal-delete').modal('.delete-addon', {
width: 400,
callback: function (obj) {
return fixPasswordField(this);
},
});
if (window.location.hash === '#delete-addon') {
$modalDelete.render();
}
}
if ($('#modal-disable').length) {
$modalDisable = $('#modal-disable').modal('.disable-addon', {
width: 400,
callback: function (d) {
$('.version_id', this).val($(d.click_target).attr('data-version'));
return true;
},
});
if (window.location.hash === '#disable-addon') {
$modalDisable.render();
}
}
if ($('#modal-unlist').length) {
const $modalUnlist = $('#modal-unlist').modal('.unlist-addon', {
width: 400,
});
if (window.location.hash === '#unlist-addon') {
$modalUnlist.render();
}
}
// Show a confirmation modal for forms that have [data-confirm="selector"].
// This is specifically used for the request full review form for unlisted
// add-ons.
$(document.body).on('submit', '[data-confirm]', function (e) {
e.preventDefault();
let $form = $(e.target);
let $modal = $form.data('modal');
if (!$modal) {
$modal = $($form.data('confirm')).modal();
$form.data('modal', $modal);
}
$modal.render();
$modal.on('click', '.cancel', function (e) {
e.preventDefault();
e.stopPropagation();
$modal.trigger('close');
});
$modal.on('submit', 'form', function (e) {
e.preventDefault();
$form.removeAttr('data-confirm');
$form.submit();
});
});
$('.enable-addon').on('click', function () {
$.ajax({
type: 'POST',
url: $(this).data('url'),
success: function () {
window.location.reload();
},
});
});
// API credentials page
$('.api-credentials').on('submit', function () {
// Disallow double-submit. Don't actually disable the buttons, because
// then the correct one would not be submitted, but set the class to
// emulate that.
$(this).find('button').addClass('disabled');
});
});
$(document).ready(function () {
$.ajaxSetup({ cache: false });
$('.more-actions-popup').each(function () {
let el = $(this);
el.popup(el.closest('li').find('.more-actions'), {
width: 'inherit',
offset: { x: 15 },
callback: function (obj) {
return { pointTo: $(obj.click_target) };
},
});
});
$('.modal-delete').each(function () {
let el = $(this);
el.modal(el.siblings('.delete-addon'), {
width: 400,
callback: function (obj) {
fixPasswordField(this);
return { pointTo: $(obj.click_target) };
},
});
});
truncateFields();
initCompatibility();
$(document).on('click', '.addon-edit-cancel', function () {
const parent_div = $(this).closest('.edit-addon-section');
parent_div.load($(this).attr('href'), function () {
$('.tooltip').tooltip('#tooltip');
hideSameSizedIcons();
refreshL10n();
});
if (parent_div.is('#edit-addon-media')) {
imageStatus.start();
}
return false;
});
});
let noEdit = $('body').hasClass('no-edit');
if (noEdit) {
const $primary = $('.primary');
const $els = $primary.find('input, select, textarea, button, a.button');
$els.prop('disabled', true);
$primary.find('span.handle, a.remove').hide();
$('.primary h3 a.button').remove();
$(document).ready(function () {
$els.off().off();
});
}
function truncateFields() {
// TODO (potch) find a good fix for this later
// as per Bug 622030...
return;
// let els = [
// "#addon-description",
// "#developer_comments"
// ];
// $(els.join(', ')).each(function(i,el) {
// let $el = $(el),
// originalHTML = $el.html();
// $el.on("click", "a.truncate_expand", function(e) {
// e.preventDefault();
// $el.html(originalHTML).css('max-height','none');
// })
// .vtruncate({
// truncText: format("… <a href='#' class='truncate_expand'>{0}</a>",[gettext("More")])
// });
// });
}
function addonFormSubmit() {
const parent_div = $(this);
// If the baseurl changes (the slug changed) we need to go to the new url.
let baseurl = function () {
return parent_div.find('#addon-edit-describe').attr('data-baseurl');
};
$('.edit-media-button button').prop('disabled', false);
$('form', parent_div).submit(function (e) {
e.preventDefault();
let old_baseurl = baseurl();
parent_div.find('.item').removeClass('loaded').addClass('loading');
let $document = $(document),
scrollBottom = $document.height() - $document.scrollTop(),
$form = $(this);
$.post($form.attr('action'), $form.serialize(), function (d) {
parent_div.html(d).each(addonFormSubmit);
// The HTML has changed after we posted the form, thus the need to retrieve the new HTML
$form = parent_div.find('form');
let hasErrors = $form.find('.errorlist').length;
$('.tooltip').tooltip('#tooltip');
if (!hasErrors && old_baseurl && old_baseurl !== baseurl()) {
document.location = baseurl();
}
$document.scrollTop($document.height() - scrollBottom);
truncateFields();
annotateLocalizedErrors(parent_div);
if (parent_div.is('#edit-addon-media')) {
imageStatus.start();
hideSameSizedIcons();
}
if ($form.find('#addon-categories-edit').length) {
initCatFields();
}
if (!hasErrors) {
let e = $(
format('<b class="save-badge">{0}</b>', [gettext('Changes Saved')]),
).appendTo(parent_div.find('h3').first());
setTimeout(function () {
e.css('opacity', 0);
setTimeout(function () {
e.remove();
}, 200);
}, 2000);
}
});
});
reorderPreviews();
refreshL10n();
}
$('#user-form-template .author-email').attr({
placeholder: gettext("Enter a new author's email address"),
readonly: false,
});
function initEditAddon() {
if (noEdit) return;
// Load the edit form.
$('#edit-addon').on('click', 'h3 a', function (e) {
e.preventDefault();
const a = e.target;
const parent_div = $(a).closest('.edit-addon-section');
parent_div.find('.item').addClass('loading');
parent_div.load($(a).attr('data-editurl'), function () {
$('.tooltip').tooltip('#tooltip');
if (parent_div.find('#addon-categories-edit').length) {
initCatFields();
}
$(this).each(addonFormSubmit);
initInvisibleUploads();
});
return false;
});
// Init icon javascript.
hideSameSizedIcons();
initUploadIcon();
initUploadPreview();
}
function create_new_preview_field() {
let forms_count = $('#id_files-TOTAL_FORMS').val(),
last = $('#file-list .preview').last(),
last_clone = last.clone();
$('input, textarea, div', last_clone).each(function () {
let re = new RegExp(format('-{0}-', [forms_count - 1])),
new_count = '-' + forms_count + '-',
el = $(this);
$.each(['id', 'name', 'data-name'], function (k, v) {
if (el.attr(v)) {
el.attr(v, el.attr(v).replace(re, new_count));
}
});
});
$(last).after(last_clone);
$('#id_files-TOTAL_FORMS').val(parseInt(forms_count, 10) + 1);
return last;
}
function renumberPreviews() {
const previews = $('#file-list').children('.preview:visible');
previews.each(function (i, el) {
$(this).find('.position input').val(i);
});
$(previews)
.find('.handle')
.toggle(previews.length > 1);
}
function reorderPreviews() {
let preview_list = $('#file-list');
if (preview_list.length) {
preview_list.sortable({
items: '.preview:visible',
handle: '.handle',
containment: preview_list,
tolerance: 'pointer',
update: renumberPreviews,
});
renumberPreviews();
}
}
function initUploadPreview() {
let forms = {},
$f = $('#edit-addon-media, #submit-media');
function upload_start_all(e) {
// Remove old errors.
$('.edit-addon-media-screenshot-error').hide();
// Don't let users submit a form.
$('.edit-media-button button').prop('disabled', true);
}
function upload_finished_all(e) {
// They can submit again
$('.edit-media-button button').prop('disabled', false);
}
function upload_start(e, file) {
const form = create_new_preview_field();
forms['form_' + file.instance] = form;
$(form)
.show()
.find('.preview-thumb')
.addClass('loading')
.css('background-image', 'url(' + file.dataURL + ')');
renumberPreviews();
}
function upload_finished(e, file) {
const form = forms['form_' + file.instance];
form.find('.preview-thumb').removeClass('loading');
renumberPreviews();
}
function upload_success(e, file, upload_hash) {
const form = forms['form_' + file.instance];
form.find('[name$="upload_hash"]').val(upload_hash);
}
function upload_errors(e, file, errors) {
let form = forms['form_' + file.instance],
$el = $(form),
error_msg = gettext('There was an error uploading your file.'),
$error_title = $('<strong>').text(error_msg),
$error_list = $('<ul>');
$el.addClass('edit-addon-media-screenshot-error');
$.each(errors, function (i, v) {
$error_list.append('<li>' + v + '</li>');
});
$el.find('.preview-thumb').addClass('error-loading');
$el
.find('.edit-previews-text')
.addClass('error')
.html('')
.append($error_title)
.append($error_list);
$el.find('.delete input').prop('checked', true);
renumberPreviews();
}
if (capabilities.fileAPI) {
$f.on('upload_finished', '#screenshot_upload', upload_finished)
.on('upload_success', '#screenshot_upload', upload_success)
.on('upload_start', '#screenshot_upload', upload_start)
.on('upload_errors', '#screenshot_upload', upload_errors)
.on('upload_start_all', '#screenshot_upload', upload_start_all)
.on('upload_finished_all', '#screenshot_upload', upload_finished_all)
.on('change', '#screenshot_upload', function (e) {
$(this).imageUploader();
});
}
$('#edit-addon-media, #submit-media').on(
'click',
'#file-list .remove',
function (e) {
e.preventDefault();
let row = $(this).closest('.preview');
row.find('.delete input').prop('checked', true);
row.slideUp(300, renumberPreviews);
},
);
}
function initInvisibleUploads() {
if (!capabilities.fileAPI) {
$('.invisible-upload').addClass('legacy');
}
}
function initUploadIcon() {
initInvisibleUploads();
$('#edit-addon-media, #submit-media').on(
'click',
'#icons_default a',
function (e) {
e.preventDefault();
let $error_list = $('#icon_preview').parent().find('.errorlist'),
$parent = $(this).closest('li');
$('input', $parent).prop('checked', true);
$('#icons_default a.active').removeClass('active');
$(this).addClass('active');
$('#id_icon_upload').val('');
$('#icon_preview').show();
$('#icon_preview_32 img').attr('src', $('img', $parent).attr('src'));
$('#icon_preview_64 img').attr('src', $('img', $parent).data('src-64'));
$('#icon_preview_128 img').attr('src', $('img', $parent).data('src-128'));
$error_list.html('');
},
);
// Upload an image!
let $f = $('#edit-addon-media, #submit-media'),
upload_errors = function (e, file, errors) {
let $error_list = $('#icon_preview').parent().find('.errorlist');
$.each(errors, function (i, v) {
$error_list.append('<li>' + v + '</li>');
});
},
upload_success = function (e, file, upload_hash) {
$('#id_icon_upload_hash').val(upload_hash);
$('#icons_default a.active').removeClass('active');
$('#icon_preview img').attr('src', file.dataURL);
$('#icons_default input:checked').prop('checked', false);
$(
'input[name="icon_type"][value="' + file.type + '"]',
$('#icons_default'),
).prop('checked', true);
},
upload_start = function (e, file) {
let $error_list = $('#icon_preview').parent().find('.errorlist');
$error_list.html('');
$('.icon_preview img', $f).addClass('loading');
$('.edit-media-button button').prop('disabled', true);
},
upload_finished = function (e) {
$('.icon_preview img', $f).removeClass('loading');
$('.edit-media-button button').prop('disabled', false);
};
$f.on('upload_success', '#id_icon_upload', upload_success)
.on('upload_start', '#id_icon_upload', upload_start)
.on('upload_finished', '#id_icon_upload', upload_finished)
.on('upload_errors', '#id_icon_upload', upload_errors)
.on('change', '#id_icon_upload', function (e) {
if (capabilities.fileAPI) {
$(this).imageUploader();
} else {
$('#icon_preview').hide();
}
});
}
function fixPasswordField($context) {
// This is a hack to prevent password managers from automatically
// deleting add-ons. See bug 630126.
$context.find('input[type="password"]').each(function () {
let $this = $(this);
if ($this.attr('data-name')) {
$this.attr('name', $this.attr('data-name'));
}
});
return true;
}
function initVersions() {
$('#modals').hide();
let versions;
$.getJSON($('#version-list').attr('data-stats'), function (json) {
versions = json;
});
$('#modal-delete-version').modal('.version-delete .remove', {
width: 400,
callback: function (d) {
/* This sucks because of ngettext. */
let el = $(d.click_target),
version = versions[el.data('version')],
is_current = el.data('is-current') === 1,
can_be_disabled = el.data('can-be-disabled') === 1,
header = $('h3', this),
files = $('#del-files', this);
header.text(format(header.attr('data-tmpl'), version));
files.text(
format(
ngettext('{files} file', '{files} files', version.files),
version,
),
);
$('.version_id', this).val(version.id);
$('.current-version-warning', this).toggle(is_current);
// If the version is promoted and current, show the warning and
// hide the whole form.
$('.promoted-version-warning', this).toggle(!can_be_disabled);
$('form', this).toggle(can_be_disabled);
return true;
},
});
function addToReviewHistory(json, historyContainer, reverseOrder) {
let empty_note = historyContainer.children('.review-entry-empty');
json.forEach(function (note) {
let clone = empty_note.clone(true, true);
clone.attr('class', 'review-entry');
if (note['highlight'] == true) {
clone.addClass('new');
}
clone.find('.action')[0].textContent = note['action_label'];
let user = clone.find('.user_name');
user[0].textContent = note['user']['name'];
let date = clone.find('.timeago');
date[0].textContent = note['date'];
date.attr('datetime', note['date']);
date.attr('title', note['date']);
clone.find('pre:contains("$comments")')[0].textContent = note['comments'];
if (note['attachment_url']) {
clone.find('.review-entry-attachment').removeClass('hidden');
clone.find('.attachment_url').attr('href', note['attachment_url']);
clone.find('.attachment_size')[0].textContent =
`(${note['attachment_size']})`;
}
if (reverseOrder) {
historyContainer.append(clone);
} else {
clone.insertAfter(historyContainer.children('.review-entry-failure'));
}
});
render(document.querySelectorAll('time.timeago'));
}
function loadReviewHistory(div, nextLoad) {
div.removeClass('hidden');
const replybox = div.children('.dev-review-reply');
if (replybox.length == 1) {
replybox[0].scrollIntoView(false);
}
let sessionId = div.data('session-id');
let container = div.children('.history-container');
container.children('.review-entry-loading').removeClass('hidden');
container.children('.review-entry-failure').addClass('hidden');
let api_url;
if (!nextLoad) {
container.children('.review-entry').remove();
api_url = div.data('api-url');
} else {
api_url = div.data('next-url');
}
let success = function (json) {
addToReviewHistory(json['results'], container);
let loadmorediv = container.children('div.review-entry-loadmore');
if (json['next']) {
loadmorediv.removeClass('hidden');
container.prepend(loadmorediv);
div.attr('data-next-url', json['next']);
} else {
loadmorediv.addClass('hidden');
}
};
let fail = function (xhr) {
container.children('.review-entry-failure').removeClass('hidden');
container
.children('.review-entry-failure')
.append(
'<pre>' +
api_url +
', ' +
xhr.statusText +
', ' +
xhr.responseText +
'</pre>',
);
};
$.ajax({
url: api_url,
type: 'get',
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Session ' + sessionId);
},
complete: function (xhr) {
container.children('.review-entry-loading').addClass('hidden');
},
processData: false,
contentType: false,
success: success,
error: fail,
});
}
$('.review-history-show').click(function (e) {
e.preventDefault();
let version = $(this).data('version');
let $show_link = $('#review-history-show-' + version);
$show_link.addClass('hidden');
$show_link.next().removeClass('hidden');
loadReviewHistory($($show_link.data('div')));
});
$('.review-history-hide').click(function (e) {
e.preventDefault();
let $tgt = $(this);
$tgt.addClass('hidden');
let prev = $tgt.prev();
prev.removeClass('hidden');
$(prev.data('div')).addClass('hidden');
});
$('a.review-history-loadmore').click(function (e) {
e.preventDefault();
let $tgt = $(this);
loadReviewHistory($($tgt.data('div')), true);
});
$('.review-history-hide').prop('style', '');
$('.review-history.hidden').prop('style', '');
$('.history-container .hidden').prop('style', '');
render(document.querySelectorAll('time.timeago'));
$('.dev-review-reply-form').submit(function (e) {
e.preventDefault();
const $replyForm = $(e.target);
if ($replyForm.children('textarea').val() == '') {
return false;
}
let submitButton = $replyForm.children('button');
$.ajax({
type: 'POST',
url: $replyForm.attr('action'),
data: $replyForm.serialize(),
beforeSend: function (xhr) {
submitButton.prop('disabled', true);
let sessionId = $replyForm.data('session-id');
xhr.setRequestHeader('Authorization', 'Session ' + sessionId);
},
success: function (json) {
let historyDiv = $($replyForm.data('history'));
let container = historyDiv.children('.history-container');
addToReviewHistory([json], container, true);
$replyForm.children('textarea').val('');
},
complete: function () {
submitButton.prop('disabled', false);
},
dataType: 'json',
});
return false;
});
}
function initSubmit() {
let dl = $('body').attr('data-default-locale');
let el = format('#trans-name [lang="{0}"]', dl);
$(el).attr('id', 'id_name');
$('#submit-describe')
.on('keyup', el, slugify)
.on('blur', el, slugify)
.on('click', '#edit_slug', show_slug_edit)
.on('change', '#id_slug', function () {
$('#id_slug').attr('data-customized', 1);
let v = $('#id_slug').val();
if (!v) {
$('#id_slug').attr('data-customized', 0);
slugify();
}
})
.on('keyup blur', showNameSummaryCroppingWarnings);
$('#id_slug').each(slugify);
showNameSummaryCroppingWarnings();
reorderPreviews();
initSubmitModals();
$('.invisible-upload [disabled]').prop('disabled', false);
$('.invisible-upload .disabled').removeClass('disabled');
}
function showNameSummaryCroppingWarnings() {
let exceeds_max_length = false,
max_length = $('.edit-addon-details .char-count').data('maxlength'),
name_default_val = $('[name^="name_"]:visible').val(),
summary_default_val = $('[name^="summary_"]:visible').val(),
selectors =
'.combine-name-summary [name^="name_"]:hidden, .combine-name-summary [name^="summary_"]:hidden';
$(selectors).each(function (index, element) {
let locale = $(element).attr('lang'),
name_val = $('[name="name_' + locale + '"]').val() || name_default_val,
summary_val =
$('[name="summary_' + locale + '"]').val() || summary_default_val;
if (locale != 'init' && name_val.length + summary_val.length > max_length) {
exceeds_max_length = true;
return false;
}
});
$('#name-summary-locales-warning').toggle(exceeds_max_length);
}
function generateErrorList(o) {
let list = $("<ul class='errorlist'></ul>");
$.each(o, function (i, v) {
list.append($(format('<li>{0}</li>', v)));
});
return list;
}
function initEditVersions() {
if (noEdit) return;
$('#file-list').on('click', 'a.remove', function () {
let row = $(this).closest('tr');
$('input:first', row).prop('checked', true);
row.hide();
row.next().show();
});
$('#file-list').on('click', 'a.undo', function () {
let row = $(this).closest('tr').prev();
$('input:first', row).prop('checked', false);
row.show();
row.next().hide();
});
$('.show_file_history').click(
_pd(function () {
$(this)
.closest('p')
.hide()
.closest('div')
.find('.version-comments')
.fadeIn();
}),
);
}
function initCatFields(delegate) {
let $delegate = $(delegate || '#addon-categories-edit');
$delegate.find('div.addon-app-cats').each(function () {
let main_selector = '.addon-categories',
misc_selector = '.addon-misc-category';
let $parent = $(this);
let $grand_parent = $(this).closest('[data-max-categories]'),
$main = $parent.find(main_selector),
$misc = $parent.find(misc_selector),
maxCats = parseInt($grand_parent.attr('data-max-categories'), 10);
let checkMainDefault = function () {
let checkedLength = $('input:checked', $main).length,
disabled = checkedLength >= maxCats;
$('input:not(:checked)', $main).prop('disabled', disabled);
return checkedLength;
};
let checkMain = function () {
let checkedLength = checkMainDefault();
$('input', $misc).prop('checked', checkedLength <= 0);
};
let checkOther = function () {
$('input', $main).prop('checked', false).prop('disabled', false);
};
checkMainDefault();
$parent.on('change', main_selector + ' input', checkMain);
$parent.on('change', misc_selector + ' input', checkOther);
});
}
function initLicenseFields() {
$('#id_has_eula').change(function (e) {
if ($(this).prop('checked')) {
$('.eula').show().removeClass('hidden');
} else {
$('.eula').hide();
}
});
$('#id_has_priv').change(function (e) {
if ($(this).prop('checked')) {
$('.priv').show().removeClass('hidden');
} else {
$('.priv').hide();
}
});
let other_val = $('.license-other').attr('data-val');
$('.license').click(function (e) {
if ($(this).val() == other_val) {
$('.license-other').show().removeClass('hidden');
} else {
$('.license-other').hide();
}
});
}
function initAuthorFields() {
// Add the help line after the blank author row.
$('#author-roles-help').popup('#what-are-roles', {
pointTo: $('#what-are-roles'),
});
if (noEdit) return;
let request = false,
timeout = false,
empty_form = template(
$('#user-form-template')
.html()
.replace(/__prefix__/g, '{0}'),
),
authors = $('#authors_confirmed'),
authors_pending_confirmation = $('#authors_pending_confirmation');
authors.sortable({
items: '.author',
handle: '.handle',
containment: authors,
tolerance: 'pointer',
update: renumberAuthors,
});
addAuthorRow();
$('.author .errorlist').each(function () {
$(this)
.parent()
.find('.author-email')
.addClass('tooltip')
.addClass('invalid')
.addClass('formerror')
.attr('title', $(this).text());
});
authors.on('click', '.remove', function (e) {
e.preventDefault();
let tgt = $(this),
row = tgt.parents('li'),
manager = $('#id_user_form-TOTAL_FORMS');
if (authors.children('.author:visible').length > 1) {
if (row.hasClass('initial')) {
row.find('.delete input').prop('checked', true);
row.hide();
} else {
row.remove();
manager.val(authors.children('.author').length);
}
renumberAuthors();
}
});
authors_pending_confirmation
.on('keypress', '.author-email', validateUser)
.on('keyup', '.author-email', validateUser)
.on('click', '.remove', function (e) {
e.preventDefault();
let tgt = $(this),
row = tgt.parents('li'),
manager = $('#id_authors_pending_confirmation-TOTAL_FORMS');
if (row.hasClass('initial')) {
row.find('.delete input').prop('checked', true);
row.hide();
} else {
row.remove();
manager.val(authors.children('.author').length);
}
});
function validateUser(e) {
let tgt = $(this),
row = tgt.parents('li');
if (row.hasClass('blank')) {
tgt.removeClass('placeholder').attr('placeholder', undefined);
row.removeClass('blank').addClass('author');
// Now that we've added a user, if it was hidden we need to show
// the Authors pending confirmation section header.
authors_pending_confirmation
.parents('tr')
.find('th')
.removeClass('invisible');
addAuthorRow();
}
}
function renumberAuthors() {
authors.children('.author').each(function (i, el) {
$(this).find('.position input').val(i);
});
if ($('.author:visible').length > 1) {
authors.sortable('enable');
$('.author .remove').show();
$('.author .handle').css('visibility', 'visible');
} else {
authors.sortable('disable');
$('.author .remove').hide();
$('.author .handle').css('visibility', 'hidden');
}
}
function addAuthorRow() {
let numForms = authors_pending_confirmation.children('.author').length,
manager = $('#id_authors_pending_confirmation-TOTAL_FORMS');
authors_pending_confirmation.append(empty_form([numForms]));
manager.val(authors_pending_confirmation.children('.author').length);
}
}
function initCompatibility() {
$(document).on(
'click',
'p.add-app a',
_pd(function (e) {
let outer = $(this).closest('form');
$('tr.app-extra', outer).each(function () {
addAppRow(this);
});
$('.new-apps', outer).toggle();
$('.new-apps ul').on(
'click',
'a',
_pd(function (e) {
let $this = $(this),
sel = format('tr.app-extra td[class="{0}"]', [$this.attr('class')]),
$row = $(sel, outer);
$row
.parents('tr.app-extra')
.find('input:checkbox')
.prop('checked', false)
.closest('tr')
.removeClass('app-extra');
$this.closest('li').remove();
if (!$('tr.app-extra', outer).length) {
$('p.add-app', outer).hide();
}
}),
);
}),
);
$(document).on(
'click',
'.compat-versions .remove',
_pd(function (e) {
let $this = $(this),
$row = $this.closest('tr');
$row.addClass('app-extra');
if (!$row.hasClass('app-extra-orig')) {
$row.find('input:checkbox').prop('checked', true);
}
$('p.add-app:hidden', $this.closest('form')).show();
addAppRow($row);
}),
);
}
function imagePoller() {
this.start = function (override, delay) {
if (override || !this.poll) {
this.poll = window.setTimeout(this.check, delay || 1000);
}
};
this.stop = function () {
window.clearTimeout(this.poll);
this.poll = null;
};
}
let imageStatus = {
start: function () {
this.icon = new imagePoller();
this.preview = new imagePoller();
this.icon.check = function () {
let self = imageStatus,
node = $('#edit-addon-media');
$.getJSON(node.attr('data-checkurl'), function (json) {
if (json !== null && json.icons) {
$('#edit-addon-media')
.find('img')
.each(function () {
$(this).attr('src', self.newurl($(this).attr('src')));
});
self.icon.stop();
self.stopping();
} else {
self.icon.start(true, 2500);
self.polling();
}
});
};
this.preview.check = function () {
let self = imageStatus;
$('div.preview-thumb').each(function () {
check_images(this);
});
function check_images(el) {
let $this = $(el);
if ($this.hasClass('preview-successful')) {
return;
}
let img = new Image();
img.onload = function () {
$this
.removeClass('preview-error preview-unknown')
.addClass('preview-successful');
$this.attr(
'style',
'background-image:url(' + self.newurl($this.attr('data-url')) + ')',
);
if (!$('div.preview-error').length) {
self.preview.stop();
self.stopping();
}
};
img.onerror = function () {
setTimeout(function () {
check_images(el);
}, 2500);
self.polling();
$this.attr('style', '').addClass('preview-error');
img = null;
};
img.src = self.newurl($this.attr('data-url'));
}
};
this.icon.start();
this.preview.start();
},
polling: function () {
if (this.icon.poll || this.preview.poll) {
let node = $('#edit-addon-media');
if (!node.find('b.image-message').length) {
$(
format('<b class="save-badge image-message">{0}</b>', [
gettext('Image changes being processed'),
]),
).appendTo(node.find('h3').first());
}
}
},
newurl: function (orig) {
let bst = new Date().getTime();
orig += (orig.indexOf('?') > 1 ? '&' : '?') + bst;
return orig;
},
cancel: function () {
this.icon.stop();
this.preview.stop();
this.stopping();
},
stopping: function () {
if (!this.icon.poll && !this.preview.poll) {
$('#edit-addon-media').find('b.image-message').remove();
}
},
};
function hideSameSizedIcons() {
const icon_sizes = [];
$('#icon_preview_readonly img')
.show()
.each(function () {
const size = $(this).width() + 'x' + $(this).height();
if ($.inArray(size, icon_sizes) >= 0) {
$(this).hide();
}
icon_sizes.push(size);
});
}
function addAppRow(obj) {
let outer = $(obj).closest('form'),
appClass = $('td.app', obj).attr('class');
if (!$('.new-apps ul', outer).length) {
$('.new-apps', outer).html('<ul></ul>');
}
let sel = format('.new-apps ul a[class="{0}"]', [appClass]);
if (!$(sel, outer).length) {
// Append app to <ul> if it's not already listed.
let appLabel = $('td.app', obj).text(),
appHTML =
'<li><a href="#" class="' + appClass + '">' + appLabel + '</a></li>';
$('.new-apps ul', outer).append(appHTML);
}
}
function compatModalCallback(obj) {
let $widget = this,
ct = $(obj.click_target),
form_url = ct.attr('data-updateurl');
if ($widget.hasClass('ajax-loading')) return;
$widget.addClass('ajax-loading');
$widget.load(form_url, function (e) {
$widget.removeClass('ajax-loading');
});
$(document).on('submit', 'form.compat-versions', function (e) {
e.preventDefault();
$widget.empty();
if ($widget.hasClass('ajax-loading')) return;
$widget.addClass('ajax-loading');
let widgetForm = $(this);
$.post(widgetForm.attr('action'), widgetForm.serialize(), function (data) {
$widget.removeClass('ajax-loading');
if ($(data).find('.errorlist').length) {
$widget.html(data);
} else {
let c = $(
'.item[data-addonid=' +
widgetForm.attr('data-addonid') +
'] .item-actions li.compat',
);
c.load(c.attr('data-src'));
$widget.hideMe();
}
});
});
return { pointTo: ct };
}
function initCCLicense() {
function setCopyright(isCopyr) {
// Set the license options based on whether the copyright license is selected.
if (isCopyr) {
$('.noncc').addClass('disabled');
// Choose "No" and "No" for the "commercial" and "derivative" questions.
$(
'input[name="cc-noncom"][value=1], input[name="cc-noderiv"][value=2]',
).prop('checked', true);
} else {
$('.noncc').removeClass('disabled');
}
}
function setLicenseFromWizard() {
let cc_data = $('input[name^="cc-"]:checked')
.map(function () {
return this.dataset.cc;
})
.get();
let radio = $(
'#submit-describe #license-list input[type=radio][data-cc="' +
cc_data.join(' ') +
'"]',
);
if (radio.length) {
radio.prop('checked', true);
return radio;
}
cc_data.pop();
radio = $(
'#submit-describe #license-list input[type=radio][data-cc="' +
cc_data.join(' ') +
'"]',
);
if (radio.length) {
radio.prop('checked', true);
return radio;
}
cc_data.pop();
radio = $(
'#submit-describe #license-list input[type=radio][data-cc="' +
cc_data.join(' ') +
'"]',
);
radio.prop('checked', true);
return radio;
}
function setWizardFromLicense($license) {
// Update license wizard if license manually selected.
$('.noncc.disabled').removeClass('disabled');
$('input[name^="cc-"]').prop('checked', false);
$('input[name^="cc-"]:not([data-cc]').prop('checked', true);
_.each($license.data('cc').split(' '), function (cc) {
$('input[type=radio][name^="cc-"][data-cc="' + cc + '"]').prop(
'checked',
true,
);
setCopyright(cc == 'copyr');
});
}
function updateLicenseBox($license) {
if ($license.length) {
let licenseTxt = $license.data('name');
let url = $license.next('a');
if (url.length) {
licenseTxt = format(
'<a href="{0}">{1}</a>',
url.attr('href'),
licenseTxt,
);
}
let $p = $('#theme-license');
$p.show()
.find('#cc-license')
.html(licenseTxt)
.attr('class', 'license icon ' + $license.data('cc'));
}
}
function licenseChangeHandler() {
let $license = $(
'#submit-describe #license-list input[type=radio][name=license-builtin]:checked',
);
if ($license.length) {
setWizardFromLicense($license);
updateLicenseBox($license);
} else {
$('.noncc').addClass('disabled');
}
}
$('#submit-describe input[name="cc-attrib"]').change(function () {
setCopyright($('input[name="cc-attrib"]:checked').data('cc') == 'copyr');
});
$('#submit-describe input[name^="cc-"]').change(function () {
let $license = setLicenseFromWizard();
updateLicenseBox($license);
});
$(
'#submit-describe #license-list input[type=radio][name=license-builtin]',
).change(licenseChangeHandler);
$('#theme-license .select-license').click(
_pd(function () {
$('#license-list').toggle();
}),
);
licenseChangeHandler();
}
function initSourceSubmitOutcomes() {
$('#submit-source #id_has_source input')
.change(function () {
$('#option_no_source').hide();
$('#option_yes_source').hide();
$('#submit-source #id_has_source input').each(function (index, element) {
let $radio = $(element);
if ($radio.val() == 'yes' && $radio.prop('checked')) {
$('#option_yes_source').show();
$('#id_source').attr('required', true);
}
if ($radio.val() == 'no' && $radio.prop('checked')) {
$('#option_no_source').show();
$('#id_source').attr('required', null);
}
});
})
.change();
$('#submit-source').submit(function () {
// Drop the upload if 'no' is selected.
$('#submit-source #id_has_source input').each(function (index, element) {
let $radio = $(element);
if ($radio.val() == 'no' && $radio.prop('checked')) {
$('#id_source').val('');
}
});
});
$('#id_source').on('change', validateFileUploadSize);
}
function initSubmitModals() {
// Called during the submit addon step
// Hide the primary container of all modals
$('#modals').hide();
// Used by "Cancel and disable version" button during submission process
if ($('#modal-confirm-submission-cancel').length > 0) {
let $modalForm = $('#modal-confirm-submission-cancel'),
$modalDelete = $modalForm.modal('.confirm-submission-cancel', {
width: 400,
});
// Submitting the form in the modal is not useful. Instead, after
// receiving user confirmation, form that contains the
// "confirm-submission-cancel" button should POST to an alternate URL
$modalForm.find('form').on('submit', function onSubmit(e) {
e.preventDefault();
// this alternate URL is stored in this modal's submit button
// so change the form action attribute and submit it
let $confirmButton = $('.confirm-submission-cancel'),
$mainForm = $confirmButton.closest('form'),
cancelUrl = $confirmButton.attr('formaction');
$mainForm.attr('action', cancelUrl);
$mainForm.trigger('submit');
return false; // don't follow the a.href link
});
}
// Warn about Android compatibility (if selected).
if ($('#modal-confirm-android-compatibility').length > 0) {
let confirmedOnce = false;
let $input = $('#id_compatible_apps label.android input[type=checkbox]');
const $modalAndroidConfirm = $(
'#modal-confirm-android-compatibility',
).modal('#id_compatible_apps label.android', {
width: 525,
callback: function shouldShowAndroidModal(options) {
if ($input.prop('disabled')) {
return false;
}
if (confirmedOnce) {
setTimeout(function () {
// $().modal() calls preventDefault() before firing the callback
// but the checkbox is temporarily checked anyway when clicking on
// it (not the label). To work around this, we wrap our toggling
// in a setTimeout() to force it to wait for the event to be
// processed.
$input.prop('checked', !$input.prop('checked'));
$input.trigger('change');
}, 0);
}
return !confirmedOnce;
},
});
$('#modal-confirm-android-compatibility')
.find('form')
.on('submit', function onSubmit(e) {
e.preventDefault();
$input.prop('checked', true);
$input.trigger('change');
$modalAndroidConfirm.trigger('close');
confirmedOnce = true;
});
}
}
function initSurveyBanner() {
const banner = $('#dev-survey-banner');
const link = $('#dev-survey-banner .survey-link');
const dismiss = $('#dev-survey-banner .survey-dismiss');
const responseUrl = banner.attr('response-url');
link.add(dismiss).on('click', () => {
$.post(responseUrl);
banner.hide();
});
}