diff --git a/packages/browser-extension/docs/README.md b/packages/browser-extension/docs/README.md
index eb6dbac..06eff4e 100644
--- a/packages/browser-extension/docs/README.md
+++ b/packages/browser-extension/docs/README.md
@@ -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`
diff --git a/packages/browser-extension/src/_locales/de/messages.json b/packages/browser-extension/src/_locales/de/messages.json
index 3c5bfda..0eb6fa4 100644
--- a/packages/browser-extension/src/_locales/de/messages.json
+++ b/packages/browser-extension/src/_locales/de/messages.json
@@ -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"
},
diff --git a/packages/browser-extension/src/_locales/en/messages.json b/packages/browser-extension/src/_locales/en/messages.json
index 5c6d67a..b9082e4 100644
--- a/packages/browser-extension/src/_locales/en/messages.json
+++ b/packages/browser-extension/src/_locales/en/messages.json
@@ -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"
},
diff --git a/packages/browser-extension/src/_locales/es/messages.json b/packages/browser-extension/src/_locales/es/messages.json
index dbbc5a3..b06a079 100644
--- a/packages/browser-extension/src/_locales/es/messages.json
+++ b/packages/browser-extension/src/_locales/es/messages.json
@@ -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"
},
diff --git a/packages/browser-extension/src/_locales/fr/messages.json b/packages/browser-extension/src/_locales/fr/messages.json
index 085f0ec..d7fc993 100644
--- a/packages/browser-extension/src/_locales/fr/messages.json
+++ b/packages/browser-extension/src/_locales/fr/messages.json
@@ -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"
},
diff --git a/packages/browser-extension/src/_locales/it/messages.json b/packages/browser-extension/src/_locales/it/messages.json
index 2f7a7de..c1534a1 100644
--- a/packages/browser-extension/src/_locales/it/messages.json
+++ b/packages/browser-extension/src/_locales/it/messages.json
@@ -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"
},
diff --git a/packages/browser-extension/src/_locales/pt_BR/messages.json b/packages/browser-extension/src/_locales/pt_BR/messages.json
index b2ea0e0..eb19af5 100644
--- a/packages/browser-extension/src/_locales/pt_BR/messages.json
+++ b/packages/browser-extension/src/_locales/pt_BR/messages.json
@@ -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"
},
diff --git a/packages/browser-extension/src/_locales/pt_PT/messages.json b/packages/browser-extension/src/_locales/pt_PT/messages.json
index b2ea0e0..eb19af5 100644
--- a/packages/browser-extension/src/_locales/pt_PT/messages.json
+++ b/packages/browser-extension/src/_locales/pt_PT/messages.json
@@ -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"
},
diff --git a/packages/browser-extension/src/_locales/ro/messages.json b/packages/browser-extension/src/_locales/ro/messages.json
index 8889b57..deeeafa 100644
--- a/packages/browser-extension/src/_locales/ro/messages.json
+++ b/packages/browser-extension/src/_locales/ro/messages.json
@@ -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"
},
diff --git a/packages/browser-extension/src/_locales/ru/messages.json b/packages/browser-extension/src/_locales/ru/messages.json
index 42dc71b..b12a04a 100644
--- a/packages/browser-extension/src/_locales/ru/messages.json
+++ b/packages/browser-extension/src/_locales/ru/messages.json
@@ -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": "Внести свой вклад в этот проект"
},
diff --git a/packages/browser-extension/src/manifest.json b/packages/browser-extension/src/manifest.json
index 06c8048..3fcdb87 100644
--- a/packages/browser-extension/src/manifest.json
+++ b/packages/browser-extension/src/manifest.json
@@ -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"],
diff --git a/packages/browser-extension/src/options.html b/packages/browser-extension/src/options.html
new file mode 100644
index 0000000..3c9f0ca
--- /dev/null
+++ b/packages/browser-extension/src/options.html
@@ -0,0 +1,110 @@
+
+
+
+ Cookie Dialog Monster > Exclusion List
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
diff --git a/packages/browser-extension/src/popup.html b/packages/browser-extension/src/popup.html
index 3005f9b..a314d83 100644
--- a/packages/browser-extension/src/popup.html
+++ b/packages/browser-extension/src/popup.html
@@ -1,7 +1,6 @@
-
@@ -10,6 +9,24 @@
@@ -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"
>
diff --git a/packages/browser-extension/src/scripts/background.js b/packages/browser-extension/src/scripts/background.js
index 595d54c..54ce3a2 100644
--- a/packages/browser-extension/src/scripts/background.js
+++ b/packages/browser-extension/src/scripts/background.js
@@ -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;
diff --git a/packages/browser-extension/src/scripts/content.js b/packages/browser-extension/src/scripts/content.js
index 477871d..d988c53 100644
--- a/packages/browser-extension/src/scripts/content.js
+++ b/packages/browser-extension/src/scripts/content.js
@@ -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();
+}
diff --git a/packages/browser-extension/src/scripts/options.js b/packages/browser-extension/src/scripts/options.js
new file mode 100644
index 0000000..2a9864f
--- /dev/null
+++ b/packages/browser-extension/src/scripts/options.js
@@ -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}
+ */
+
+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}
+ */
+
+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);
diff --git a/packages/browser-extension/src/scripts/popup.js b/packages/browser-extension/src/scripts/popup.js
index ef1f342..783f090 100644
--- a/packages/browser-extension/src/scripts/popup.js
+++ b/packages/browser-extension/src/scripts/popup.js
@@ -64,80 +64,104 @@ let state = { enabled: true };
/**
* @async
* @description Setup stars handlers and result message links
+ * @returns {Promise}
*/
-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}
*/
-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}
*/
-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
diff --git a/packages/browser-extension/src/styles/options.css b/packages/browser-extension/src/styles/options.css
new file mode 100644
index 0000000..1b97cff
--- /dev/null
+++ b/packages/browser-extension/src/styles/options.css
@@ -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;
+}
diff --git a/packages/browser-extension/src/styles/popup.css b/packages/browser-extension/src/styles/popup.css
index 8a0da66..1e8be98 100644
--- a/packages/browser-extension/src/styles/popup.css
+++ b/packages/browser-extension/src/styles/popup.css
@@ -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;