From 915add3a4cdbff390a4d0f7d227a4ece5fa31072 Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Tue, 7 Oct 2025 14:18:23 +0300 Subject: [PATCH] feat: add websites to exception list --- README.md | 3 +- _locales/de/messages.json | 23 ++++++- _locales/en/messages.json | 21 ++++++ _locales/es/messages.json | 21 ++++++ _locales/fr/messages.json | 21 ++++++ _locales/hi/messages.json | 21 ++++++ _locales/it/messages.json | 21 ++++++ _locales/ja/messages.json | 21 ++++++ _locales/ko/messages.json | 21 ++++++ _locales/nl/messages.json | 21 ++++++ _locales/pl/messages.json | 21 ++++++ _locales/pt_BR/messages.json | 23 ++++++- _locales/ru/messages.json | 21 ++++++ _locales/tr/messages.json | 21 ++++++ _locales/zh_CN/messages.json | 21 ++++++ background.js | 8 +++ main.js | 19 +++++- manifest.json | 3 +- popup/popup.css | 51 ++++++++++++++ popup/popup.html | 15 +++++ popup/popup.js | 125 +++++++++++++++++++++++++++++++++-- 21 files changed, 511 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f0714a3..9e0a6da 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ Goose Highlighter is a browser extension that allows you to highlight custom wor - **Custom Colors:** Set background and foreground for each list and individual word. - **Bulk Add:** Paste multiple words at once. - **Enable/Disable:** Toggle highlighting globally, per list, or per word. -- **Import/Export:** Backup or share your highlight lists as JSON files. +- **Site Exceptions:** Add websites to an exceptions list to disable highlighting on specific sites. +- **Import/Export:** Backup or share your highlight lists and exceptions as JSON files. ## Install diff --git a/_locales/de/messages.json b/_locales/de/messages.json index fea45c1..3b79f29 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -94,5 +94,26 @@ }, "options": { "message": "Optionen" }, "match_case": { "message": "Groß-/Kleinschreibung beachten" }, - "match_whole": { "message": "Ganzes Wort übereinstimmen" } + "match_whole": { "message": "Ganzes Wort übereinstimmen" }, + "site_exceptions": { + "message": "Website-Ausnahmen" + }, + "add_exception": { + "message": "Zu Ausnahmen hinzufügen" + }, + "remove_exception": { + "message": "Aus Ausnahmen entfernen" + }, + "manage_exceptions": { + "message": "Verwalten" + }, + "exceptions_list": { + "message": "Ausnahme-Websites:" + }, + "clear_all": { + "message": "Alle löschen" + }, + "confirm_clear_exceptions": { + "message": "Möchten Sie wirklich alle Ausnahmen löschen?" + } } \ No newline at end of file diff --git a/_locales/en/messages.json b/_locales/en/messages.json index dbbdd91..47f8694 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "Match Whole Word" + }, + "site_exceptions": { + "message": "Site Exceptions" + }, + "add_exception": { + "message": "Add to Exceptions" + }, + "remove_exception": { + "message": "Remove from Exceptions" + }, + "manage_exceptions": { + "message": "Manage" + }, + "exceptions_list": { + "message": "Exception Sites:" + }, + "clear_all": { + "message": "Clear All" + }, + "confirm_clear_exceptions": { + "message": "Are you sure you want to clear all exceptions?" } } \ No newline at end of file diff --git a/_locales/es/messages.json b/_locales/es/messages.json index f618259..e5202d7 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "Coincidir palabra completa" + }, + "site_exceptions": { + "message": "Excepciones de sitios" + }, + "add_exception": { + "message": "Agregar a excepciones" + }, + "remove_exception": { + "message": "Quitar de excepciones" + }, + "manage_exceptions": { + "message": "Gestionar" + }, + "exceptions_list": { + "message": "Sitios de excepción:" + }, + "clear_all": { + "message": "Limpiar todo" + }, + "confirm_clear_exceptions": { + "message": "¿Estás seguro de que deseas limpiar todas las excepciones?" } } \ No newline at end of file diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 7c70fe4..0dff166 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "Mot entier seulement" + }, + "site_exceptions": { + "message": "Exceptions de sites" + }, + "add_exception": { + "message": "Ajouter aux exceptions" + }, + "remove_exception": { + "message": "Retirer des exceptions" + }, + "manage_exceptions": { + "message": "Gérer" + }, + "exceptions_list": { + "message": "Sites d'exception :" + }, + "clear_all": { + "message": "Tout effacer" + }, + "confirm_clear_exceptions": { + "message": "Êtes-vous sûr de vouloir effacer toutes les exceptions ?" } } \ No newline at end of file diff --git a/_locales/hi/messages.json b/_locales/hi/messages.json index 0c14435..0bc0da8 100644 --- a/_locales/hi/messages.json +++ b/_locales/hi/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "पूरा शब्द मिलाएं" + }, + "site_exceptions": { + "message": "साइट अपवाद" + }, + "add_exception": { + "message": "अपवादों में जोड़ें" + }, + "remove_exception": { + "message": "अपवादों से हटाएं" + }, + "manage_exceptions": { + "message": "प्रबंधित करें" + }, + "exceptions_list": { + "message": "अपवाद साइटें:" + }, + "clear_all": { + "message": "सभी साफ करें" + }, + "confirm_clear_exceptions": { + "message": "क्या आप वाकई सभी अपवादों को साफ करना चाहते हैं?" } } \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index a832aac..d6a7d13 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "Solo parola intera" + }, + "site_exceptions": { + "message": "Eccezioni siti" + }, + "add_exception": { + "message": "Aggiungi alle eccezioni" + }, + "remove_exception": { + "message": "Rimuovi dalle eccezioni" + }, + "manage_exceptions": { + "message": "Gestisci" + }, + "exceptions_list": { + "message": "Siti di eccezione:" + }, + "clear_all": { + "message": "Cancella tutto" + }, + "confirm_clear_exceptions": { + "message": "Sei sicuro di voler cancellare tutte le eccezioni?" } } \ No newline at end of file diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index b93a981..9b27a67 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "完全一致" + }, + "site_exceptions": { + "message": "サイト例外" + }, + "add_exception": { + "message": "例外に追加" + }, + "remove_exception": { + "message": "例外から削除" + }, + "manage_exceptions": { + "message": "管理" + }, + "exceptions_list": { + "message": "例外サイト:" + }, + "clear_all": { + "message": "すべてクリア" + }, + "confirm_clear_exceptions": { + "message": "すべての例外をクリアしてもよろしいですか?" } } \ No newline at end of file diff --git a/_locales/ko/messages.json b/_locales/ko/messages.json index 50ebccd..c5be855 100644 --- a/_locales/ko/messages.json +++ b/_locales/ko/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "전체 단어 일치" + }, + "site_exceptions": { + "message": "사이트 예외" + }, + "add_exception": { + "message": "예외에 추가" + }, + "remove_exception": { + "message": "예외에서 제거" + }, + "manage_exceptions": { + "message": "관리" + }, + "exceptions_list": { + "message": "예외 사이트:" + }, + "clear_all": { + "message": "모두 지우기" + }, + "confirm_clear_exceptions": { + "message": "모든 예외를 지우시겠습니까?" } } \ No newline at end of file diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index 8cf63a9..594334c 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "Alleen volledig woord" + }, + "site_exceptions": { + "message": "Site-uitzonderingen" + }, + "add_exception": { + "message": "Toevoegen aan uitzonderingen" + }, + "remove_exception": { + "message": "Verwijderen uit uitzonderingen" + }, + "manage_exceptions": { + "message": "Beheren" + }, + "exceptions_list": { + "message": "Uitzondering sites:" + }, + "clear_all": { + "message": "Alles wissen" + }, + "confirm_clear_exceptions": { + "message": "Weet je zeker dat je alle uitzonderingen wilt wissen?" } } \ No newline at end of file diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 804f134..aaf9aee 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "Tylko całe słowo" + }, + "site_exceptions": { + "message": "Wyjątki stron" + }, + "add_exception": { + "message": "Dodaj do wyjątków" + }, + "remove_exception": { + "message": "Usuń z wyjątków" + }, + "manage_exceptions": { + "message": "Zarządzaj" + }, + "exceptions_list": { + "message": "Strony wyjątków:" + }, + "clear_all": { + "message": "Wyczyść wszystko" + }, + "confirm_clear_exceptions": { + "message": "Czy na pewno chcesz wyczyścić wszystkie wyjątki?" } } \ No newline at end of file diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json index 02bb2df..a463bdf 100644 --- a/_locales/pt_BR/messages.json +++ b/_locales/pt_BR/messages.json @@ -94,5 +94,26 @@ }, "options": { "message": "Opções" }, "match_case": { "message": "Diferenciar maiúsculas/minúsculas" }, - "match_whole": { "message": "Palavra inteira" } + "match_whole": { "message": "Palavra inteira" }, + "site_exceptions": { + "message": "Exceções de sites" + }, + "add_exception": { + "message": "Adicionar às exceções" + }, + "remove_exception": { + "message": "Remover das exceções" + }, + "manage_exceptions": { + "message": "Gerenciar" + }, + "exceptions_list": { + "message": "Sites de exceção:" + }, + "clear_all": { + "message": "Limpar tudo" + }, + "confirm_clear_exceptions": { + "message": "Tem certeza de que deseja limpar todas as exceções?" + } } \ No newline at end of file diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 94505ea..ca8bda2 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "Слово целиком" + }, + "site_exceptions": { + "message": "Сайты-исключения" + }, + "add_exception": { + "message": "Добавить в исключения" + }, + "remove_exception": { + "message": "Удалить из исключений" + }, + "manage_exceptions": { + "message": "Управление" + }, + "exceptions_list": { + "message": "Сайты-исключения:" + }, + "clear_all": { + "message": "Очистить все" + }, + "confirm_clear_exceptions": { + "message": "Вы уверены, что хотите очистить все исключения?" } } \ No newline at end of file diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index 9be70a1..3a7fdce 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "Tüm kelimeyle eşleş" + }, + "site_exceptions": { + "message": "Site İstisnaları" + }, + "add_exception": { + "message": "İstisnalara Ekle" + }, + "remove_exception": { + "message": "İstisnalardan Çıkar" + }, + "manage_exceptions": { + "message": "Yönet" + }, + "exceptions_list": { + "message": "İstisna Siteleri:" + }, + "clear_all": { + "message": "Hepsini Temizle" + }, + "confirm_clear_exceptions": { + "message": "Tüm istisnaları temizlemek istediğinizden emin misiniz?" } } \ No newline at end of file diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 80005c1..68f440d 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -100,5 +100,26 @@ }, "match_whole": { "message": "全词匹配" + }, + "site_exceptions": { + "message": "网站例外" + }, + "add_exception": { + "message": "添加到例外" + }, + "remove_exception": { + "message": "从例外中移除" + }, + "manage_exceptions": { + "message": "管理" + }, + "exceptions_list": { + "message": "例外网站:" + }, + "clear_all": { + "message": "清除全部" + }, + "confirm_clear_exceptions": { + "message": "您确定要清除所有例外吗?" } } \ No newline at end of file diff --git a/background.js b/background.js index 0515f6f..b602714 100644 --- a/background.js +++ b/background.js @@ -7,4 +7,12 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { console.warn('Injection failed:', err); }); } +}); + +chrome.runtime.onInstalled.addListener(() => { + chrome.storage.local.get(['exceptionsList'], (result) => { + if (!result.exceptionsList) { + chrome.storage.local.set({ exceptionsList: [] }); + } + }); }); \ No newline at end of file diff --git a/main.js b/main.js index 2137884..d6d9b8d 100644 --- a/main.js +++ b/main.js @@ -1,5 +1,7 @@ let currentLists = []; let isGlobalHighlightEnabled = true; +let exceptionsList = []; +let isCurrentSiteException = false; let matchCase = false; let matchWhole = false; let styleSheet = null; @@ -9,6 +11,11 @@ function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } +function isCurrentSiteInExceptions() { + const currentHostname = window.location.hostname; + return exceptionsList.includes(currentHostname); +} + function initializeStyleSheet() { if (!styleSheet) { const style = document.createElement('style'); @@ -59,7 +66,7 @@ function processNodes() { observer.disconnect(); clearHighlights(); - if (!isGlobalHighlightEnabled) { + if (!isGlobalHighlightEnabled || isCurrentSiteException) { observer.observe(document.body, { childList: true, subtree: true, @@ -152,13 +159,15 @@ function debounce(func, wait) { } // Initial highlight on load -chrome.storage.local.get(['lists', 'globalHighlightEnabled', 'matchCaseEnabled', 'matchWholeEnabled'], ({ lists, globalHighlightEnabled, matchCaseEnabled, matchWholeEnabled }) => { +chrome.storage.local.get(['lists', 'globalHighlightEnabled', 'matchCaseEnabled', 'matchWholeEnabled', 'exceptionsList'], ({ lists, globalHighlightEnabled, matchCaseEnabled, matchWholeEnabled, exceptionsList: exceptions }) => { if (Array.isArray(lists)) setListsAndUpdate(lists); if (globalHighlightEnabled !== undefined) { isGlobalHighlightEnabled = globalHighlightEnabled; } matchCase = !!matchCaseEnabled; matchWhole = !!matchWholeEnabled; + exceptionsList = Array.isArray(exceptions) ? exceptions : []; + isCurrentSiteException = isCurrentSiteInExceptions(); processNodes(); }); @@ -175,6 +184,12 @@ chrome.runtime.onMessage.addListener((message) => { matchCase = !!message.matchCase; matchWhole = !!message.matchWhole; processNodes(); + } else if (message.type === 'EXCEPTIONS_LIST_UPDATED') { + chrome.storage.local.get('exceptionsList', ({ exceptionsList: exceptions }) => { + exceptionsList = Array.isArray(exceptions) ? exceptions : []; + isCurrentSiteException = isCurrentSiteInExceptions(); + processNodes(); + }); } }); diff --git a/manifest.json b/manifest.json index feb2eb9..9a183f7 100644 --- a/manifest.json +++ b/manifest.json @@ -6,7 +6,8 @@ "default_locale": "en", "permissions": [ "scripting", - "storage" + "storage", + "tabs" ], "host_permissions": [ "" diff --git a/popup/popup.css b/popup/popup.css index a0e4b15..912f013 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -505,4 +505,55 @@ html::-webkit-scrollbar-corner, body::-webkit-scrollbar-corner, #wordList::-webkit-scrollbar-corner { background: var(--scrollbar-bg); +} + +/* Exception Panel Styles */ +.exceptions-panel { + margin-top: 10px; + padding: 12px; + border: 1px solid var(--input-border); + border-radius: var(--border-radius); + background: var(--input-bg); +} + +.exceptions-list { + max-height: 120px; + overflow-y: auto; + margin: 8px 0; + border: 1px solid var(--input-border); + border-radius: 6px; + background: var(--section-bg); +} + +.exception-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + border-bottom: 1px solid var(--highlight-tag-border); + font-size: 12px; +} + +.exception-item:last-child { + border-bottom: none; +} + +.exception-domain { + flex: 1; + word-break: break-all; + margin-right: 8px; +} + +.exception-remove { + background: var(--danger); + color: white; + border: none; + padding: 4px 8px; + border-radius: 4px; + cursor: pointer; + font-size: 10px; +} + +.exception-remove:hover { + background: #d00030; } \ No newline at end of file diff --git a/popup/popup.html b/popup/popup.html index 3001d8b..6785af6 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -29,6 +29,21 @@ +
+
+

Site Exceptions

+
+
+ + +
+ +
+

Highlight Lists

diff --git a/popup/popup.js b/popup/popup.js index c89855d..7e7cd46 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -15,6 +15,8 @@ let globalHighlightEnabled = true; let wordSearchQuery = ''; let matchCaseEnabled = false; let matchWholeEnabled = false; +let exceptionsList = []; +let currentTabHost = ''; function escapeHtml(str) { return str.replace(/[&<>"']/g, function (m) { @@ -33,7 +35,8 @@ async function save() { lists: lists, globalHighlightEnabled: globalHighlightEnabled, matchCaseEnabled, - matchWholeEnabled + matchWholeEnabled, + exceptionsList }); renderLists(); renderWords(); @@ -51,6 +54,7 @@ async function save() { matchCase: matchCaseEnabled, matchWhole: matchWholeEnabled }); + chrome.tabs.sendMessage(tab.id, { type: 'EXCEPTIONS_LIST_UPDATED' }); } } }); @@ -75,15 +79,28 @@ async function load() { lists: [], globalHighlightEnabled: true, matchCaseEnabled: false, - matchWholeEnabled: false + matchWholeEnabled: false, + exceptionsList: [] }); lists = res.lists; globalHighlightEnabled = res.globalHighlightEnabled !== false; matchCaseEnabled = !!res.matchCaseEnabled; matchWholeEnabled = !!res.matchWholeEnabled; + exceptionsList = res.exceptionsList || []; matchCase.checked = matchCaseEnabled; matchWhole.checked = matchWholeEnabled; + try { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (tab && tab.url) { + const url = new URL(tab.url); + currentTabHost = url.hostname; + updateExceptionButton(); + } + } catch (e) { + console.warn('Could not get current tab:', e); + } + if (!lists.length) { lists.push({ id: Date.now(), @@ -96,6 +113,7 @@ async function load() { } renderLists(); renderWords(); + renderExceptions(); document.getElementById('globalHighlightToggle').checked = globalHighlightEnabled; } @@ -229,6 +247,42 @@ function renderWords() { } } +function updateExceptionButton() { + const toggleBtn = document.getElementById('toggleExceptionBtn'); + const btnText = document.getElementById('exceptionBtnText'); + + if (!toggleBtn || !btnText || !currentTabHost) return; + + const isException = exceptionsList.includes(currentTabHost); + + if (isException) { + btnText.textContent = chrome.i18n.getMessage('remove_exception') || 'Remove from Exceptions'; + toggleBtn.className = 'danger'; + toggleBtn.querySelector('i').className = 'fa-solid fa-check'; + } else { + btnText.textContent = chrome.i18n.getMessage('add_exception') || 'Add to Exceptions'; + toggleBtn.className = ''; + toggleBtn.querySelector('i').className = 'fa-solid fa-ban'; + } +} + +function renderExceptions() { + const container = document.getElementById('exceptionsList'); + if (!container) return; + + if (exceptionsList.length === 0) { + container.innerHTML = '
No exceptions
'; + return; + } + + container.innerHTML = exceptionsList.map(domain => + `
+ ${escapeHtml(domain)} + +
` + ).join(''); +} + document.addEventListener('DOMContentLoaded', () => { localizePage(); document.getElementById('selectAllBtn').onclick = () => { @@ -355,7 +409,11 @@ document.addEventListener('DOMContentLoaded', () => { const exportBtn = document.getElementById('exportBtn'); exportBtn.onclick = () => { - const blob = new Blob([JSON.stringify(lists, null, 2)], { type: 'application/json' }); + const exportData = { + lists: lists, + exceptionsList: exceptionsList + }; + const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; @@ -374,11 +432,24 @@ document.addEventListener('DOMContentLoaded', () => { reader.onload = e => { try { const data = JSON.parse(e.target.result); + if (Array.isArray(data)) { + // Old format - just lists lists = data; - currentListIndex = 0; - save(); + } else if (data && typeof data === 'object') { + // New format - object with lists and exceptions + if (Array.isArray(data.lists)) { + lists = data.lists; + } + if (Array.isArray(data.exceptionsList)) { + exceptionsList = data.exceptionsList; + } } + + currentListIndex = 0; + updateExceptionButton(); + renderExceptions(); + save(); } catch (err) { alert(chrome.i18n.getMessage('invalid_json_error:' + err.message)); } @@ -447,5 +518,49 @@ document.addEventListener('DOMContentLoaded', () => { save(); }); + document.getElementById('toggleExceptionBtn').addEventListener('click', () => { + if (!currentTabHost) return; + + const isException = exceptionsList.includes(currentTabHost); + + if (isException) { + exceptionsList = exceptionsList.filter(domain => domain !== currentTabHost); + } else { + exceptionsList.push(currentTabHost); + } + + updateExceptionButton(); + renderExceptions(); + save(); + }); + + document.getElementById('manageExceptionsBtn').addEventListener('click', () => { + const panel = document.getElementById('exceptionsPanel'); + if (panel.style.display === 'none') { + panel.style.display = 'block'; + } else { + panel.style.display = 'none'; + } + }); + + document.getElementById('clearExceptionsBtn').addEventListener('click', () => { + if (confirm(chrome.i18n.getMessage('confirm_clear_exceptions') || 'Clear all exceptions?')) { + exceptionsList = []; + updateExceptionButton(); + renderExceptions(); + save(); + } + }); + + document.getElementById('exceptionsList').addEventListener('click', (e) => { + if (e.target.classList.contains('exception-remove')) { + const domain = e.target.dataset.domain; + exceptionsList = exceptionsList.filter(d => d !== domain); + updateExceptionButton(); + renderExceptions(); + save(); + } + }); + load(); });