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": {
|
"add_words": {
|
||||||
"message": "Add Words"
|
"message": "Add Words"
|
||||||
|
},
|
||||||
|
"global_highlight_toggle": {
|
||||||
|
"message": "Enable"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,5 +82,8 @@
|
|||||||
},
|
},
|
||||||
"add_words": {
|
"add_words": {
|
||||||
"message": "Добавить слова"
|
"message": "Добавить слова"
|
||||||
|
},
|
||||||
|
"global_highlight_toggle": {
|
||||||
|
"message": "Вкл"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
25
content.js
25
content.js
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user