242 lines
7.1 KiB
JavaScript
242 lines
7.1 KiB
JavaScript
|
/*
|
||
|
global messageBox resolveWith
|
||
|
gloabl editor showHelp onChange
|
||
|
*/
|
||
|
'use strict';
|
||
|
|
||
|
const exclusions = (() => {
|
||
|
|
||
|
// get exclusions from a select element
|
||
|
function get(options = {}) {
|
||
|
const lists = {};
|
||
|
const excluded = options.exclusions || getMultiOptions(options);
|
||
|
excluded.forEach(list => {
|
||
|
lists[list] = createRegExp(list);
|
||
|
});
|
||
|
return lists;
|
||
|
}
|
||
|
|
||
|
function createRegExp(url) {
|
||
|
// returning a regex string; Object.assign is used on style & doesn't save RegExp
|
||
|
return url.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&').replace(/[*]/g, '.+?');
|
||
|
}
|
||
|
|
||
|
function getMultiOptions({select, selectedOnly, elements} = {}) {
|
||
|
return [...(select || exclusions.select).children].reduce((acc, opt) => {
|
||
|
if (selectedOnly && opt.selected) {
|
||
|
acc.push(elements ? opt : opt.value);
|
||
|
} else if (!selectedOnly) {
|
||
|
acc.push(elements ? opt : opt.value);
|
||
|
}
|
||
|
return acc;
|
||
|
}, []);
|
||
|
}
|
||
|
|
||
|
function populateSelect(options = []) {
|
||
|
exclusions.select.textContent = '';
|
||
|
const option = $create('option');
|
||
|
options.forEach(value => {
|
||
|
const opt = option.cloneNode();
|
||
|
opt.value = value;
|
||
|
opt.textContent = value;
|
||
|
opt.title = value;
|
||
|
exclusions.select.appendChild(opt);
|
||
|
});
|
||
|
exclusions.lastValue = exclusions.select.textContent;
|
||
|
}
|
||
|
|
||
|
function openInputDialog({title, callback, value = ''}) {
|
||
|
messageBox({
|
||
|
title,
|
||
|
className: 'center',
|
||
|
contents: [
|
||
|
$create('div', {id: 'excludedError', textContent: '\xa0\xa0'}),
|
||
|
$create('input', {type: 'text', id: 'excluded-input', value})
|
||
|
],
|
||
|
buttons: [t('confirmOK'), t('confirmCancel')]
|
||
|
});
|
||
|
setTimeout(() => {
|
||
|
const btn = $('#message-box-buttons button', messageBox.element);
|
||
|
// not using onkeyup here because pressing enter to activate add/edit
|
||
|
// button fires onkeyup here when user releases the key
|
||
|
$('#excluded-input').onkeydown = event => {
|
||
|
if (event.which === 13) {
|
||
|
event.preventDefault();
|
||
|
callback.apply(btn);
|
||
|
}
|
||
|
};
|
||
|
btn.onclick = callback;
|
||
|
}, 1);
|
||
|
}
|
||
|
|
||
|
function validateURL(url) {
|
||
|
const lists = getMultiOptions();
|
||
|
// Generic URL globs; e.g. "https://test.com/*" & "*.test.com"
|
||
|
return !lists.includes(url) && /^(?:https?:\/\/)?([\w*]+\.)+[\w*./-]+/.test(url);
|
||
|
}
|
||
|
|
||
|
function addExclusion() {
|
||
|
openInputDialog({
|
||
|
title: t('exclusionsAddTitle'),
|
||
|
callback: function () {
|
||
|
const value = $('#excluded-input').value;
|
||
|
if (value && validateURL(value)) {
|
||
|
exclusions.select.appendChild($create('option', {value, innerText: value}));
|
||
|
done();
|
||
|
messageBox.listeners.button.apply(this);
|
||
|
} else {
|
||
|
const errorBox = $('#excludedError', messageBox.element);
|
||
|
errorBox.textContent = t('exclusionsInvalidUrl');
|
||
|
setTimeout(() => {
|
||
|
errorBox.textContent = '';
|
||
|
}, 5000);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function editExclusion() {
|
||
|
const value = exclusions.select.value;
|
||
|
if (value) {
|
||
|
openInputDialog({
|
||
|
title: t('exclusionsAddTitle'),
|
||
|
value,
|
||
|
callback: function () {
|
||
|
const newValue = $('#excluded-input').value;
|
||
|
// only edit the first selected option
|
||
|
const option = getMultiOptions({selectedOnly: true, elements: true})[0];
|
||
|
if (newValue && validateURL(newValue) && option) {
|
||
|
option.value = newValue;
|
||
|
option.textContent = newValue;
|
||
|
option.title = newValue;
|
||
|
if (newValue !== value) {
|
||
|
// make it dirty!
|
||
|
exclusions.select.savedValue = '';
|
||
|
}
|
||
|
done();
|
||
|
messageBox.listeners.button.apply(this);
|
||
|
} else {
|
||
|
const errorBox = $('#excludedError', messageBox.element);
|
||
|
errorBox.textContent = t('exclusionsInvalidUrl');
|
||
|
setTimeout(() => {
|
||
|
errorBox.textContent = '';
|
||
|
}, 5000);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function deleteExclusions() {
|
||
|
const entries = getMultiOptions({selectedOnly: true, elements: true});
|
||
|
if (entries.length) {
|
||
|
messageBox
|
||
|
.confirm(t('exclusionsDeleteConfirmation', [entries.length]))
|
||
|
.then(ok => {
|
||
|
if (ok) {
|
||
|
entries.forEach(el => exclusions.select.removeChild(el));
|
||
|
done();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function excludeAction(event) {
|
||
|
const target = event.target;
|
||
|
if (target.id && target.id.startsWith('excluded-list-')) {
|
||
|
// class "excluded-list-(add/edit/delete)" -> ['excluded', 'list', 'add']
|
||
|
const type = target.id.split('-').pop();
|
||
|
switch (type) {
|
||
|
case 'add':
|
||
|
addExclusion();
|
||
|
break;
|
||
|
case 'edit':
|
||
|
editExclusion();
|
||
|
break;
|
||
|
case 'delete':
|
||
|
deleteExclusions();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function done() {
|
||
|
if (editor) {
|
||
|
// make usercss dirty
|
||
|
exclusions.select.onchange();
|
||
|
} else {
|
||
|
// make regular userstyle dirty
|
||
|
onChange({target: exclusions.select});
|
||
|
}
|
||
|
updateStats();
|
||
|
}
|
||
|
|
||
|
function updateStats() {
|
||
|
if (exclusions.select) {
|
||
|
const excludedTotal = exclusions.select.children.length;
|
||
|
const state = excludedTotal === 0;
|
||
|
exclusions.select.setAttribute('size', excludedTotal || 1);
|
||
|
$('#excluded-stats').textContent = state ? '' : t('exclusionsStatus', [excludedTotal]);
|
||
|
toggleButtons(state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function toggleButtons(state = false) {
|
||
|
const noSelection = exclusions.select.value === '';
|
||
|
$('#excluded-list-edit').disabled = noSelection || state;
|
||
|
$('#excluded-list-delete').disabled = noSelection || state;
|
||
|
}
|
||
|
|
||
|
function showExclusionHelp(event) {
|
||
|
event.preventDefault();
|
||
|
showHelp(t('exclusionsHelpTitle'), t('exclusionsHelp').replace(/\n/g, '<br>'), 'info');
|
||
|
}
|
||
|
|
||
|
function onRuntimeMessage(msg) {
|
||
|
if (msg.method === 'styleUpdated' && msg.style && msg.style.exclusions && exclusions.select) {
|
||
|
update(Object.keys(msg.style.exclusions));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function update(list = exclusions.list) {
|
||
|
populateSelect(list);
|
||
|
updateStats();
|
||
|
}
|
||
|
|
||
|
function onchange(dirty) {
|
||
|
exclusions.select.onchange = function () {
|
||
|
dirty.modify('exclusions', exclusions.lastValue, exclusions.select.textContent);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function save(style, dirty) {
|
||
|
style.reason = 'exclusionsUpdate';
|
||
|
API.saveStyle(style);
|
||
|
if (dirty) {
|
||
|
dirty.clear('exclusions');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function init(style) {
|
||
|
const list = Object.keys(style.exclusions || {});
|
||
|
const size = list.length;
|
||
|
exclusions.select = $('#excluded-list');
|
||
|
exclusions.select.savedValue = String(size);
|
||
|
exclusions.list = list;
|
||
|
update();
|
||
|
|
||
|
$('#excluded-wrap').onclick = excludeAction;
|
||
|
$('#excluded-list-help').onclick = showExclusionHelp;
|
||
|
// Disable Edit & Delete buttons if nothing selected
|
||
|
exclusions.select.onclick = () => toggleButtons();
|
||
|
document.head.appendChild($create('style', `
|
||
|
#excluded-list:empty:after {
|
||
|
content: "${t('exclusionsEmpty')}";
|
||
|
}
|
||
|
`));
|
||
|
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
||
|
}
|
||
|
|
||
|
return {init, get, update, onchange, save, createRegExp, getMultiOptions};
|
||
|
})();
|