/* 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, '
'), '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}; })();