Merge pull request #304 from wanhose/v6.4.0

6.4.0
This commit is contained in:
wanhose 2023-09-29 17:17:04 +02:00 committed by GitHub
commit 631453600b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 826 additions and 75 deletions

View File

@ -1,5 +1,9 @@
# Cookie Monster Dialog Browser Extension
This browser extension was designed to remove cookie consent dialogs that appear on websites without setting your preferences. Only in a few cases, it operates based on predefined rules specified in the file `data/fixes.txt` from where the extension will automatically decline or accept the cookie consent dialogs.
Please note that the `data/fixes.txt` file should be regularly updated to reflect changes in websites' cookie consent practices and to ensure accurate handling of the dialogs by the extension.
## Downloads
- [Chrome Web Store](https://chrome.google.com/webstore/detail/djcbfpkdhdkaflcigibkbpboflaplabg)
@ -11,9 +15,9 @@
- All browsers based on Chromium 88+ (Blisk, Brave, Colibri, Epic Browser, Iron Browser, Vivaldi and many more)
- Google Chrome 88+
- Microsoft Edge 88+
- ~~Mozilla Firefox 54+~~ (development stalled until further notice, you can still download and use this extension in its **5.5.5** version)
- ~~Mozilla Firefox~~ (development stalled until further notice)
## Installation (only for developers)
## Installation (only for developers or Mozilla Firefox users)
1. Clone this repository and then run `yarn install`
2. Build this repository running the command `yarn workspace browser-extension run build`

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Webseite melden..."
},
"options_clearButton": {
"message": "Liste leeren"
},
"options_exclusionListTitle": {
"message": "Ausschlussliste"
},
"options_exportButton": {
"message": "Liste exportieren"
},
"options_filterPlaceholder": {
"message": "Nach dem Eintippen ENTER drücken, um zu filtern"
},
"options_importButton": {
"message": "Liste importieren"
},
"popup_contributeOption": {
"message": "Tragen Sie zu diesem Projekt bei"
},

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Report site..."
},
"options_clearButton": {
"message": "Clear list"
},
"options_exclusionListTitle": {
"message": "Exclusion list"
},
"options_exportButton": {
"message": "Export list"
},
"options_filterPlaceholder": {
"message": "Press ENTER to filter after typing"
},
"options_importButton": {
"message": "Import list"
},
"popup_contributeOption": {
"message": "Contribute to this project"
},

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Reportar sitio..."
},
"options_clearButton": {
"message": "Borrar lista"
},
"options_exclusionListTitle": {
"message": "Lista de exclusión"
},
"options_exportButton": {
"message": "Exportar lista"
},
"options_filterPlaceholder": {
"message": "Presiona ENTER para filtrar después de escribir"
},
"options_importButton": {
"message": "Importar lista"
},
"popup_contributeOption": {
"message": "Contribuye a este proyecto"
},

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Signaler le site..."
},
"options_clearButton": {
"message": "Effacer la liste"
},
"options_exclusionListTitle": {
"message": "Liste d'exclusion"
},
"options_exportButton": {
"message": "Exporter la liste"
},
"options_filterPlaceholder": {
"message": "Appuyez sur ENTRÉE pour filtrer après avoir tapé"
},
"options_importButton": {
"message": "Importer la liste"
},
"popup_contributeOption": {
"message": "Contribuez à ce projet"
},

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Segnala sito..."
},
"options_clearButton": {
"message": "Cancella elenco"
},
"options_exclusionListTitle": {
"message": "Elenco di esclusione"
},
"options_exportButton": {
"message": "Esporta elenco"
},
"options_filterPlaceholder": {
"message": "Premi INVIO per filtrare dopo aver digitato"
},
"options_importButton": {
"message": "Importa elenco"
},
"popup_contributeOption": {
"message": "Contribuisci a questo progetto"
},

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Reportar site..."
},
"options_clearButton": {
"message": "Limpar lista"
},
"options_exclusionListTitle": {
"message": "Lista de exclusão"
},
"options_exportButton": {
"message": "Exportar lista"
},
"options_filterPlaceholder": {
"message": "Pressione ENTER para filtrar após digitar"
},
"options_importButton": {
"message": "Importar lista"
},
"popup_contributeOption": {
"message": "Contribua para este projeto"
},

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Reportar site..."
},
"options_clearButton": {
"message": "Limpar lista"
},
"options_exclusionListTitle": {
"message": "Lista de exclusão"
},
"options_exportButton": {
"message": "Exportar lista"
},
"options_filterPlaceholder": {
"message": "Pressione ENTER para filtrar após digitar"
},
"options_importButton": {
"message": "Importar lista"
},
"popup_contributeOption": {
"message": "Contribua para este projeto"
},

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Raportați site-ul..."
},
"options_clearButton": {
"message": "Ștergeți lista"
},
"options_exclusionListTitle": {
"message": "Listă de excludere"
},
"options_exportButton": {
"message": "Exportați lista"
},
"options_filterPlaceholder": {
"message": "Apăsați ENTER pentru a filtra după ce ați tastat"
},
"options_importButton": {
"message": "Importați lista"
},
"popup_contributeOption": {
"message": "Contribuie la acest proiect"
},

View File

@ -5,6 +5,21 @@
"contextMenu_reportOption": {
"message": "Сообщить о сайте..."
},
"options_clearButton": {
"message": "Очистить список"
},
"options_exclusionListTitle": {
"message": "Список исключений"
},
"options_exportButton": {
"message": "Экспорт списка"
},
"options_filterPlaceholder": {
"message": "Нажмите ENTER для фильтрации после ввода"
},
"options_importButton": {
"message": "Импорт списка"
},
"popup_contributeOption": {
"message": "Внести свой вклад в этот проект"
},

View File

@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Cookie Dialog Monster",
"version": "6.3.4",
"version": "6.4.0",
"default_locale": "en",
"description": "__MSG_appDesc__",
"icons": {
@ -13,6 +13,7 @@
"default_icon": "assets/icons/disabled.png",
"default_title": "Cookie Dialog Monster"
},
"options_page": "options.html",
"author": "wanhose",
"background": {
"service_worker": "scripts/background.js"
@ -25,6 +26,7 @@
"*://*.sharepoint.com/*",
"*://*.youtube.com/embed/*",
"*://*.youtube-nocookie.com/embed/*",
"https://translate.google.com/*",
"https://www.cookie-dialog-monster.com/*"
],
"js": ["scripts/content.js", "scripts/dialog.js"],

View File

@ -0,0 +1,110 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Cookie Dialog Monster > Exclusion List</title>
<link rel="stylesheet" href="/styles/reset.css" />
<link rel="stylesheet" href="/styles/options.css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter" />
<script src="/scripts/options.js"></script>
</head>
<body>
<header>
<div>
<h1 class="header-title">
Cookie Dialog Monster > <span data-i18n="options_exclusionListTitle"></span>
</h1>
</div>
</header>
<main>
<div class="button-group">
<button data-variant="large" id="clear-button">
<span data-i18n="options_clearButton"></span>
<svg
aria-hidden="true"
fill="none"
height="14"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="14"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
/>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
<button data-variant="large" id="import-button">
<span data-i18n="options_importButton"></span>
<svg
aria-hidden="true"
fill="none"
height="14"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="14"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
<input accept=".cdm" id="file-input" style="display: none" type="file" />
</button>
<button data-variant="large" id="export-button">
<span data-i18n="options_exportButton"></span>
<svg
aria-hidden="true"
fill="none"
height="14"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="14"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
</button>
</div>
<input id="filter-input" data-i18n-placeholder="options_filterPlaceholder" />
<ul id="exclusion-list">
<li id="exclusion-list-item-template" style="display: none">
<span></span>
<button>
<svg
aria-hidden="true"
fill="none"
height="14"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="14"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
/>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</li>
<li id="exclusion-list-item-empty" style="display: none"></li>
</ul>
</main>
<footer></footer>
</body>
</html>

View File

@ -1,7 +1,6 @@
<html>
<head>
<meta charset="UTF-8" />
<meta name="author" content="wanhose" />
<link rel="stylesheet" href="/styles/reset.css" />
<link rel="stylesheet" href="/styles/popup.css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter" />
@ -10,6 +9,24 @@
<body>
<header>
<h1 class="header-title">Cookie Dialog Monster</h1>
<button id="settings-button">
<svg
aria-hidden="true"
fill="none"
height="18"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="18"
>
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
</button>
</header>
<main>
<popup-button id="power-option" role="button" tabindex="0">
@ -17,10 +34,10 @@
aria-hidden="true"
fill="none"
height="32"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="32"
>
@ -34,10 +51,10 @@
aria-hidden="true"
fill="none"
height="32"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="32"
>
@ -57,10 +74,10 @@
aria-hidden="true"
fill="none"
height="32"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="32"
>
@ -75,10 +92,10 @@
aria-hidden="true"
fill="none"
height="32"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
stroke="currentColor"
viewBox="0 0 24 24"
width="32"
>

View File

@ -5,13 +5,6 @@
const apiUrl = 'https://api.cookie-dialog-monster.com/rest/v2';
/**
* @description Initial state
* @type {{ enabled: boolean }}
*/
const initial = { enabled: true };
/**
* @description Context menu identifier
* @type {string}
@ -90,31 +83,65 @@ chrome.runtime.onMessage.addListener((message, sender, callback) => {
switch (message.type) {
case 'DISABLE_ICON':
if (isPage && tabId) chrome.action.setIcon({ path: '/assets/icons/disabled.png', tabId });
if (isPage && tabId) {
chrome.action.setIcon({ path: '/assets/icons/disabled.png', tabId });
}
break;
case 'ENABLE_ICON':
if (isPage && tabId) chrome.action.setIcon({ path: '/assets/icons/enabled.png', tabId });
if (isPage && tabId) {
chrome.action.setIcon({ path: '/assets/icons/enabled.png', tabId });
}
break;
case 'ENABLE_POPUP':
if (isPage && tabId) chrome.action.setPopup({ popup: '/popup.html', tabId });
if (isPage && tabId) {
chrome.action.setPopup({ popup: '/popup.html', tabId });
}
break;
case 'GET_DATA':
storage.get('data', ({ data }) => (data ? callback(data) : refreshData(callback)));
storage.get('data', ({ data }) => {
if (data) {
callback(data);
} else {
refreshData(callback);
}
});
return true;
case 'GET_STATE':
if (hostname) storage.get(hostname, (state) => callback(state[hostname] ?? initial));
case 'GET_EXCLUSION_LIST':
storage.get(null, (exclusions) => {
const exclusionList = Object.entries(exclusions || {}).flatMap((x) =>
x[0] !== 'data' && !x[1]?.enabled ? [x[0]] : []
);
callback(exclusionList);
});
return true;
case 'GET_HOSTNAME_STATE':
if (hostname) {
storage.get(hostname, (state) => {
callback(state[hostname] ?? { enabled: true });
});
}
return true;
case 'GET_TAB':
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => callback(tabs[0]));
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
callback(tabs[0]);
});
return true;
case 'INSERT_DIALOG_CSS':
if (isPage && tabId) script.insertCSS({ files: ['styles/dialog.css'], target: { tabId } });
if (isPage && tabId) {
script.insertCSS({ files: ['styles/dialog.css'], target: { tabId } });
}
break;
case 'REPORT':
if (tabId) report(message, sender.tab);
if (tabId) {
report(message, sender.tab);
}
break;
case 'UPDATE_STATE':
if (hostname) storage.set({ [hostname]: message.state });
case 'SET_HOSTNAME_STATE':
if (hostname && message.state.enabled === false) {
storage.set({ [hostname]: message.state });
} else if (hostname) {
storage.remove(hostname);
}
break;
default:
break;

View File

@ -42,6 +42,7 @@ let state = { enabled: true };
* @description Cleans DOM
* @param {Element[]} elements
* @param {boolean?} skipMatch
* @returns {void}
*/
function clean(elements, skipMatch) {
@ -59,6 +60,7 @@ function clean(elements, skipMatch) {
/**
* @description Forces a DOM clean in the specific element
* @param {HTMLElement} element
* @returns {void}
*/
function forceClean(element) {
@ -73,6 +75,7 @@ function forceClean(element) {
/**
* @description Forces element to have these styles
* @param {HTMLElement} element
* @returns {void}
*/
function forceElementStyles(element) {
@ -112,7 +115,7 @@ function match(element, skipMatch) {
return false;
}
if (data?.tags.includes(element.tagName?.toUpperCase?.())) {
if (!data?.tags?.length || data.tags.includes(element.tagName?.toUpperCase?.())) {
return false;
}
@ -136,6 +139,7 @@ function match(element, skipMatch) {
/**
* @description Fixes scroll issues
* @returns {void}
*/
function fix() {
@ -198,6 +202,29 @@ function readingTime() {
return time;
}
/**
* @async
* @description Sets up everything
* @param {boolean} skipReadyStateHack
*/
async function runSetup(skipReadyStateHack) {
state = (await dispatch({ hostname, type: 'GET_HOSTNAME_STATE' })) ?? state;
dispatch({ type: 'ENABLE_POPUP' });
if (state.enabled) {
data = await dispatch({ hostname, type: 'GET_DATA' });
// 2023-06-13: hack to force clean when data request takes too long and there are no changes later
if (document.readyState === 'complete' && !skipReadyStateHack) {
window.dispatchEvent(new Event('run'));
}
dispatch({ type: 'ENABLE_ICON' });
observer.observe(document.body ?? document.documentElement, options);
}
}
/**
* @description Mutation Observer instance
* @type {MutationObserver}
@ -212,22 +239,40 @@ const observer = new MutationObserver((mutations) => {
}
});
/**
* @async
* @description Runs setup if the page wasn't focused yet
* @listens window#focus
* @returns {void}
*/
window.addEventListener('focus', async () => {
if (!data) {
await runSetup(true);
forceClean(document.body);
}
});
/**
* @description Fixes still existing elements when page fully load
* @listens window#load
* @returns {void}
*/
window.addEventListener('load', () => {
window.dispatchEvent(new Event('run'));
if (document.hasFocus()) {
window.dispatchEvent(new Event('run'));
}
});
/**
* @description Fixes bfcache issues
* @listens window#pageshow
* @returns {void}
*/
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
if (document.hasFocus() && event.persisted) {
window.dispatchEvent(new Event('run'));
}
});
@ -235,6 +280,7 @@ window.addEventListener('pageshow', (event) => {
/**
* @description Forces a clean when this event is fired
* @listens window#run
* @returns {void}
*/
window.addEventListener('run', () => {
@ -249,23 +295,9 @@ window.addEventListener('run', () => {
});
/**
* @async
* @description Sets up everything
* @description As this extension do really expensive work, it only runs if the user is on the page
*/
(async () => {
state = (await dispatch({ hostname, type: 'GET_STATE' })) ?? state;
dispatch({ type: 'ENABLE_POPUP' });
if (state.enabled) {
data = await dispatch({ hostname, type: 'GET_DATA' });
// 2023-06-13: hack to force clean when data request takes too long and there are no changes later
if (document.readyState === 'complete') {
window.dispatchEvent(new Event('run'));
}
dispatch({ type: 'ENABLE_ICON' });
observer.observe(document.body ?? document.documentElement, options);
}
})();
if (document.hasFocus()) {
runSetup();
}

View File

@ -0,0 +1,244 @@
/**
* @description Shortcut to send messages to background script
*/
const dispatch = chrome.runtime.sendMessage;
/**
* @description Exclusion list, URLs where the user prefers to disable the extension
* @type {string[]}
*/
let exclusionList = [];
/**
* @description Renders exclusion items into exclusion list
* @returns {void}
*/
function createList() {
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');
}
}
/**
* @async
* @description Clear all items from the exclusion list
* @returns {Promise<void>}
*/
async function handleClearClick() {
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());
}
/**
* @async
* @description Setup handlers and items
*/
async function handleContentLoaded() {
exclusionList = await dispatch({ type: 'GET_EXCLUSION_LIST' });
createList();
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();
}
/**
* @async
* @description Deletes the clicked element from the exclusion list
* @param {MouseEvent} event
* @returns {Promise<void>}
*/
async function handleDeleteClick(event) {
const filterInputElement = document.getElementById('filter-input');
const { value } = event.currentTarget.parentElement.dataset;
const state = { enabled: true };
await dispatch({ hostname: value, state, type: 'SET_HOSTNAME_STATE' });
exclusionList = exclusionList.filter((exclusionValue) => exclusionValue !== value);
itemElement.remove();
updateList(filterInputElement.value.trim());
}
/**
* @description Exports a file with the current exclusion list
* @returns {void}
*/
function handleExportClick() {
const anchor = document.createElement('a');
const text = exclusionList.join('\n');
const blob = new Blob([text], { type: 'octet/stream' });
const url = window.URL.createObjectURL(blob);
anchor.href = url;
anchor.download = `${new Date().valueOf()}.cdm`;
anchor.click();
window.URL.revokeObjectURL(url);
}
/**
* @description Processes a file and sends the updates
* @param {InputEvent} event
* @returns {void}
*/
function handleFileChange(event) {
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);
}
/**
* @description Applies filter to the exclusion list when the user presses ENTER key
* @param {KeyboardEvent} event
* @returns {void}
*/
function handleFilterKeyDown(event) {
if (event.key === 'Enter') {
const filterValue = event.currentTarget.value.trim();
updateList(filterValue);
}
}
/**
* @description Shallow clicks an hidden input to open the file explorer
* @returns {void}
*/
function handleImportClick() {
const fileInputElement = document.getElementById('file-input');
fileInputElement.click();
}
/**
* @description Applies translations to tags with i18n data attribute
* @returns {void}
*/
function translate() {
const nodes = document.querySelectorAll('[data-i18n], [data-i18n-placeholder]');
for (let i = nodes.length; i--; ) {
const node = nodes[i];
const { i18n, i18nPlaceholder } = node.dataset;
if (i18n) {
node.innerHTML = chrome.i18n.getMessage(i18n);
}
if (i18nPlaceholder) {
node.setAttribute('placeholder', chrome.i18n.getMessage(i18nPlaceholder));
}
}
}
/**
* @description Updates exclusion items in DOM
* @param {string | undefined} filterValue
* @returns {void}
*/
function updateList(filterValue) {
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');
}
}
/**
* @description Listen to document ready
* @listens document#DOMContentLoaded
*/
document.addEventListener('DOMContentLoaded', handleContentLoaded);

View File

@ -64,80 +64,104 @@ let state = { enabled: true };
/**
* @async
* @description Setup stars handlers and result message links
* @returns {Promise<void>}
*/
const handleContentLoaded = async () => {
async function handleContentLoaded() {
const tab = await dispatch({ type: 'GET_TAB' });
hostname = tab?.url
? new URL(tab.url).hostname.split('.').slice(-3).join('.').replace('www.', '')
: undefined;
state = (await dispatch({ hostname, type: 'GET_STATE' })) ?? state;
state = (await dispatch({ hostname, type: 'GET_HOSTNAME_STATE' })) ?? state;
const host = document.getElementById('host');
host.innerText = hostname ?? 'unknown';
const hostTextElement = document.getElementById('host');
hostTextElement.innerText = hostname ?? 'unknown';
const contribute = document.getElementById('contribute-option');
contribute?.addEventListener('click', handleLinkRedirect);
const contributeButtonElement = document.getElementById('contribute-option');
contributeButtonElement?.addEventListener('click', handleLinkRedirect);
const help = document.getElementById('help-option');
help?.addEventListener('click', handleLinkRedirect);
const helpButtonElement = document.getElementById('help-option');
helpButtonElement?.addEventListener('click', handleLinkRedirect);
const power = document.getElementById('power-option');
power?.addEventListener('click', handlePowerToggle);
if (state.enabled) power?.setAttribute('data-value', 'on');
else power?.setAttribute('data-value', 'off');
const powerButtonElement = document.getElementById('power-option');
powerButtonElement?.addEventListener('click', handlePowerToggle);
if (state.enabled) powerButtonElement?.setAttribute('data-value', 'on');
else powerButtonElement?.setAttribute('data-value', 'off');
const rate = document.getElementById('rate-option');
rate?.addEventListener('click', handleLinkRedirect);
if (isEdge) rate?.setAttribute('data-href', edgeUrl);
else if (isChromium) rate?.setAttribute('data-href', chromeUrl);
else if (isFirefox) rate?.setAttribute('data-href', firefoxUrl);
const rateButtonElement = document.getElementById('rate-option');
rateButtonElement?.addEventListener('click', handleLinkRedirect);
if (isEdge) rateButtonElement?.setAttribute('data-href', edgeUrl);
else if (isChromium) rateButtonElement?.setAttribute('data-href', chromeUrl);
else if (isFirefox) rateButtonElement?.setAttribute('data-href', firefoxUrl);
const settingsButtonElement = document.getElementById('settings-button');
settingsButtonElement.addEventListener('click', handleSettingsClick);
translate();
};
}
/**
* @async
* @description Opens a new tab
* @param {MouseEvent} event
* @returns {Promise<void>}
*/
const handleLinkRedirect = async (event) => {
async function handleLinkRedirect(event) {
const { href } = event.currentTarget.dataset;
if (href) {
await chrome.tabs.create({ url: href });
}
};
}
/**
* @async
* @description Disables or enables extension on current page
* @param {MouseEvent} event
* @returns {Promise<void>}
*/
const handlePowerToggle = async (event) => {
async function handlePowerToggle(event) {
state = { enabled: !state.enabled };
dispatch({ hostname, state, type: 'UPDATE_STATE' });
dispatch({ hostname, state, type: 'SET_HOSTNAME_STATE' });
if (state.enabled) event.currentTarget.setAttribute('data-value', 'on');
else event.currentTarget.setAttribute('data-value', 'off');
await chrome.tabs.reload({ bypassCache: true });
};
window.close();
}
/**
* @description Opens options page
* @returns {void}
*/
function handleSettingsClick() {
chrome.runtime.openOptionsPage();
}
/**
* @description Applies translations to tags with i18n data attribute
* @returns {void}
*/
const translate = () => {
const nodes = document.querySelectorAll('[data-i18n]');
function translate() {
const nodes = document.querySelectorAll('[data-i18n], [data-i18n-placeholder]');
for (let i = nodes.length; i--; ) {
const node = nodes[i];
const { i18n } = node.dataset;
const { i18n, i18nPlaceholder } = node.dataset;
node.innerHTML = chrome.i18n.getMessage(i18n);
if (i18n) {
node.innerHTML = chrome.i18n.getMessage(i18n);
}
if (i18nPlaceholder) {
node.setAttribute('placeholder', chrome.i18n.getMessage(i18nPlaceholder));
}
}
};
}
/**
* @description Listen to document ready

View File

@ -0,0 +1,137 @@
:root {
--color-error: #cc0000;
--color-primary: #3dd9eb;
--color-secondary: #34495e;
--color-success: #5cb85c;
--color-tertiary: #6b7280;
--color-transparent: transparent;
--color-warning: #ffdf00;
--color-white: #ffffff;
}
body {
box-sizing: border-box;
color: var(--color-tertiary);
display: flex;
flex-direction: column;
font-family: Inter, Arial, Helvetica, sans-serif;
min-height: 100vh;
}
body * {
box-sizing: border-box;
font-family: inherit;
}
button {
align-items: center;
background-color: var(--color-white);
border: none;
border-radius: 4px;
color: var(--color-secondary);
display: inline-flex;
gap: 4px;
outline: none;
transition: 0.4s;
}
button[data-variant='large'] {
direction: rtl;
padding: 8px;
}
button:focus,
button:hover {
background-color: var(--color-secondary);
color: var(--color-white);
}
footer {
background-color: var(--color-secondary);
font-size: 12px;
height: 4px;
margin-top: auto;
text-align: center;
}
header {
background-color: var(--color-secondary);
color: var(--color-white);
font-size: 16px !important;
height: 48px;
}
header > div {
align-items: center;
display: flex;
height: 100%;
justify-content: space-between;
margin: auto 0px;
}
main input {
-webkit-appearance: none;
appearance: none;
background-color: var(--color-white);
border: none;
border-bottom: 1px solid var(--color-tertiary);
border-radius: 0px;
color: var(--color-secondary);
font-size: 16px;
height: 42px;
outline: none;
padding: 0px 8px;
width: 100%;
}
main input::placeholder {
color: var(--color-tertiary);
opacity: 1;
}
main input:focus,
main input:hover {
border-bottom: 1px solid var(--color-primary);
}
header > div,
main {
margin: 0px auto;
max-width: 768px;
padding: 16px;
width: 100%;
}
.button-group {
display: flex;
justify-content: flex-end;
gap: 4px;
margin-bottom: 4px;
}
#exclusion-list {
font-size: 14px;
list-style: none;
padding: 0px;
}
#exclusion-list > li {
align-items: center;
border-radius: 4px;
display: flex;
justify-content: space-between;
padding: 8px;
transition: 0.4s;
}
#exclusion-list > li:focus-within,
#exclusion-list > li:hover {
background-color: var(--color-secondary);
color: var(--color-white);
}
#exclusion-list > li > button {
background-color: var(--color-white);
color: var(--color-error);
padding: 4px;
}

View File

@ -21,6 +21,25 @@ body * {
box-sizing: border-box;
}
button {
align-items: center;
background-color: var(--color-secondary);
border: none;
border-radius: 4px;
color: var(--color-white);
display: inline-flex;
gap: 4px;
outline: none;
padding: 2px;
transition: 0.4s;
}
button:focus,
button:hover {
background-color: var(--color-white);
color: var(--color-secondary);
}
footer {
background-color: var(--color-secondary);
font-size: 12px;