feat: add websites to exception list

This commit is contained in:
2025-10-07 14:18:23 +03:00
parent a1701a3504
commit 915add3a4c
21 changed files with 511 additions and 11 deletions

View File

@@ -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

View File

@@ -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?"
}
}

View File

@@ -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?"
}
}

View File

@@ -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?"
}
}

View File

@@ -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 ?"
}
}

View File

@@ -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": "क्या आप वाकई सभी अपवादों को साफ करना चाहते हैं?"
}
}

View File

@@ -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?"
}
}

View File

@@ -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": "すべての例外をクリアしてもよろしいですか?"
}
}

View File

@@ -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": "모든 예외를 지우시겠습니까?"
}
}

View File

@@ -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?"
}
}

View File

@@ -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?"
}
}

View File

@@ -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?"
}
}

View File

@@ -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": "Вы уверены, что хотите очистить все исключения?"
}
}

View File

@@ -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?"
}
}

View File

@@ -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": "您确定要清除所有例外吗?"
}
}

View File

@@ -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: [] });
}
});
});

19
main.js
View File

@@ -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();
});
}
});

View File

@@ -6,7 +6,8 @@
"default_locale": "en",
"permissions": [
"scripting",
"storage"
"storage",
"tabs"
],
"host_permissions": [
"<all_urls>"

View File

@@ -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;
}

View File

@@ -29,6 +29,21 @@
</div>
</div>
<div class="section">
<div class="section-header">
<h2><i class="fa-solid fa-ban"></i> <span data-i18n="site_exceptions">Site Exceptions</span></h2>
</div>
<div class="button-row">
<button id="toggleExceptionBtn"><i class="fa-solid fa-ban"></i> <span id="exceptionBtnText" data-i18n="add_exception">Add to Exceptions</span></button>
<button id="manageExceptionsBtn"><i class="fa-solid fa-list"></i> <span data-i18n="manage_exceptions">Manage</span></button>
</div>
<div id="exceptionsPanel" class="exceptions-panel" style="display: none;">
<h3 data-i18n="exceptions_list">Exception Sites:</h3>
<div id="exceptionsList" class="exceptions-list"></div>
<button id="clearExceptionsBtn" class="danger"><i class="fa-solid fa-trash"></i> <span data-i18n="clear_all">Clear All</span></button>
</div>
</div>
<div class="section">
<div class="section-header">
<h2><i class="fa-solid fa-list"></i> <span data-i18n="highlight_lists">Highlight Lists</span></h2>

View File

@@ -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 = '<div class="exception-item">No exceptions</div>';
return;
}
container.innerHTML = exceptionsList.map(domain =>
`<div class="exception-item">
<span class="exception-domain">${escapeHtml(domain)}</span>
<button class="exception-remove" data-domain="${escapeHtml(domain)}">Remove</button>
</div>`
).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();
});