2cdd264780
Manage: Disable tabindex in nested buttons Manage: Fix homepage link alignment Manage: Make entry actions keyboard navigatable Manage: Add search help accessibility Msgbox: Save and restore focus Manage: Fix delete confirmation cancel Manage: Make history button accessible Manage: Make favicons dropdown accessible Manage: Prevent focus on hidden textarea Msgbox: Stop tabbing outside of msgbox Usercss: Make nondefault reset accessible Usercss: Make colorpicker accessible Edit: Disable tabindex in nested buttons Edit: Make info and config icons accessible Options: Cleanup HTML Options: Make advanced toggle accessible Edit: Fix issues popup Edit: Remove link underlines from icons
256 lines
8.6 KiB
JavaScript
256 lines
8.6 KiB
JavaScript
/* global messageBox */
|
|
/* global ENTRY_ID_PREFIX, newUI */
|
|
/* global filtersSelector, filterAndAppend */
|
|
'use strict';
|
|
|
|
onDOMready().then(() => {
|
|
$('#check-all-updates').onclick = checkUpdateAll;
|
|
$('#check-all-updates-force').onclick = checkUpdateAll;
|
|
$('#apply-all-updates').onclick = applyUpdateAll;
|
|
$('#update-history').onclick = showUpdateHistory;
|
|
});
|
|
|
|
|
|
function applyUpdateAll() {
|
|
const btnApply = $('#apply-all-updates');
|
|
btnApply.disabled = true;
|
|
setTimeout(() => {
|
|
btnApply.classList.add('hidden');
|
|
btnApply.disabled = false;
|
|
renderUpdatesOnlyFilter({show: false});
|
|
}, 1000);
|
|
|
|
$$('.can-update .update').forEach(button => {
|
|
scrollElementIntoView(button);
|
|
button.click();
|
|
});
|
|
}
|
|
|
|
|
|
function checkUpdateAll() {
|
|
document.body.classList.add('update-in-progress');
|
|
$('#check-all-updates').disabled = true;
|
|
$('#check-all-updates-force').classList.add('hidden');
|
|
$('#apply-all-updates').classList.add('hidden');
|
|
$('#update-all-no-updates').classList.add('hidden');
|
|
|
|
const ignoreDigest = this && this.id === 'check-all-updates-force';
|
|
$$('.updatable:not(.can-update)' + (ignoreDigest ? '' : ':not(.update-problem)'))
|
|
.map(el => checkUpdate(el, {single: false}));
|
|
|
|
let total = 0;
|
|
let checked = 0;
|
|
let skippedEdited = 0;
|
|
let updated = 0;
|
|
|
|
BG.updater.checkAllStyles({observer, save: false, ignoreDigest}).then(done);
|
|
|
|
function observer(state, value, details) {
|
|
switch (state) {
|
|
case BG.updater.COUNT:
|
|
total = value;
|
|
break;
|
|
case BG.updater.UPDATED:
|
|
if (++updated === 1) {
|
|
$('#apply-all-updates').disabled = true;
|
|
$('#apply-all-updates').classList.remove('hidden');
|
|
}
|
|
$('#apply-all-updates').dataset.value = updated;
|
|
// fallthrough
|
|
case BG.updater.SKIPPED:
|
|
checked++;
|
|
if (details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED) {
|
|
skippedEdited++;
|
|
}
|
|
reportUpdateState(state, value, details);
|
|
break;
|
|
}
|
|
const progress = $('#update-progress');
|
|
const maxWidth = progress.parentElement.clientWidth;
|
|
progress.style.width = Math.round(checked / total * maxWidth) + 'px';
|
|
}
|
|
|
|
function done() {
|
|
document.body.classList.remove('update-in-progress');
|
|
$('#check-all-updates').disabled = total === 0;
|
|
$('#apply-all-updates').disabled = false;
|
|
renderUpdatesOnlyFilter({check: updated + skippedEdited > 0});
|
|
if (!updated) {
|
|
$('#update-all-no-updates').dataset.skippedEdited = skippedEdited > 0;
|
|
$('#update-all-no-updates').classList.remove('hidden');
|
|
$('#check-all-updates-force').classList.toggle('hidden', skippedEdited === 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function checkUpdate(entry, {single = true} = {}) {
|
|
$('.update-note', entry).textContent = t('checkingForUpdate');
|
|
$('.check-update', entry).title = '';
|
|
if (single) {
|
|
BG.updater.checkStyle({
|
|
save: false,
|
|
ignoreDigest: entry.classList.contains('update-problem'),
|
|
style: BG.cachedStyles.byId.get(entry.styleId),
|
|
observer: reportUpdateState,
|
|
});
|
|
}
|
|
entry.classList.remove('checking-update', 'no-update', 'update-problem');
|
|
entry.classList.add('checking-update');
|
|
}
|
|
|
|
|
|
function reportUpdateState(state, style, details) {
|
|
const entry = $(ENTRY_ID_PREFIX + style.id);
|
|
entry.classList.remove('checking-update');
|
|
switch (state) {
|
|
case BG.updater.UPDATED:
|
|
entry.classList.add('can-update');
|
|
entry.updatedCode = style;
|
|
$('.update-note', entry).textContent = '';
|
|
$('#only-updates').classList.remove('hidden');
|
|
break;
|
|
case BG.updater.SKIPPED: {
|
|
if (entry.classList.contains('can-update')) {
|
|
break;
|
|
}
|
|
const same = (
|
|
details === BG.updater.SAME_MD5 ||
|
|
details === BG.updater.SAME_CODE ||
|
|
details === BG.updater.SAME_VERSION
|
|
);
|
|
const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED;
|
|
entry.dataset.details = details;
|
|
if (!details) {
|
|
details = t('updateCheckFailServerUnreachable') + '\n' + style.updateUrl;
|
|
} else if (typeof details === 'number') {
|
|
details = t('updateCheckFailBadResponseCode', [details]) + '\n' + style.updateUrl;
|
|
} else if (details === BG.updater.EDITED) {
|
|
details = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
|
|
} else if (details === BG.updater.MAYBE_EDITED) {
|
|
details = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
|
|
}
|
|
const message = same ? t('updateCheckSucceededNoUpdate') : details;
|
|
entry.classList.add('no-update');
|
|
entry.classList.toggle('update-problem', !same);
|
|
$('.update-note', entry).textContent = message;
|
|
$('.check-update', entry).title = newUI.enabled ? message : '';
|
|
$('.update', entry).title = t(edited ? 'updateCheckManualUpdateForce' : 'installUpdate');
|
|
if (!document.body.classList.contains('update-in-progress')) {
|
|
// this is a single update job so we can decide whether to hide the filter
|
|
renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')});
|
|
}
|
|
}
|
|
}
|
|
if (filtersSelector.hide) {
|
|
filterAndAppend({entry});
|
|
}
|
|
}
|
|
|
|
|
|
function renderUpdatesOnlyFilter({show, check} = {}) {
|
|
const numUpdatable = $$('.can-update').length;
|
|
const mightUpdate = numUpdatable > 0 || $('.update-problem');
|
|
const checkbox = $('#only-updates input');
|
|
show = show !== undefined ? show : mightUpdate;
|
|
check = check !== undefined ? show && check : checkbox.checked && mightUpdate;
|
|
|
|
$('#only-updates').classList.toggle('hidden', !show);
|
|
checkbox.checked = check && show;
|
|
checkbox.dispatchEvent(new Event('change'));
|
|
|
|
const btnApply = $('#apply-all-updates');
|
|
if (!btnApply.matches('.hidden')) {
|
|
if (numUpdatable > 0) {
|
|
btnApply.dataset.value = numUpdatable;
|
|
} else {
|
|
btnApply.classList.add('hidden');
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function showUpdateHistory(event) {
|
|
event.preventDefault();
|
|
const log = $create('.update-history-log');
|
|
let logText, scroller, toggler;
|
|
let deleted = false;
|
|
BG.chromeLocal.getValue('updateLog').then((lines = []) => {
|
|
logText = lines.join('\n');
|
|
messageBox({
|
|
title: t('updateCheckHistory'),
|
|
contents: log,
|
|
buttons: [
|
|
t('confirmOK'),
|
|
logText && {textContent: t('confirmDelete'), onclick: deleteHistory},
|
|
],
|
|
onshow: logText && (() => {
|
|
scroller = $('#message-box-contents');
|
|
scroller.setAttribute('tabindex', 0);
|
|
scroller.focus();
|
|
scrollToBottom();
|
|
$('#message-box-buttons button').insertAdjacentElement('afterend',
|
|
// TODO: add a global class for our labels
|
|
// TODO: add a <template> or a common function to create such controls
|
|
$create('label', {style: 'position: relative; padding-left: 16px;'}, [
|
|
toggler =
|
|
$create('input', {type: 'checkbox', checked: true, onchange: toggleSkipped}),
|
|
$create('SVG:svg.svg-icon.checked',
|
|
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
|
|
t('manageOnlyUpdates'),
|
|
]));
|
|
toggler.onchange();
|
|
}),
|
|
blockScroll: true
|
|
});
|
|
});
|
|
function scrollToBottom() {
|
|
scroller.scrollTop = 1e9;
|
|
}
|
|
function calcScrollRatio() {
|
|
return (scroller.scrollTop + scroller.clientHeight) / scroller.scrollHeight;
|
|
}
|
|
function toggleSkipped() {
|
|
if (deleted) {
|
|
return;
|
|
}
|
|
const scrollRatio = calcScrollRatio();
|
|
const rxRemoveNOP = this.checked && new RegExp([
|
|
'^[^#]*(',
|
|
Object.keys(BG.updater)
|
|
.filter(k => k.startsWith('SAME_'))
|
|
.map(k => stringAsRegExp(BG.updater[k]))
|
|
.map(rx => rx.source)
|
|
.join('|'),
|
|
').*\r?\n',
|
|
].join(''), 'gm');
|
|
log.textContent = !this.checked ? logText : logText.replace(rxRemoveNOP, '');
|
|
if (Math.abs(scrollRatio - calcScrollRatio()) > .1) {
|
|
scroller.scrollTop = scrollRatio * scroller.scrollHeight - scroller.clientHeight;
|
|
}
|
|
}
|
|
function deleteHistory() {
|
|
if (deleted) {
|
|
BG.chromeLocal.setValue('updateLog', logText.split('\n'));
|
|
setTimeout(scrollToBottom);
|
|
} else {
|
|
BG.chromeLocal.remove('updateLog');
|
|
log.textContent = '';
|
|
}
|
|
deleted = !deleted;
|
|
toggler.onchange();
|
|
this.textContent = t(deleted ? 'undo' : 'confirmDelete');
|
|
}
|
|
}
|
|
|
|
|
|
function handleUpdateInstalled(entry, reason) {
|
|
const isNew = reason === 'install';
|
|
const note = t(isNew ? 'installButtonInstalled' : 'updateCompleted');
|
|
entry.classList.add('update-done', ...(isNew ? ['install-done'] : []));
|
|
entry.classList.remove('can-update', 'updatable');
|
|
$('.update-note', entry).textContent = note;
|
|
$('.updated', entry).title = note;
|
|
renderUpdatesOnlyFilter();
|
|
}
|