2024-04-15 16:04:23 +00:00
|
|
|
if (typeof browser === 'undefined') {
|
|
|
|
browser = chrome;
|
|
|
|
}
|
|
|
|
|
2023-09-25 16:38:52 +00:00
|
|
|
/**
|
|
|
|
* @description Shortcut to send messages to background script
|
|
|
|
*/
|
2024-04-15 16:04:23 +00:00
|
|
|
const dispatch = browser.runtime.sendMessage;
|
2023-09-25 16:38:52 +00:00
|
|
|
|
2023-11-09 17:52:09 +00:00
|
|
|
/**
|
|
|
|
* @description Domain RegExp
|
|
|
|
*/
|
|
|
|
const domainRx = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/g;
|
|
|
|
|
2023-09-25 16:38:52 +00:00
|
|
|
/**
|
|
|
|
* @description Exclusion list, URLs where the user prefers to disable the extension
|
|
|
|
* @type {string[]}
|
|
|
|
*/
|
|
|
|
let exclusionList = [];
|
|
|
|
|
|
|
|
/**
|
2024-02-24 18:13:40 +00:00
|
|
|
* @description Render exclusion items into exclusion list
|
2023-09-25 16:38:52 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
function createList() {
|
2023-09-25 16:38:52 +00:00
|
|
|
const emptyItemElement = document.getElementById('exclusion-list-item-empty');
|
|
|
|
const exclusionListElement = document.getElementById('exclusion-list');
|
|
|
|
const exclusionListItemTemplateElement = document.getElementById('exclusion-list-item-template');
|
|
|
|
|
|
|
|
Array.from(exclusionListElement.querySelectorAll('[data-value]')).forEach((exclusionItem) => {
|
|
|
|
exclusionItem.remove();
|
|
|
|
});
|
|
|
|
|
|
|
|
if (exclusionList.length) {
|
|
|
|
for (const exclusionValue of exclusionList) {
|
|
|
|
const ariaLabelOrTitle = `Delete ${exclusionValue}`;
|
|
|
|
const itemElement = exclusionListItemTemplateElement.cloneNode(true);
|
|
|
|
const deleteButtonElement = itemElement.getElementsByTagName('button')[0];
|
|
|
|
|
|
|
|
deleteButtonElement.addEventListener('click', handleDeleteClick);
|
|
|
|
deleteButtonElement.setAttribute('aria-label', ariaLabelOrTitle);
|
|
|
|
deleteButtonElement.setAttribute('title', ariaLabelOrTitle);
|
|
|
|
itemElement.removeAttribute('id');
|
|
|
|
itemElement.getElementsByTagName('span')[0].innerText = exclusionValue;
|
|
|
|
itemElement.setAttribute('data-value', exclusionValue);
|
|
|
|
itemElement.style.removeProperty('display');
|
|
|
|
exclusionListElement.appendChild(itemElement);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
emptyItemElement.innerText = "You don't have any exclusions yet";
|
|
|
|
emptyItemElement.style.removeProperty('display');
|
|
|
|
}
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
2023-11-09 17:52:09 +00:00
|
|
|
/**
|
|
|
|
* @async
|
|
|
|
* @description Add a new item to the exclusion list
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async function handleAddClick() {
|
2024-04-15 16:04:23 +00:00
|
|
|
const exclusionValue = window.prompt(browser.i18n.getMessage('options_addPrompt'));
|
2023-11-09 17:52:09 +00:00
|
|
|
|
|
|
|
if (exclusionValue?.trim() && (domainRx.test(exclusionValue) || exclusionValue === 'localhost')) {
|
|
|
|
const filterInputElement = document.getElementById('filter-input');
|
|
|
|
const state = { enabled: false };
|
|
|
|
await dispatch({ hostname: exclusionValue, state, type: 'SET_HOSTNAME_STATE' });
|
|
|
|
|
|
|
|
exclusionList = [...new Set([...exclusionList, exclusionValue])].sort();
|
|
|
|
createList();
|
|
|
|
updateList(filterInputElement.value.trim());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-25 16:38:52 +00:00
|
|
|
/**
|
|
|
|
* @async
|
|
|
|
* @description Clear all items from the exclusion list
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
async function handleClearClick() {
|
2023-09-25 16:38:52 +00:00
|
|
|
const filterInputElement = document.getElementById('filter-input');
|
|
|
|
|
|
|
|
for (const exclusionValue of exclusionList) {
|
|
|
|
const state = { enabled: true };
|
|
|
|
await dispatch({ hostname: exclusionValue, state, type: 'SET_HOSTNAME_STATE' });
|
|
|
|
}
|
|
|
|
|
|
|
|
exclusionList = [];
|
|
|
|
createList();
|
|
|
|
updateList(filterInputElement.value.trim());
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @async
|
|
|
|
* @description Setup handlers and items
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
async function handleContentLoaded() {
|
2023-09-25 16:38:52 +00:00
|
|
|
exclusionList = await dispatch({ type: 'GET_EXCLUSION_LIST' });
|
|
|
|
createList();
|
|
|
|
|
2023-11-09 17:52:09 +00:00
|
|
|
const addButtonElement = document.getElementById('add-button');
|
|
|
|
addButtonElement.addEventListener('click', handleAddClick);
|
|
|
|
|
2023-09-25 16:38:52 +00:00
|
|
|
const clearButtonElement = document.getElementById('clear-button');
|
|
|
|
clearButtonElement.addEventListener('click', handleClearClick);
|
|
|
|
|
|
|
|
const exportButtonElement = document.getElementById('export-button');
|
|
|
|
exportButtonElement.addEventListener('click', handleExportClick);
|
|
|
|
|
|
|
|
const fileInputElement = document.getElementById('file-input');
|
|
|
|
fileInputElement.addEventListener('change', handleFileChange);
|
|
|
|
|
|
|
|
const filterInputElement = document.getElementById('filter-input');
|
|
|
|
filterInputElement.addEventListener('keydown', handleFilterKeyDown);
|
|
|
|
|
|
|
|
const importButtonElement = document.getElementById('import-button');
|
|
|
|
importButtonElement.addEventListener('click', handleImportClick);
|
|
|
|
|
|
|
|
translate();
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @async
|
2024-02-24 18:13:40 +00:00
|
|
|
* @description Delete the clicked element from the exclusion list
|
2023-09-25 16:38:52 +00:00
|
|
|
* @param {MouseEvent} event
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
async function handleDeleteClick(event) {
|
2023-09-25 16:38:52 +00:00
|
|
|
const filterInputElement = document.getElementById('filter-input');
|
|
|
|
const { value } = event.currentTarget.parentElement.dataset;
|
2023-10-07 10:45:20 +00:00
|
|
|
const itemElement = document.querySelector(`[data-value="${value}"]`);
|
2023-09-25 16:38:52 +00:00
|
|
|
const state = { enabled: true };
|
|
|
|
|
|
|
|
await dispatch({ hostname: value, state, type: 'SET_HOSTNAME_STATE' });
|
|
|
|
exclusionList = exclusionList.filter((exclusionValue) => exclusionValue !== value);
|
2023-10-07 10:45:20 +00:00
|
|
|
itemElement?.remove();
|
2023-09-25 16:38:52 +00:00
|
|
|
updateList(filterInputElement.value.trim());
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
2024-02-24 18:13:40 +00:00
|
|
|
* @description Export a file with the current exclusion list
|
2023-09-25 16:38:52 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
function handleExportClick() {
|
2023-09-25 16:38:52 +00:00
|
|
|
const anchor = document.createElement('a');
|
2023-10-07 10:45:20 +00:00
|
|
|
const now = new Date();
|
2023-10-11 06:50:02 +00:00
|
|
|
const day = now.getDate().toString().padStart(2, '0');
|
|
|
|
const month = now.getMonth().toString().padStart(2, '0');
|
2023-10-07 10:45:20 +00:00
|
|
|
const year = now.getUTCFullYear();
|
2023-09-25 16:38:52 +00:00
|
|
|
const text = exclusionList.join('\n');
|
2023-10-11 06:50:02 +00:00
|
|
|
const defaultTitle = `${year}${month}${day}`;
|
|
|
|
const customTitle = window.prompt('Enter a file name', defaultTitle);
|
2023-09-25 16:38:52 +00:00
|
|
|
const blob = new Blob([text], { type: 'octet/stream' });
|
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
|
|
|
|
|
anchor.href = url;
|
2023-10-11 06:50:02 +00:00
|
|
|
anchor.download = `${customTitle || defaultTitle}.cdm`;
|
2023-09-25 16:38:52 +00:00
|
|
|
anchor.click();
|
|
|
|
window.URL.revokeObjectURL(url);
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
2024-02-24 18:13:40 +00:00
|
|
|
* @description Process a file and send the updates
|
2023-09-25 16:38:52 +00:00
|
|
|
* @param {InputEvent} event
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
function handleFileChange(event) {
|
2023-09-25 16:38:52 +00:00
|
|
|
const file = event.currentTarget.files[0];
|
|
|
|
const filterInputElement = document.getElementById('filter-input');
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
|
|
|
reader.addEventListener('load', async (event) => {
|
|
|
|
const newExclusionList = event.currentTarget.result.split('\n').filter((x) => x.trim());
|
|
|
|
|
|
|
|
for (const exclusionValue of newExclusionList) {
|
|
|
|
const state = { enabled: false };
|
|
|
|
await dispatch({ hostname: exclusionValue, state, type: 'SET_HOSTNAME_STATE' });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newExclusionList.length) {
|
|
|
|
exclusionList = [...new Set([...exclusionList, ...newExclusionList])].sort();
|
|
|
|
createList();
|
|
|
|
updateList(filterInputElement.value.trim());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
event.currentTarget.value = '';
|
|
|
|
reader.readAsText(file);
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
2024-02-24 18:13:40 +00:00
|
|
|
* @description Apply filter to the exclusion list when the user presses ENTER key
|
2023-09-25 16:38:52 +00:00
|
|
|
* @param {KeyboardEvent} event
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
function handleFilterKeyDown(event) {
|
2023-09-25 16:38:52 +00:00
|
|
|
if (event.key === 'Enter') {
|
|
|
|
const filterValue = event.currentTarget.value.trim();
|
|
|
|
updateList(filterValue);
|
|
|
|
}
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
2024-02-24 18:13:40 +00:00
|
|
|
* @description Shallow click an hidden input to open the file explorer
|
2023-09-25 16:38:52 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
function handleImportClick() {
|
2023-09-25 16:38:52 +00:00
|
|
|
const fileInputElement = document.getElementById('file-input');
|
|
|
|
fileInputElement.click();
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
2024-02-24 18:13:40 +00:00
|
|
|
* @description Apply translations to tags with i18n data attribute
|
2023-09-25 16:38:52 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
function translate() {
|
|
|
|
const nodes = document.querySelectorAll('[data-i18n], [data-i18n-placeholder]');
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
for (let i = nodes.length; i--; ) {
|
|
|
|
const node = nodes[i];
|
2023-09-25 18:15:47 +00:00
|
|
|
const { i18n, i18nPlaceholder } = node.dataset;
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
if (i18n) {
|
2024-04-15 16:04:23 +00:00
|
|
|
node.innerHTML = browser.i18n.getMessage(i18n);
|
2023-09-25 16:38:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (i18nPlaceholder) {
|
2024-04-15 16:04:23 +00:00
|
|
|
node.setAttribute('placeholder', browser.i18n.getMessage(i18nPlaceholder));
|
2023-09-25 16:38:52 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
2024-02-24 18:13:40 +00:00
|
|
|
* @description Update exclusion items in DOM
|
2023-09-25 16:38:52 +00:00
|
|
|
* @param {string | undefined} filterValue
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2023-09-25 18:15:47 +00:00
|
|
|
function updateList(filterValue) {
|
2023-09-25 16:38:52 +00:00
|
|
|
const emptyItemElement = document.getElementById('exclusion-list-item-empty');
|
|
|
|
const exclusionListElement = document.getElementById('exclusion-list');
|
|
|
|
const exclusionListElements = exclusionListElement.querySelectorAll(`[data-value]`);
|
|
|
|
|
|
|
|
if (exclusionListElements.length) {
|
|
|
|
let isEmpty = true;
|
|
|
|
emptyItemElement.style.setProperty('display', 'none');
|
|
|
|
|
|
|
|
for (const exclusionItemElement of Array.from(exclusionListElements)) {
|
|
|
|
if (exclusionItemElement.matches(`[data-value*="${filterValue}"]`) || !filterValue) {
|
|
|
|
exclusionItemElement.style.removeProperty('display');
|
|
|
|
isEmpty = false;
|
|
|
|
} else {
|
|
|
|
exclusionItemElement.style.setProperty('display', 'none');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isEmpty) {
|
|
|
|
emptyItemElement.innerText = 'No exclusions found';
|
|
|
|
emptyItemElement.style.removeProperty('display');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
emptyItemElement.innerText = "You don't have any exclusions yet";
|
|
|
|
emptyItemElement.style.removeProperty('display');
|
|
|
|
}
|
2023-09-25 18:15:47 +00:00
|
|
|
}
|
2023-09-25 16:38:52 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @description Listen to document ready
|
|
|
|
* @listens document#DOMContentLoaded
|
|
|
|
*/
|
|
|
|
document.addEventListener('DOMContentLoaded', handleContentLoaded);
|