e12f88f1d6
This has been broken for a long time; looks like it happened when we switched over to using our standard details tag and no longer emit a <span/> element unconditionally. Signed-off-by: Dan McGee <dan@archlinux.org>
558 lines
19 KiB
JavaScript
558 lines
19 KiB
JavaScript
/*'use strict';*/
|
|
/* tablesorter custom parsers for various pages:
|
|
* devel/index.html, mirrors/status.html, todolists/view.html */
|
|
if (typeof $ !== 'undefined' && typeof $.tablesorter !== 'undefined') {
|
|
$.tablesorter.addParser({
|
|
id: 'pkgcount',
|
|
is: function(s) { return false; },
|
|
format: function(s) {
|
|
var m = s.match(/\d+/);
|
|
return m ? parseInt(m[0], 10) : 0;
|
|
},
|
|
type: 'numeric'
|
|
});
|
|
|
|
$.tablesorter.addParser({
|
|
id: 'todostatus',
|
|
is: function(s) { return false; },
|
|
format: function(s) {
|
|
if (s.match(/incomplete/i)) {
|
|
return 1;
|
|
} else if (s.match(/in-progress/i)) {
|
|
return 0.5;
|
|
}
|
|
return 0;
|
|
},
|
|
type: 'numeric'
|
|
});
|
|
|
|
$.tablesorter.addParser({
|
|
/* sorts numeric, but put '', 'unknown', and '∞' last. */
|
|
id: 'mostlydigit',
|
|
special: ['', 'unknown', '∞'],
|
|
is: function(s, table) {
|
|
var c = table.config;
|
|
return ($.inArray(s, this.special) > -1) || $.tablesorter.isDigit(s, c);
|
|
},
|
|
format: function(s, t) {
|
|
if ($.inArray(s, this.special) > -1) {
|
|
return Number.MAX_VALUE;
|
|
}
|
|
return $.tablesorter.formatFloat(s, t);
|
|
},
|
|
type: 'numeric'
|
|
});
|
|
|
|
$.tablesorter.addParser({
|
|
/* sorts duration; put '', 'unknown', and '∞' last. */
|
|
id: 'duration',
|
|
re: /^([0-9]+):([0-5][0-9])$/,
|
|
special: ['', 'unknown', '∞'],
|
|
is: function(s) {
|
|
return ($.inArray(s, this.special) > -1) || this.re.test(s);
|
|
},
|
|
format: function(s) {
|
|
if ($.inArray(s, this.special) > -1) {
|
|
return Number.MAX_VALUE;
|
|
}
|
|
var matches = this.re.exec(s);
|
|
if (!matches) {
|
|
return Number.MAX_VALUE;
|
|
}
|
|
return matches[1] * 60 + matches[2];
|
|
},
|
|
type: 'numeric'
|
|
});
|
|
$.tablesorter.addParser({
|
|
id: 'epochdate',
|
|
is: function(s) { return false; },
|
|
format: function(s, t, c) {
|
|
/* TODO: this assumes our magic class is the only one */
|
|
var epoch = $(c).attr('class');
|
|
if (epoch.indexOf('epoch-') !== 0) {
|
|
return 0;
|
|
}
|
|
return epoch.slice(6);
|
|
},
|
|
type: 'numeric'
|
|
});
|
|
|
|
$.tablesorter.addParser({
|
|
id: 'longDateTime',
|
|
re: /^(\d{4})-(\d{2})-(\d{2}) ([012]\d):([0-5]\d)(:([0-5]\d))?( (\w+))?$/,
|
|
is: function(s) {
|
|
return this.re.test(s);
|
|
},
|
|
format: function(s, t) {
|
|
var matches = this.re.exec(s);
|
|
if (!matches) {
|
|
return 0;
|
|
}
|
|
/* skip group 6, group 7 is optional seconds */
|
|
if (matches[7] === undefined) {
|
|
matches[7] = 0;
|
|
}
|
|
/* The awesomeness of the JS date constructor. Month needs to be
|
|
* between 0-11, because things have to be difficult. */
|
|
var date = new Date(matches[1], matches[2] - 1, matches[3],
|
|
matches[4], matches[5], matches[7]);
|
|
return $.tablesorter.formatFloat(date.getTime(), t);
|
|
},
|
|
type: 'numeric'
|
|
});
|
|
|
|
$.tablesorter.addParser({
|
|
id: 'filesize',
|
|
re: /^(\d+(?:\.\d+)?)[ \u00a0](bytes?|[KMGTPEZY]i?B)$/,
|
|
is: function(s) {
|
|
return this.re.test(s);
|
|
},
|
|
format: function(s) {
|
|
var matches = this.re.exec(s);
|
|
if (!matches) {
|
|
return 0;
|
|
}
|
|
var size = parseFloat(matches[1]),
|
|
suffix = matches[2];
|
|
|
|
switch(suffix) {
|
|
/* intentional fall-through at each level */
|
|
case 'YB':
|
|
case 'YiB':
|
|
size *= 1024;
|
|
case 'ZB':
|
|
case 'ZiB':
|
|
size *= 1024;
|
|
case 'EB':
|
|
case 'EiB':
|
|
size *= 1024;
|
|
case 'PB':
|
|
case 'PiB':
|
|
size *= 1024;
|
|
case 'TB':
|
|
case 'TiB':
|
|
size *= 1024;
|
|
case 'GB':
|
|
case 'GiB':
|
|
size *= 1024;
|
|
case 'MB':
|
|
case 'MiB':
|
|
size *= 1024;
|
|
case 'KB':
|
|
case 'KiB':
|
|
size *= 1024;
|
|
}
|
|
return size;
|
|
},
|
|
type: 'numeric'
|
|
});
|
|
|
|
$.tablesorter.removeParser = function(id) {
|
|
$.tablesorter.parsers = $.grep($.tablesorter.parsers,
|
|
function(ele, i) {
|
|
return ele.id !== id;
|
|
});
|
|
};
|
|
|
|
// We don't use currency, and the parser is over-zealous at deciding it
|
|
// matches. Kill it from the parser selection.
|
|
$.tablesorter.removeParser('currency');
|
|
}
|
|
|
|
(function($) {
|
|
$.fn.enableCheckboxRangeSelection = function() {
|
|
var lastCheckbox = null,
|
|
spec = this;
|
|
|
|
spec.unbind("click.checkboxrange");
|
|
spec.bind("click.checkboxrange", function(e) {
|
|
if (lastCheckbox !== null && e.shiftKey) {
|
|
spec.slice(
|
|
Math.min(spec.index(lastCheckbox), spec.index(e.target)),
|
|
Math.max(spec.index(lastCheckbox), spec.index(e.target)) + 1
|
|
).attr({checked: e.target.checked ? "checked" : ""});
|
|
}
|
|
lastCheckbox = e.target;
|
|
});
|
|
|
|
};
|
|
})(jQuery);
|
|
|
|
/* news/add.html */
|
|
function enablePreview() {
|
|
$('#news-preview-button').click(function(event) {
|
|
event.preventDefault();
|
|
$.post('/news/preview/', {
|
|
data: $('#id_content').val(),
|
|
csrfmiddlewaretoken: $('#newsform input[name=csrfmiddlewaretoken]').val()
|
|
},
|
|
function(data) {
|
|
$('#news-preview-data').html(data);
|
|
$('#news-preview').show();
|
|
}
|
|
);
|
|
$('#news-preview-title').html($('#id_title').val());
|
|
});
|
|
}
|
|
|
|
/* packages/details.html */
|
|
function ajaxifyFiles() {
|
|
$('#filelink').click(function(event) {
|
|
event.preventDefault();
|
|
$.getJSON(this.href + 'json/', function(data) {
|
|
// Map each file item into an <li/> with the correct class
|
|
var list_items = $.map(data.files, function(value, i) {
|
|
var cls = value.match(/\/$/) ? 'd' : 'f';
|
|
return ['<li class="', cls, '">', value, '</li>'];
|
|
});
|
|
$('#pkgfilelist').empty();
|
|
if (data.pkg_last_update > data.files_last_update) {
|
|
$('#pkgfilelist').append('<p class="message">Note: This file list was generated from a previous version of the package; it may be out of date.</p>');
|
|
}
|
|
if (list_items.length > 0) {
|
|
$('#pkgfilelist').append('<ul>' + list_items.join('') + '</ul>');
|
|
} else if (data.files_last_update === null) {
|
|
$('#pkgfilelist').append('<p class="message">No file list available.</p>');
|
|
} else {
|
|
$('#pkgfilelist').append('<p class="message">Package has no files.</p>');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function collapseDependsList(list) {
|
|
list = $(list);
|
|
// Hide everything past a given limit. Don't do anything if we don't have
|
|
// enough items, or the link already exists.
|
|
var limit = 20,
|
|
linkid = list.attr('id') + 'link',
|
|
items = list.find('li').slice(limit);
|
|
if (items.length <= 1 || $('#' + linkid).length > 0) {
|
|
return;
|
|
}
|
|
items.hide();
|
|
list.after('<p><a id="' + linkid + '" href="#">Show More…</a></p>');
|
|
|
|
// add link and wire it up to show the hidden items
|
|
$('#' + linkid).click(function(event) {
|
|
event.preventDefault();
|
|
list.find('li').show();
|
|
// remove the full <p/> node from the DOM
|
|
$(this).parent().remove();
|
|
});
|
|
}
|
|
|
|
function collapseRelatedTo(elements) {
|
|
var limit = 5;
|
|
$(elements).each(function(idx, ele) {
|
|
ele = $(ele);
|
|
// Hide everything past a given limit. Don't do anything if we don't
|
|
// have enough items, or the link already exists.
|
|
var items = ele.find('span.related').slice(limit);
|
|
if (items.length <= 1 || ele.find('a.morelink').length > 0) {
|
|
return;
|
|
}
|
|
items.hide();
|
|
ele.append('<a class="morelink" href="#">More…</a>');
|
|
|
|
// add link and wire it up to show the hidden items
|
|
ele.find('a.morelink').click(function(event) {
|
|
event.preventDefault();
|
|
ele.find('span.related').show();
|
|
$(this).remove();
|
|
});
|
|
});
|
|
}
|
|
|
|
/* packages/differences.html */
|
|
function filter_packages() {
|
|
/* start with all rows, and then remove ones we shouldn't show */
|
|
var rows = $('#tbody_differences').children(),
|
|
all_rows = rows;
|
|
if (!$('#id_multilib').is(':checked')) {
|
|
rows = rows.not('.multilib').not('.multilib-testing');
|
|
}
|
|
var arch = $('#id_archonly').val();
|
|
if (arch !== 'all') {
|
|
rows = rows.filter('.' + arch);
|
|
}
|
|
if (!$('#id_minor').is(':checked')) {
|
|
/* this check is done last because it is the most expensive */
|
|
var pat = /(.*)-(.+)/;
|
|
rows = rows.filter(function(index) {
|
|
var cells = $(this).children('td');
|
|
|
|
/* all this just to get the split version out of the table cell */
|
|
var ver_a = cells.eq(2).text().match(pat);
|
|
if (!ver_a) {
|
|
return true;
|
|
}
|
|
|
|
var ver_b = cells.eq(3).text().match(pat);
|
|
if (!ver_b) {
|
|
return true;
|
|
}
|
|
|
|
/* first check pkgver */
|
|
if (ver_a[1] !== ver_b[1]) {
|
|
return true;
|
|
}
|
|
/* pkgver matched, so see if rounded pkgrel matches */
|
|
if (Math.floor(parseFloat(ver_a[2])) ===
|
|
Math.floor(parseFloat(ver_b[2]))) {
|
|
return false;
|
|
}
|
|
/* pkgrel didn't match, so keep the row */
|
|
return true;
|
|
});
|
|
}
|
|
/* hide all rows, then show the set we care about */
|
|
all_rows.hide();
|
|
rows.show();
|
|
/* make sure we update the odd/even styling from sorting */
|
|
$('.results').trigger('applyWidgets', [false]);
|
|
}
|
|
function filter_packages_reset() {
|
|
$('#id_archonly').val('both');
|
|
$('#id_multilib').removeAttr('checked');
|
|
$('#id_minor').removeAttr('checked');
|
|
filter_packages();
|
|
}
|
|
|
|
/* todolists/view.html */
|
|
function todolist_flag() {
|
|
// TODO: fix usage of this
|
|
var link = this;
|
|
$.getJSON(link.href, function(data) {
|
|
$(link).text(data.status).removeClass(
|
|
'complete inprogress incomplete').addClass(
|
|
data.css_class.toLowerCase());
|
|
/* let tablesorter know the cell value has changed */
|
|
$('.results').trigger('updateCell', [$(link).closest('td')[0], false, null]);
|
|
});
|
|
return false;
|
|
}
|
|
|
|
function filter_pkgs_list(filter_ele, tbody_ele) {
|
|
/* start with all rows, and then remove ones we shouldn't show */
|
|
var rows = $(tbody_ele).children(),
|
|
all_rows = rows;
|
|
/* apply the filters, cheaper ones first */
|
|
if ($('#id_mine_only').is(':checked')) {
|
|
rows = rows.filter('.mine');
|
|
}
|
|
/* apply arch and repo filters */
|
|
$(filter_ele + ' .arch_filter').add(
|
|
filter_ele + ' .repo_filter').each(function() {
|
|
if (!$(this).is(':checked')) {
|
|
rows = rows.not('.' + $(this).val());
|
|
}
|
|
});
|
|
/* more expensive filter because of 'has' call */
|
|
if ($('#id_incomplete').is(':checked')) {
|
|
rows = rows.has('.incomplete');
|
|
}
|
|
/* hide all rows, then show the set we care about */
|
|
all_rows.hide();
|
|
rows.show();
|
|
$('#filter-count').text(rows.length);
|
|
/* make sure we update the odd/even styling from sorting */
|
|
$('.results').trigger('applyWidgets', [false]);
|
|
}
|
|
function filter_pkgs_reset(callback) {
|
|
$('#id_incomplete').removeAttr('checked');
|
|
$('#id_mine_only').removeAttr('checked');
|
|
$('.arch_filter').attr('checked', 'checked');
|
|
$('.repo_filter').attr('checked', 'checked');
|
|
callback();
|
|
}
|
|
|
|
function filter_todolist_save(list_id) {
|
|
var state = $('#todolist_filter').serializeArray();
|
|
localStorage['filter_todolist_' + list_id] = JSON.stringify(state);
|
|
}
|
|
function filter_todolist_load(list_id) {
|
|
var state = localStorage['filter_todolist_' + list_id];
|
|
if (!state)
|
|
return;
|
|
state = JSON.parse(state);
|
|
$('#todolist_filter input[type="checkbox"]').removeAttr('checked');
|
|
$.each(state, function (i, v) {
|
|
// this assumes our only filters are checkboxes
|
|
$('#todolist_filter input[name="' + v['name'] + '"]').attr('checked', 'checked');
|
|
});
|
|
}
|
|
|
|
function filter_report_save(report_id) {
|
|
var state = $('#report_filter').serializeArray();
|
|
localStorage['filter_report_' + report_id] = JSON.stringify(state);
|
|
}
|
|
function filter_report_load(report_id) {
|
|
var state = localStorage['filter_report_' + report_id];
|
|
if (!state)
|
|
return;
|
|
state = JSON.parse(state);
|
|
$('#report_filter input[type="checkbox"]').removeAttr('checked');
|
|
$.each(state, function (i, v) {
|
|
// this assumes our only filters are checkboxes
|
|
$('#report_filter input[name="' + v['name'] + '"]').attr('checked', 'checked');
|
|
});
|
|
}
|
|
|
|
/* signoffs.html */
|
|
function signoff_package() {
|
|
// TODO: fix usage of this
|
|
var link = this;
|
|
$.getJSON(link.href, function(data) {
|
|
link = $(link);
|
|
var signoff = null,
|
|
cell = link.closest('td');
|
|
if (data.created) {
|
|
signoff = $('<li>').addClass('signed-username').text(data.user);
|
|
var list = cell.children('ul.signoff-list');
|
|
if (list.size() === 0) {
|
|
list = $('<ul class="signoff-list">').prependTo(cell);
|
|
}
|
|
list.append(signoff);
|
|
} else if(data.user) {
|
|
signoff = link.closest('td').find('li').filter(function(index) {
|
|
return $(this).text() == data.user;
|
|
});
|
|
}
|
|
if (signoff && data.revoked) {
|
|
signoff.text(signoff.text() + ' (revoked)');
|
|
}
|
|
/* update the approved column to reflect reality */
|
|
var approved = link.closest('tr').children('.approval');
|
|
approved.attr('class', 'approval');
|
|
if (data.known_bad) {
|
|
approved.text('Bad').addClass('signoff-bad');
|
|
} else if (!data.enabled) {
|
|
approved.text('Disabled').addClass('signoff-disabled');
|
|
} else if (data.approved) {
|
|
approved.text('Yes').addClass('signoff-yes');
|
|
} else {
|
|
approved.text('No').addClass('signoff-no');
|
|
}
|
|
link.removeAttr('title');
|
|
/* Form our new link. The current will be something like
|
|
* '/packages/repo/arch/package/...' */
|
|
var base_href = link.attr('href').split('/').slice(0, 5).join('/');
|
|
if (data.revoked) {
|
|
link.text('Signoff');
|
|
link.attr('href', base_href + '/signoff/');
|
|
/* should we be hiding the link? */
|
|
if (data.known_bad || !data.enabled) {
|
|
link.remove();
|
|
}
|
|
} else {
|
|
link.text('Revoke Signoff');
|
|
link.attr('href', base_href + '/signoff/revoke/');
|
|
}
|
|
/* let tablesorter know the cell value has changed */
|
|
$('.results').trigger('updateCell', [approved[0], false, null]);
|
|
});
|
|
return false;
|
|
}
|
|
|
|
function filter_signoffs() {
|
|
/* start with all rows, and then remove ones we shouldn't show */
|
|
var rows = $('#tbody_signoffs').children(),
|
|
all_rows = rows;
|
|
/* apply arch and repo filters */
|
|
$('#signoffs_filter .arch_filter').add(
|
|
'#signoffs_filter .repo_filter').each(function() {
|
|
if (!$(this).is(':checked')) {
|
|
rows = rows.not('.' + $(this).val());
|
|
}
|
|
});
|
|
/* and then the slightly more expensive pending check */
|
|
if ($('#id_pending').is(':checked')) {
|
|
rows = rows.has('td.signoff-no');
|
|
}
|
|
/* hide all rows, then show the set we care about */
|
|
all_rows.hide();
|
|
rows.show();
|
|
$('#filter-count').text(rows.length);
|
|
/* make sure we update the odd/even styling from sorting */
|
|
$('.results').trigger('applyWidgets', [false]);
|
|
filter_signoffs_save();
|
|
}
|
|
function filter_signoffs_reset() {
|
|
$('#signoffs_filter .arch_filter').attr('checked', 'checked');
|
|
$('#signoffs_filter .repo_filter').attr('checked', 'checked');
|
|
$('#id_pending').removeAttr('checked');
|
|
filter_signoffs();
|
|
}
|
|
function filter_signoffs_save() {
|
|
var state = $('#signoffs_filter').serializeArray();
|
|
localStorage['filter_signoffs'] = JSON.stringify(state);
|
|
}
|
|
function filter_signoffs_load() {
|
|
var state = localStorage['filter_signoffs'];
|
|
if (!state)
|
|
return;
|
|
state = JSON.parse(state);
|
|
$('#signoffs_filter input[type="checkbox"]').removeAttr('checked');
|
|
$.each(state, function (i, v) {
|
|
// this assumes our only filters are checkboxes
|
|
$('#signoffs_filter input[name="' + v['name'] + '"]').attr('checked', 'checked');
|
|
});
|
|
}
|
|
|
|
function collapseNotes(elements) {
|
|
// Remove any trailing <br/> tags from the note contents
|
|
$(elements).children('br').filter(':last-child').filter(function(i, e) { return !e.nextSibling; }).remove();
|
|
|
|
var maxElements = 8;
|
|
$(elements).each(function(idx, ele) {
|
|
ele = $(ele);
|
|
// Hide everything past a given limit. Don't do anything if we don't
|
|
// have enough items, or the link already exists.
|
|
var contents = ele.contents();
|
|
if (contents.length <= maxElements || ele.find('a.morelink').length > 0) {
|
|
return;
|
|
}
|
|
contents.slice(maxElements).wrapAll('<div class="hide"/>');
|
|
ele.append('<br class="morelink-spacer"/><a class="morelink" href="#">Show More…</a>');
|
|
|
|
// add link and wire it up to show the hidden items
|
|
ele.find('a.morelink').click(function(event) {
|
|
event.preventDefault();
|
|
$(this).remove();
|
|
ele.find('br.morelink-spacer').remove();
|
|
// move the div contents back and delete the empty div
|
|
var hidden = ele.find('div.hide');
|
|
hidden.contents().appendTo(ele);
|
|
hidden.remove();
|
|
});
|
|
});
|
|
}
|
|
|
|
/* visualizations */
|
|
function format_filesize(size, decimals) {
|
|
/*var labels = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];*/
|
|
var labels = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
|
|
label = 0;
|
|
|
|
while (size > 2048.0 && label < labels.length - 1) {
|
|
label++;
|
|
size /= 1024.0;
|
|
}
|
|
if (decimals === undefined) {
|
|
decimals = 2;
|
|
}
|
|
|
|
return size.toFixed(decimals) + ' ' + labels[label];
|
|
}
|
|
|
|
/* HTML5 input type and attribute enhancements */
|
|
function modify_attributes(to_change) {
|
|
/* jQuery doesn't let us change the 'type' attribute directly due to IE
|
|
woes, so instead we can clone and replace, setting the type. */
|
|
$.each(to_change, function(id, attrs) {
|
|
var obj = $(id);
|
|
obj.replaceWith(obj.clone().attr(attrs));
|
|
});
|
|
}
|