static/js/zamboni/reviewers.js (604 lines of code) (raw):
import $ from 'jquery';
import _ from 'underscore';
import { _pd } from '../lib/prevent-default';
import { Storage } from '../zamboni/storage';
import { b64toBlob } from './helpers';
import { validateFileUploadSize } from './global';
import { format } from '../lib/format';
import { z } from '../zamboni/z';
import { capabilities } from '../zamboni/capabilities';
$(document).ready(function () {
if ($('.daily-message').length) {
initDailyMessage();
}
let show_comments = function (e) {
e.preventDefault();
let $me = $(e.target);
$me.hide();
$me.next().show();
$me.parents('tr').next().show();
};
let hide_comments = function (e) {
e.preventDefault();
let $me = $(e.target);
$me.hide();
$me.prev().show();
$me.parents('tr').next().hide();
};
$('a.show').click(show_comments);
$('a.hide').click(hide_comments);
if ($('#review-actions').length > 0) {
initReviewActions();
}
if ($('#extra-review-actions').length) {
initExtraReviewActions();
}
if ($('.all-backgrounds').length) {
initBackgroundImagesForTheme();
}
if (
$('#scroll_sidebar').length &&
!$('body.mobile, body.tablet').length &&
!capabilities.mobile
) {
initScrollingSidebar();
}
if ($('#addon-queue').length) {
initQueue();
}
if ($('.version-adu').length > 0) {
initVersionsADU();
}
if ($('.review-held-action-form').length > 0) {
let comments_field = $('.review-held-action-form #id_comments');
let choices = $('.review-held-action-form #id_choice input');
choices.click(function () {
if ($('.review-held-action-form #id_choice input:checked').val() == 'cancel') {
comments_field.attr('disabled', false);
} else {
comments_field.attr('disabled', true);
}
})
}
// Show add-on ID when icon is clicked
if ($('#addon[data-id], #persona[data-id]').length) {
$('#addon .icon').click(function () {
window.location.hash = 'id=' + $('#addon, #persona').attr('data-id');
});
}
if ($('#addon-queue-filter-form').length) {
let filter_form = $('#addon-queue-filter-form form')[0];
$('#addon-queue-filter-form button').click(function () {
if (filter_form.hidden) {
filter_form.hidden = false;
} else {
filter_form.hidden = true;
}
});
if (
$('#addon-queue-filter-form input[type="checkbox"]').length ==
$('#addon-queue-filter-form input[type="checkbox"]:checked').length
) {
filter_form.hidden = true;
}
$('#addon-queue-filter-form .select-all').click(function (e) {
e.preventDefault();
$('#addon-queue-filter-form input[type="checkbox"]').prop(
'checked',
true,
);
});
$('#addon-queue-filter-form .select-none').click(function (e) {
e.preventDefault();
$('#addon-queue-filter-form input[type="checkbox"]').prop(
'checked',
false,
);
});
}
});
function initReviewActions() {
function showForm(element, pageload) {
let $element = $(element),
value = $element.find('input').val(),
$data_toggle = $('form.review-form').find('.data-toggle'),
$data_toggle_hide = $('form.review-form').find('.data-toggle-hide'),
$comments = $('#id_comments'),
boilerplate_text = $element.find('input').attr('data-value');
pageload = pageload || false;
$element.closest('.review-actions').addClass('on');
$('.review-actions .action_nav #id_action > *').removeClass('on-tab');
$element.find('input').prop('checked', true);
// Input message into empty comments textbox.
if ($comments.val().length === 0 && boilerplate_text) {
insertAtCursor($comments, boilerplate_text);
}
$element.addClass('on-tab');
if (pageload) {
$('#review-actions-form').show();
} else {
$('#review-actions-form').slideDown();
$('#review-actions').find('.errorlist').remove();
}
// Hide everything, then show the ones containing the value we're interested in.
$data_toggle.hide().prop('disabled', true);
$data_toggle.parent('label').hide();
$data_toggle
.filter('[data-value~="' + value + '"]')
.show()
.prop('disabled', false);
$data_toggle
.filter('[data-value~="' + value + '"]')
.parent('label')
.show();
// For data_toggle_hide, the opposite - show everything, then hide the ones containing
// the value we're interested in.
$data_toggle_hide.show().prop('disabled', false);
$data_toggle_hide.parent('label').show();
$data_toggle_hide
.filter('[data-value~="' + value + '"]')
.hide()
.prop('disabled', true);
$data_toggle_hide
.filter('[data-value~="' + value + '"]')
.parent('label')
.hide();
showHideDelayedRejectionDateWidget();
}
function showHideDelayedRejectionDateWidget() {
var delayed_rejection_input = $(
'#id_delayed_rejection input[name=delayed_rejection]:checked',
);
console.log(delayed_rejection_input);
var delayed_rejection_date_widget = $('#id_delayed_rejection_date');
if (delayed_rejection_input.prop('value') == 'True') {
delayed_rejection_date_widget.prop('disabled', false);
} else {
delayed_rejection_date_widget.prop('disabled', true);
}
}
$('#id_delayed_rejection input[name=delayed_rejection]').change(
showHideDelayedRejectionDateWidget,
);
$('#review-actions .action_nav #id_action > *:not(.disabled)').click(
function () {
showForm(this);
},
);
let review_checked = $('#review-actions [name=action]:checked');
if (review_checked.length > 0) {
showForm(review_checked.parentsUntil('#id_action', 'div'), true);
}
/* Review action reason stuff */
$('.review-actions-reasons-select input').change(function () {
const cannedResponse = this.dataset.value;
if (cannedResponse.length) {
if (this.checked) {
insertAtCursor($('#id_comments'), cannedResponse);
} else {
// Attempt to remove the canned response related to the reason.
$('#id_comments').val(
$('#id_comments').val().replace(cannedResponse, ''),
);
}
}
});
/* Install Triggers */
// IT IS TOTALLY UNCLEAR WHERE THESE VALUES ARE SET.
$('.files .install').click(
_pd(function () {
let $this = $(this),
installer = $this.is('[data-type="search-tools"]')
? z.installSearch
: z.installAddon;
installer($this.text(), $this.attr('href'), '');
}),
);
/* Who's currently on this page? */
function check_currently_viewing() {
const addon_id = $('#addon').data('id');
const url = $('#addon').data('url');
const $current = $('.currently_viewing_warning');
const updateWarning = (title) => {
let $current_div = $current.filter('div');
$current_div.find('strong').remove();
$current_div.prepend($('<strong>', { text: title }));
};
$.post(url, { addon_id: addon_id }, (d) => {
const show = d.is_user != 1 && typeof d.current_name != 'undefined';
$current.toggle(show);
if (show) {
let title;
if (d.is_user == 2) {
/* 2 is when the editor has reached the lock limit */
title = d.current_name;
} else {
title = format(gettext('{name} was viewing this page first.'), {
name: d.current_name,
});
}
updateWarning(title);
}
}).fail(() => {
$current.toggle(true);
updateWarning(gettext('Review page polling failed.'));
});
}
if (!(capabilities.localStorage && window.localStorage.dont_poll)) {
check_currently_viewing();
const interval = $('#addon').data('review-viewing-interval');
setInterval(check_currently_viewing, interval * 1000);
}
/* Item History */
$('.review-files tr.listing-header').click(function () {
$(this).next('tr.listing-body').toggle();
});
let storage = Storage(),
eh_setting = storage.get('reviewers_history'),
eh_els = $('#history .review-files tr.listing-body'),
eh_size = eh_els.length;
if (!eh_setting) eh_setting = 3;
toggleHistory();
function toggleHistory() {
eh_els.slice(eh_size - eh_setting, eh_setting).show();
eh_els.slice(0, eh_size - eh_setting).hide();
}
$('.eh_open').click(
_pd(function () {
eh_setting = $(this).attr('data-num');
storage.set('reviewers_history', eh_setting);
toggleHistory();
highlightHistory();
}),
);
function highlightHistory() {
$('#history a').removeClass('on');
$(format('#history a[data-num="{0}"]', eh_setting)).addClass('on');
}
highlightHistory();
}
function callReviewersAPI(apiUrl, method, data, successCallback) {
let sessionId = $('form.more-actions').data('session-id');
if (data) {
data = JSON.stringify(data);
}
$.ajax({
url: apiUrl,
data: data,
type: method,
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Session ' + sessionId);
},
processData: false,
contentType: 'application/json',
success: successCallback,
});
}
$('#id_attachment_file').on('change', validateFileUploadSize);
function initExtraReviewActions() {
/* Inline actions that should trigger a XHR and modify the form element
* accordingly.
*/
// Checkbox-style actions. Only for subscribe/unsubscribe.
$('#notify_new_listed_versions').click(
_pd(function () {
let $input = $(this).prop('disabled', true); // Prevent double-send.
let checked = !$input.prop('checked'); // It's already changed.
let apiUrl;
if (checked) {
apiUrl = $input.data('api-url-unsubscribe-listed');
} else {
apiUrl = $input.data('api-url-subscribe-listed');
}
callReviewersAPI(apiUrl, 'post', null, function () {
$input.prop('disabled', false);
$input.prop('checked', !checked);
});
}),
);
$('#notify_new_unlisted_versions').click(
_pd(function () {
let $input = $(this).prop('disabled', true); // Prevent double-send.
let checked = !$input.prop('checked'); // It's already changed.
let apiUrl;
if (checked) {
apiUrl = $input.data('api-url-unsubscribe-unlisted');
} else {
apiUrl = $input.data('api-url-subscribe-unlisted');
}
callReviewersAPI(apiUrl, 'post', null, function () {
$input.prop('disabled', false);
$input.prop('checked', !checked);
});
}),
);
$('#due_date_update').on(
'change',
_pd(function () {
$('#submit_due_date_update').removeClass('disabled');
}),
);
$('#submit_due_date_update').on(
'click',
_pd(function () {
$(this).addClass('disabled');
let $input = $('#due_date_update').prop('disabled', true); // Prevent double-send.
let apiUrl = $input.data('api-url');
let data = { due_date: $input.val(), version: $input.data('api-data') };
callReviewersAPI(apiUrl, 'post', data, function (response) {
$input.prop('disabled', false);
});
}),
);
const showToggleWrapper = () => {
$(
'#attachment_input_wrapper, #attachment_file_wrapper, #attachment_back',
).addClass('hidden');
$('#attachment-type-toggle').removeClass('hidden');
$('#id_attachment_file, #id_attachment_input').val('');
};
const showFileWrapper = (e) => {
e?.preventDefault();
$('#attachment-type-toggle, #attachment_input_wrapper').addClass('hidden');
$('#attachment_file_wrapper, #attachment_back').removeClass('hidden');
};
const showInputWrapper = (e) => {
e?.preventDefault();
$('#attachment-type-toggle, #attachment_file_wrapper').addClass('hidden');
$('#attachment_input_wrapper, #attachment_back').removeClass('hidden');
};
$('#id_attachment_file').prop('files')?.length && showFileWrapper();
$('#id_attachment_input').val() && showInputWrapper();
$('#attachment_back').on('click', showToggleWrapper);
$('#toggle_attachment_file').on('click', showFileWrapper);
$('#toggle_attachment_input').on('click', showInputWrapper);
// One-off-style buttons.
$('.more-actions button.oneoff[data-api-url]').click(
_pd(function () {
let $button = $(this).prop('disabled', true); // Prevent double-send.
let apiUrl = $button.data('api-url');
let data = $button.data('api-data') || null;
let method = $button.data('api-method') || 'post';
callReviewersAPI(apiUrl, method, data, function (response) {
$button.remove();
});
}),
);
// Toggle-style buttons.
$('.more-actions button.toggle[data-api-url]').click(
_pd(function () {
let $button = $(this).prop('disabled', true); // Prevent double-send.
let $other_button = $($button.data('toggle-button-selector'));
let apiUrl = $button.data('api-url');
let data = $button.data('api-data') || null;
let method = $button.data('api-method') || 'post';
callReviewersAPI(apiUrl, method, data, function () {
$button.prop('disabled', false).parents('li').addClass('hidden').hide();
$other_button.parents('li').removeClass('hidden').show();
});
}),
);
}
function initBackgroundImagesForTheme() {
function rollOverInit(e) {
if (!e.target.complete) return;
let $target = $(e.target);
$target.attr('height', e.target.naturalHeight);
$target.attr('width', e.target.naturalWidth);
$target.parent().zoomBox();
}
function loadBackgroundImages($parent_element) {
let url = $parent_element.data('backgrounds-url');
if (!url) return;
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
// load the image as a blob so we can treat it as a File
xhr.onload = function () {
const jsonResponse = xhr.response,
loop_len = Object.keys(jsonResponse).length;
let loop_count = 0;
$.each(jsonResponse, function (background_filename, background_b64) {
loop_count++;
let blob = b64toBlob(background_b64),
imageUrl = window.URL.createObjectURL(blob);
let $div_element = $('<div>')
.addClass('background zoombox')
.appendTo($parent_element);
let $img_element = $('<img>')
.attr('src', imageUrl)
.attr('width', '1000')
.attr('height', '200')
.appendTo($div_element);
let span_content = document.createTextNode(
format('Background file {0} of {1} - {2}', [
loop_count,
loop_len,
background_filename,
]),
);
$('<span>').append(span_content).appendTo($div_element);
$img_element.on('load', rollOverInit).trigger('load');
});
};
xhr.send();
}
loadBackgroundImages($('div.all-backgrounds'));
}
function insertAtCursor(textarea, text) {
let area = $(textarea)[0],
scrollPos = area.scrollTop;
// IE
if (document.selection) {
area.focus();
let rng = document.selection.createRange();
rng.text = text + rng.text;
// FF/Safari/Chrome
} else if (area.selectionStart || area.selectionStart == '0') {
area.focus();
let startPos = area.selectionStart;
area.value =
area.value.substring(0, startPos) +
text +
area.value.substring(startPos, area.value.length);
area.setSelectionRange(startPos + text.length, startPos + text.length);
// everything else - append text to end
} else {
area.value += text;
}
// restore scrollbar location
area.scrollTop = scrollPos;
}
function initDailyMessage(doc) {
let $motd = $('.daily-message', doc),
storage = Storage();
if ($('#editor-motd', doc).length) {
// The message on the MOTD page should never be closable, so don't
// show close button nor attach handlers.
return;
}
$motd.find('.close').show();
if (storage.get('motd_closed') != $('p', $motd).text()) {
// You haven't read this spam yet? Here, I have something to show you.
$motd.show();
}
$motd.find('.close').click(function (e) {
e.stopPropagation();
storage.set('motd_closed', $('.daily-message p').text());
$motd.slideUp();
});
}
function initQueue() {
let $q = $('#addon-queue[data-url]');
if (!$q.length) {
return;
}
let url = $q.attr('data-url');
let addon_ids = $.map($('.addon-row'), function (el) {
return $(el).attr('data-addon');
});
if (!('localStorage' in window && window.localStorage.dont_poll)) {
(function checkCurrentlyViewing() {
$.get(url, { addon_ids: addon_ids.join(',') }, function (data) {
$('#addon-queue .locked').removeClass('locked').prop('title', '');
$.each(data, function (k, v) {
$('#addon-' + k)
.addClass('locked')
.attr(
'title',
format(gettext('{name} was viewing this add-on first.'), {
name: v,
}),
);
});
setTimeout(checkCurrentlyViewing, 2000);
});
})();
}
let pop = $('#popup-notes').hide(),
loadNotes = function (e) {
let addon_id = $(e.click_target).closest('tr').attr('data-addon');
pop.html(gettext('Loading…'));
$.get(pop.attr('data-version-url') + addon_id, function (data) {
pop.html('');
let empty = true;
if (data.release_notes) {
pop.append($('<strong>', { text: gettext('Version Notes') }));
pop.append(
$('<div>', { class: 'version-notes', text: data.release_notes }),
);
empty = false;
}
if (data.approval_notes) {
pop.append($('<strong>', { text: gettext('Notes for Reviewers') }));
pop.append(
$('<div>', { class: 'version-notes', text: data.approval_notes }),
);
empty = false;
}
if (empty) {
pop.append($('<em>', { text: gettext('No version notes found') }));
}
});
return true;
},
loadReview = function (e) {
let addon_id = $(e.click_target).closest('tr').attr('data-review-log');
pop.html(gettext('Loading…'));
$.get(pop.attr('data-review-url') + addon_id, function (data) {
pop.html('');
let empty = true;
if (data.reviewtext) {
pop.append($('<strong>', { text: gettext('Review Text') }));
pop.append(
$('<div>', { class: 'version-notes', text: data.reviewtext }),
);
empty = false;
}
if (empty) {
pop.append($('<em>', { text: gettext('Review notes found') }));
}
});
return true;
};
$('.addon-version-notes a').each(function (i, el) {
$(pop).popup(el, { pointTo: el, callback: loadNotes, width: 500 });
});
$('.addon-review-text a').each(function (i, el) {
$(pop).popup(el, { pointTo: el, callback: loadReview, width: 500 });
});
}
function initScrollingSidebar() {
let $window = $(window),
$sb = $('#scroll_sidebar'),
addon_top = $('#addon').offset().top,
current_state = false;
function setSticky(state) {
if (state == current_state) return;
current_state = state;
$sb.toggleClass('sticky', state);
}
$window.scroll(
_.throttle(function () {
setSticky(window.scrollY > addon_top);
}, 20),
);
}
function initVersionsADU() {
function fillVersionsTable(versionAduPairs) {
versionAduPairs.forEach(([version, adu]) => {
$(
format(
'.version-adu[data-version-string="{0}"] .version-adu-value',
version,
),
).text(adu);
});
let missingAduText;
const queryLimit = $('#addon').data('versions-adu-max-results');
if (versionAduPairs.length === queryLimit) {
// if we've got max results we may have hit the limit of the query
const [_, min_adu] = versionAduPairs[queryLimit - 1];
missingAduText = format('<= {0}', min_adu);
} else {
// otherwise these are just 0 ADU versions
missingAduText = '0';
}
$('.version-adu-value:contains("\u2014")').text(missingAduText);
}
function fillTopTenBox(versionAduPairs) {
const review_version_url = $('#addon').data('review-version-url');
versionAduPairs.slice(0, 10).forEach(([version, adu]) => {
const versionEntryId = '#version-' + version.replaceAll('.', '_');
let versionLink;
if ($(versionEntryId).length) {
versionLink = format(
'<a href="{0}">\u2B07 {1}</a>',
versionEntryId,
version,
);
} else {
versionLink = format(
'<a href="{0}">\u2794 {1}</a>',
review_version_url.replace('__', version),
version,
);
}
$('#version-adu-top-ten ol').append(
format('<li>{0}: {1}</li>', versionLink, adu),
);
});
if (!versionAduPairs.length) {
$('#version-adu-top-ten div').append(
'No average daily user values found.',
);
}
}
function loadVersionsADU() {
const aduUrl = $('#addon').data('versions-adu-url');
$.get(aduUrl, function (data) {
const versionAduPairs = data.adus;
if (versionAduPairs !== undefined) {
fillVersionsTable(versionAduPairs);
fillTopTenBox(versionAduPairs);
} else {
$('#version-adu-top-ten div').append(
'No average daily user values because BigQuery disabled.',
);
}
});
}
loadVersionsADU();
}