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

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