mirror of
https://github.com/obsqrbtz/goose-highlighter.git
synced 2026-04-08 20:19:06 +03:00
feat: global on off toggle
This commit is contained in:
11
README.md
Normal file
11
README.md
Normal 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.
|
||||
@@ -82,5 +82,8 @@
|
||||
},
|
||||
"add_words": {
|
||||
"message": "Add Words"
|
||||
},
|
||||
"global_highlight_toggle": {
|
||||
"message": "Enable"
|
||||
}
|
||||
}
|
||||
@@ -82,5 +82,8 @@
|
||||
},
|
||||
"add_words": {
|
||||
"message": "Добавить слова"
|
||||
},
|
||||
"global_highlight_toggle": {
|
||||
"message": "Вкл"
|
||||
}
|
||||
}
|
||||
25
content.js
25
content.js
@@ -1,4 +1,5 @@
|
||||
let currentLists = [];
|
||||
let isGlobalHighlightEnabled = true;
|
||||
|
||||
function escapeRegex(s) {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
@@ -19,9 +20,18 @@ function clearHighlights() {
|
||||
|
||||
function processNodes() {
|
||||
observer.disconnect();
|
||||
|
||||
clearHighlights();
|
||||
|
||||
// If global highlighting is disabled, skip processing
|
||||
if (!isGlobalHighlightEnabled) {
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const textNodes = [];
|
||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
|
||||
acceptNode: node => {
|
||||
@@ -35,7 +45,6 @@ function processNodes() {
|
||||
while (walker.nextNode()) textNodes.push(walker.currentNode);
|
||||
|
||||
const activeWords = [];
|
||||
|
||||
for (const list of currentLists) {
|
||||
if (!list.active) continue;
|
||||
for (const word of list.words) {
|
||||
@@ -92,8 +101,12 @@ function debounce(func, wait) {
|
||||
}
|
||||
|
||||
// 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 (globalHighlightEnabled !== undefined) {
|
||||
isGlobalHighlightEnabled = globalHighlightEnabled;
|
||||
}
|
||||
processNodes(); // Initial processing
|
||||
});
|
||||
|
||||
// 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 }) => {
|
||||
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,
|
||||
characterData: true
|
||||
});
|
||||
window.addEventListener('scroll', debouncedProcessNodes);
|
||||
|
||||
window.addEventListener('scroll', debouncedProcessNodes);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
--button-bg: #222;
|
||||
--button-hover: #444;
|
||||
--button-text: white;
|
||||
--accent: #ffeb3b;
|
||||
--accent: #ec9c23;
|
||||
--accent-text: #000;
|
||||
--highlight-tag: #292929;
|
||||
--highlight-tag-border: #444;
|
||||
@@ -16,7 +16,7 @@
|
||||
--border-radius: 12px;
|
||||
--section-bg: #111;
|
||||
--switch-bg: #444;
|
||||
--checkbox-accent: #ffeb3b;
|
||||
--checkbox-accent: #ec9c23;
|
||||
--checkbox-border: #666;
|
||||
}
|
||||
|
||||
@@ -298,4 +298,86 @@ input[type="file"] {
|
||||
font-weight: normal;
|
||||
margin-left: -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;
|
||||
}
|
||||
@@ -12,15 +12,26 @@
|
||||
|
||||
<body class="dark">
|
||||
<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-header">
|
||||
<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>
|
||||
<label for="listSelect" data-i18n="select_list">Select List:</label>
|
||||
<select id="listSelect"></select>
|
||||
|
||||
@@ -6,11 +6,11 @@ const listActive = document.getElementById("listActive");
|
||||
const bulkPaste = document.getElementById("bulkPaste");
|
||||
const wordList = document.getElementById("wordList");
|
||||
const importInput = document.getElementById("importInput");
|
||||
|
||||
let lists = [];
|
||||
let currentListIndex = 0;
|
||||
let saveTimeout;
|
||||
let selectedCheckboxes = new Set();
|
||||
let globalHighlightEnabled = true;
|
||||
|
||||
async function debouncedSave() {
|
||||
clearTimeout(saveTimeout);
|
||||
@@ -20,7 +20,10 @@ async function debouncedSave() {
|
||||
}
|
||||
|
||||
async function save() {
|
||||
await chrome.storage.local.set({ lists });
|
||||
await chrome.storage.local.set({
|
||||
lists: lists,
|
||||
globalHighlightEnabled: globalHighlightEnabled
|
||||
});
|
||||
renderLists();
|
||||
renderWords();
|
||||
|
||||
@@ -28,14 +31,37 @@ async function save() {
|
||||
for (let tab of tabs) {
|
||||
if (tab.id) {
|
||||
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() {
|
||||
const res = await chrome.storage.local.get("lists");
|
||||
lists = res.lists || [];
|
||||
const res = await chrome.storage.local.get({
|
||||
lists: [],
|
||||
globalHighlightEnabled: true
|
||||
});
|
||||
lists = res.lists;
|
||||
globalHighlightEnabled = res.globalHighlightEnabled !== false; // Default to true if undefined
|
||||
|
||||
if (!lists.length) {
|
||||
lists.push({
|
||||
id: Date.now(),
|
||||
@@ -48,6 +74,8 @@ async function load() {
|
||||
}
|
||||
renderLists();
|
||||
renderWords();
|
||||
|
||||
document.getElementById("globalHighlightToggle").checked = globalHighlightEnabled;
|
||||
}
|
||||
|
||||
function renderLists() {
|
||||
@@ -179,6 +207,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
renderWords();
|
||||
};
|
||||
|
||||
// Add event listener for the global toggle
|
||||
document.getElementById("globalHighlightToggle").addEventListener('change', function () {
|
||||
globalHighlightEnabled = this.checked;
|
||||
updateGlobalToggleState();
|
||||
});
|
||||
|
||||
wordList.addEventListener("change", e => {
|
||||
if (e.target.type === "checkbox") {
|
||||
if (e.target.dataset.index != null) {
|
||||
|
||||
Reference in New Issue
Block a user