feat: global on off toggle

This commit is contained in:
2025-06-23 11:20:51 +03:00
parent 2d0878f562
commit 428c97b3c5
7 changed files with 176 additions and 15 deletions

11
README.md Normal file
View File

@@ -0,0 +1,11 @@
# Goose Highlighter
Goose Highlighter is a browser extension that allows you to highlight custom words and phrases on any webpage. Organize your highlights into lists, customize their appearance, and toggle highlighting or theme modes with ease.
## Features
- **Multiple Highlight Lists:** Organize words into separate lists.
- **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.

View File

@@ -82,5 +82,8 @@
}, },
"add_words": { "add_words": {
"message": "Add Words" "message": "Add Words"
},
"global_highlight_toggle": {
"message": "Enable"
} }
} }

View File

@@ -82,5 +82,8 @@
}, },
"add_words": { "add_words": {
"message": "Добавить слова" "message": "Добавить слова"
},
"global_highlight_toggle": {
"message": "Вкл"
} }
} }

View File

@@ -1,4 +1,5 @@
let currentLists = []; let currentLists = [];
let isGlobalHighlightEnabled = true;
function escapeRegex(s) { function escapeRegex(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -19,9 +20,18 @@ function clearHighlights() {
function processNodes() { function processNodes() {
observer.disconnect(); observer.disconnect();
clearHighlights(); clearHighlights();
// If global highlighting is disabled, skip processing
if (!isGlobalHighlightEnabled) {
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
return;
}
const textNodes = []; const textNodes = [];
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode: node => { acceptNode: node => {
@@ -35,7 +45,6 @@ function processNodes() {
while (walker.nextNode()) textNodes.push(walker.currentNode); while (walker.nextNode()) textNodes.push(walker.currentNode);
const activeWords = []; const activeWords = [];
for (const list of currentLists) { for (const list of currentLists) {
if (!list.active) continue; if (!list.active) continue;
for (const word of list.words) { for (const word of list.words) {
@@ -92,8 +101,12 @@ function debounce(func, wait) {
} }
// Initial highlight on load // Initial highlight on load
chrome.storage.local.get("lists", ({ lists }) => { chrome.storage.local.get(["lists", "globalHighlightEnabled"], ({ lists, globalHighlightEnabled }) => {
if (Array.isArray(lists)) setListsAndUpdate(lists); if (Array.isArray(lists)) setListsAndUpdate(lists);
if (globalHighlightEnabled !== undefined) {
isGlobalHighlightEnabled = globalHighlightEnabled;
}
processNodes(); // Initial processing
}); });
// Listen for updates from the popup and re-apply highlights // Listen for updates from the popup and re-apply highlights
@@ -102,6 +115,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
chrome.storage.local.get("lists", ({ lists }) => { chrome.storage.local.get("lists", ({ lists }) => {
if (Array.isArray(lists)) setListsAndUpdate(lists); if (Array.isArray(lists)) setListsAndUpdate(lists);
}); });
} else if (message.type === "GLOBAL_TOGGLE_UPDATED") {
isGlobalHighlightEnabled = message.enabled;
processNodes();
} }
}); });
@@ -112,4 +128,5 @@ observer.observe(document.body, {
subtree: true, subtree: true,
characterData: true characterData: true
}); });
window.addEventListener('scroll', debouncedProcessNodes);
window.addEventListener('scroll', debouncedProcessNodes);

View File

@@ -6,7 +6,7 @@
--button-bg: #222; --button-bg: #222;
--button-hover: #444; --button-hover: #444;
--button-text: white; --button-text: white;
--accent: #ffeb3b; --accent: #ec9c23;
--accent-text: #000; --accent-text: #000;
--highlight-tag: #292929; --highlight-tag: #292929;
--highlight-tag-border: #444; --highlight-tag-border: #444;
@@ -16,7 +16,7 @@
--border-radius: 12px; --border-radius: 12px;
--section-bg: #111; --section-bg: #111;
--switch-bg: #444; --switch-bg: #444;
--checkbox-accent: #ffeb3b; --checkbox-accent: #ec9c23;
--checkbox-border: #666; --checkbox-border: #666;
} }
@@ -298,4 +298,86 @@ input[type="file"] {
font-weight: normal; font-weight: normal;
margin-left: -8px; margin-left: -8px;
margin-right: -8px; margin-right: -8px;
}
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: var(--bg-color);
border-radius: 12px;
color: var(--fg-color);
font-weight: bold;
font-size: 16px;
margin-bottom: 16px;
}
.icon-toggles {
display: flex;
gap: 12px;
}
.icon-toggle {
cursor: pointer;
font-size: 18px;
color: #ffa500;
display: flex;
align-items: center;
transition: color 0.2s;
}
.icon-toggle:hover {
color: #ffd580;
}
.hidden-toggle {
display: none;
}
.icon-toggle {
cursor: pointer;
font-size: 18px;
color: #ffa500;
display: flex;
align-items: center;
transition: color 0.2s;
}
.icon-toggle:hover {
color: #ffd580;
}
/* GLOBAL HIGHLIGHT ICON: toggle-on/off */
.global-icon::before {
content: "\f204";
/* fa-toggle-off (default) */
}
#globalHighlightToggle:checked+.global-icon::before {
content: "\f205";
/* fa-toggle-on */
}
/* THEME ICON: sun/moon */
.theme-icon::before {
content: "\f185";
/* fa-sun (light mode) */
}
#themeToggle:checked+.theme-icon::before {
content: "\f186";
/* fa-moon (dark mode) */
}
/* Font Awesome fallback settings */
.toggle-icon {
font-family: "Font Awesome 6 Free";
font-weight: 900;
}
label:has(input.switch) {
display: flex;
align-items: center;
gap: 8px;
} }

View File

@@ -12,15 +12,26 @@
<body class="dark"> <body class="dark">
<div class="container"> <div class="container">
<h1><i class="fa-solid fa-marker"></i> <span data-i18n="extension_name">Goose Highlighter</span></h1>
<div class="header-bar">
<span class="title">
<i class="fa-solid fa-highlighter"></i> Goose Highlighter
</span>
<div class="icon-toggles">
<label class="icon-toggle" title="Toggle highlighting">
<input type="checkbox" class="hidden-toggle" id="globalHighlightToggle" />
<i class="toggle-icon global-icon fa-solid"></i>
</label>
<label class="icon-toggle" title="Toggle dark mode">
<input type="checkbox" class="hidden-toggle" id="themeToggle" />
<i class="toggle-icon theme-icon fa-solid"></i>
</label>
</div>
</div>
<div class="section"> <div class="section">
<div class="section-header"> <div class="section-header">
<h2><i class="fa-solid fa-list"></i> <span data-i18n="highlight_lists">Highlight Lists</span></h2> <h2><i class="fa-solid fa-list"></i> <span data-i18n="highlight_lists">Highlight Lists</span></h2>
<label>
<input type="checkbox" id="themeToggle" />
<i class="fa-solid fa-moon"></i> <span data-i18n="dark_mode">Dark Mode</span>
</label>
</div> </div>
<label for="listSelect" data-i18n="select_list">Select List:</label> <label for="listSelect" data-i18n="select_list">Select List:</label>
<select id="listSelect"></select> <select id="listSelect"></select>

View File

@@ -6,11 +6,11 @@ const listActive = document.getElementById("listActive");
const bulkPaste = document.getElementById("bulkPaste"); const bulkPaste = document.getElementById("bulkPaste");
const wordList = document.getElementById("wordList"); const wordList = document.getElementById("wordList");
const importInput = document.getElementById("importInput"); const importInput = document.getElementById("importInput");
let lists = []; let lists = [];
let currentListIndex = 0; let currentListIndex = 0;
let saveTimeout; let saveTimeout;
let selectedCheckboxes = new Set(); let selectedCheckboxes = new Set();
let globalHighlightEnabled = true;
async function debouncedSave() { async function debouncedSave() {
clearTimeout(saveTimeout); clearTimeout(saveTimeout);
@@ -20,7 +20,10 @@ async function debouncedSave() {
} }
async function save() { async function save() {
await chrome.storage.local.set({ lists }); await chrome.storage.local.set({
lists: lists,
globalHighlightEnabled: globalHighlightEnabled
});
renderLists(); renderLists();
renderWords(); renderWords();
@@ -28,14 +31,37 @@ async function save() {
for (let tab of tabs) { for (let tab of tabs) {
if (tab.id) { if (tab.id) {
chrome.tabs.sendMessage(tab.id, { type: "WORD_LIST_UPDATED" }); chrome.tabs.sendMessage(tab.id, { type: "WORD_LIST_UPDATED" });
chrome.tabs.sendMessage(tab.id, {
type: "GLOBAL_TOGGLE_UPDATED",
enabled: globalHighlightEnabled
});
}
}
});
}
async function updateGlobalToggleState() {
await chrome.storage.local.set({ globalHighlightEnabled: globalHighlightEnabled });
chrome.tabs.query({}, function (tabs) {
for (let tab of tabs) {
if (tab.id) {
chrome.tabs.sendMessage(tab.id, {
type: "GLOBAL_TOGGLE_UPDATED",
enabled: globalHighlightEnabled
});
} }
} }
}); });
} }
async function load() { async function load() {
const res = await chrome.storage.local.get("lists"); const res = await chrome.storage.local.get({
lists = res.lists || []; lists: [],
globalHighlightEnabled: true
});
lists = res.lists;
globalHighlightEnabled = res.globalHighlightEnabled !== false; // Default to true if undefined
if (!lists.length) { if (!lists.length) {
lists.push({ lists.push({
id: Date.now(), id: Date.now(),
@@ -48,6 +74,8 @@ async function load() {
} }
renderLists(); renderLists();
renderWords(); renderWords();
document.getElementById("globalHighlightToggle").checked = globalHighlightEnabled;
} }
function renderLists() { function renderLists() {
@@ -179,6 +207,12 @@ document.addEventListener('DOMContentLoaded', () => {
renderWords(); renderWords();
}; };
// Add event listener for the global toggle
document.getElementById("globalHighlightToggle").addEventListener('change', function () {
globalHighlightEnabled = this.checked;
updateGlobalToggleState();
});
wordList.addEventListener("change", e => { wordList.addEventListener("change", e => {
if (e.target.type === "checkbox") { if (e.target.type === "checkbox") {
if (e.target.dataset.index != null) { if (e.target.dataset.index != null) {