186 lines
5.5 KiB
JavaScript
186 lines
5.5 KiB
JavaScript
/* global showHelp $ $$ debounce API template t */
|
|
'use strict';
|
|
|
|
const exclusions = (() => {
|
|
|
|
// `\S*\*\S*` => `foo*`, `*bar`, `f*bar`
|
|
// `\S+\.\S+` => `foo.bar`, `f.b`
|
|
// see https://regex101.com/r/NUuwiu/2
|
|
const validExclusionRegex = /^(\S*\*\S*|\S+\.\S+)$/;
|
|
// ms to wait before validating user input
|
|
const saveDelay = 250;
|
|
|
|
// get exclusions from a select element
|
|
function get() {
|
|
const list = {};
|
|
$$('#excluded-wrap input').forEach(input => {
|
|
const url = input.value;
|
|
if (url && validExclusionRegex.test(url)) {
|
|
list[url] = createRegExp(url);
|
|
}
|
|
});
|
|
exclusions.list = Object.keys(list).sort().reduce((acc, ex) => {
|
|
acc[ex] = list[ex];
|
|
return acc;
|
|
}, {});
|
|
return exclusions.list;
|
|
}
|
|
|
|
function createRegExp(url) {
|
|
// Include boundaries to prevent `e.c` from matching `google.com`
|
|
const prefix = url.startsWith('^') ? '' : '\\b';
|
|
const suffix = url.endsWith('$') ? '' : '\\b';
|
|
// Only escape `.`; alter `*`; all other regex allowed
|
|
return `${prefix}${url.replace(/\./g, '\\.').replace(/\*/g, '.*?')}${suffix}`;
|
|
}
|
|
|
|
function addExclusionEntry({container, value, insertAfter}) {
|
|
const item = template.exclusionEntry.cloneNode(true);
|
|
const input = $('input', item);
|
|
const regex = validExclusionRegex.toString();
|
|
input.value = value;
|
|
input.setAttribute('pattern', regex.substring(1, regex.length - 1));
|
|
if (insertAfter) {
|
|
insertAfter.insertAdjacentElement('afterend', item);
|
|
} else {
|
|
container.appendChild(item);
|
|
}
|
|
input.focus();
|
|
}
|
|
|
|
function populateList() {
|
|
// List should never be empty - need to add an empty input
|
|
const list = exclusions.list.length ? exclusions.list : [''];
|
|
const block = $('#excluded-wrap');
|
|
block.textContent = '';
|
|
const container = document.createDocumentFragment();
|
|
list.sort().forEach(value => {
|
|
addExclusionEntry({container, value});
|
|
});
|
|
block.appendChild(container);
|
|
}
|
|
|
|
function validateEntry(input) {
|
|
const lists = Object.keys(get());
|
|
const url = input.value;
|
|
const index = $$('.exclusion-entry input:valid').indexOf(input);
|
|
// Generic URL globs; e.g. "https://test.com/*" & "*.test.com"
|
|
return !(lists.includes(url) && lists.indexOf(url) !== index) &&
|
|
validExclusionRegex.test(url);
|
|
}
|
|
|
|
function updateList() {
|
|
const list = get();
|
|
const keys = Object.keys(list);
|
|
if (exclusions.savedValue !== keys.join(',')) {
|
|
exclusions.saveValue = keys.join(',');
|
|
exclusions.list = list;
|
|
}
|
|
debounce(save, 100, {});
|
|
updateStats();
|
|
}
|
|
|
|
function deleteExclusions(entry) {
|
|
if ($('#excluded-wrap').children.length === 1) {
|
|
const input = $('.exclusion-input', entry);
|
|
input.value = '';
|
|
input.focus();
|
|
} else {
|
|
const nextFocus = entry.previousElementSibling || entry.nextElementSibling;
|
|
entry.parentNode.removeChild(entry);
|
|
if (nextFocus) {
|
|
$('input', nextFocus).focus();
|
|
}
|
|
}
|
|
updateList();
|
|
}
|
|
|
|
function excludeAction(event) {
|
|
event.preventDefault();
|
|
const target = event.target;
|
|
const entry = target.closest('.exclusion-entry');
|
|
if (target.classList.contains('exclusion-add')) {
|
|
addExclusionEntry({
|
|
container: $('#excluded-wrap'),
|
|
value: '',
|
|
insertAfter: entry
|
|
});
|
|
} else if (target.classList.contains('exclusion-delete')) {
|
|
deleteExclusions(entry);
|
|
}
|
|
}
|
|
|
|
function excludeValidate(event) {
|
|
const target = event.target;
|
|
target.setCustomValidity('');
|
|
target.title = '';
|
|
if (target.matches(':valid')) {
|
|
if (!validateEntry(target)) {
|
|
target.setCustomValidity(t('exclusionsvalidateEntry'));
|
|
target.title = t('exclusionsvalidateEntry');
|
|
} else {
|
|
updateList();
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateStats() {
|
|
const total = Object.keys(exclusions.list).length;
|
|
$('#excluded-stats').textContent = total ? t('exclusionsStatus', [total]) : '';
|
|
}
|
|
|
|
function showExclusionHelp(event) {
|
|
event.preventDefault();
|
|
showHelp(t('exclusionsHelpTitle'), t('exclusionsHelp').replace(/\n/g, '<br>'), 'info');
|
|
}
|
|
|
|
function onRuntimeMessage(msg) {
|
|
if (msg.method === 'exclusionsUpdated' && msg.style && msg.style.exclusions) {
|
|
update({list: Object.keys(msg.style.exclusions), isUpdating: true});
|
|
// update popup, if loaded
|
|
// if (typeof popupExclusions !== 'undefined') {
|
|
// popupExclusions.selectExclusions(msg.style.exclusions);
|
|
// }
|
|
}
|
|
}
|
|
|
|
function update({list = exclusions.list, isUpdating}) {
|
|
if (!isUpdating) {
|
|
exclusions.list = list;
|
|
populateList();
|
|
}
|
|
updateStats();
|
|
}
|
|
|
|
function save({id, exclusionList = get()}) {
|
|
// get last saved version
|
|
API.getStyles({id: id || exclusions.id}).then(([style]) => {
|
|
style.exclusions = exclusionList;
|
|
style.reason = 'exclusionsUpdated';
|
|
API.saveStyle(style);
|
|
});
|
|
}
|
|
|
|
function init(style) {
|
|
const block = $('#exclusions');
|
|
const list = Object.keys(style.exclusions || {});
|
|
const size = list.length;
|
|
exclusions.id = style.id;
|
|
exclusions.savedValue = list.join(',');
|
|
exclusions.list = list;
|
|
if (size) {
|
|
block.setAttribute('open', true);
|
|
} else {
|
|
block.removeAttribute('open');
|
|
}
|
|
update({});
|
|
|
|
$('#excluded-wrap').onclick = excludeAction;
|
|
$('#excluded-wrap').oninput = event => debounce(excludeValidate, saveDelay, event);
|
|
$('#excluded-list-help').onclick = showExclusionHelp;
|
|
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
|
}
|
|
|
|
return {init, get, update, save, createRegExp};
|
|
})();
|