mirror of
https://github.com/obsqrbtz/goose-highlighter.git
synced 2026-04-09 04:29:09 +03:00
Notify content script about wordlists updates
This commit is contained in:
64
content.js
64
content.js
@@ -1,9 +1,27 @@
|
|||||||
|
let currentLists = [];
|
||||||
|
|
||||||
function escapeRegex(s) {
|
function escapeRegex(s) {
|
||||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlightWords(lists) {
|
function clearHighlights() {
|
||||||
const processNodes = () => {
|
// Remove all <mark> elements added by the highlighter
|
||||||
|
const marks = document.querySelectorAll('mark[data-gh]');
|
||||||
|
for (const mark of marks) {
|
||||||
|
// Replace the <mark> with its text content
|
||||||
|
const parent = mark.parentNode;
|
||||||
|
if (parent) {
|
||||||
|
parent.replaceChild(document.createTextNode(mark.textContent), mark);
|
||||||
|
parent.normalize(); // Merge adjacent text nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processNodes() {
|
||||||
|
observer.disconnect();
|
||||||
|
|
||||||
|
clearHighlights();
|
||||||
|
|
||||||
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 => {
|
||||||
@@ -18,7 +36,7 @@ function highlightWords(lists) {
|
|||||||
|
|
||||||
const activeWords = [];
|
const activeWords = [];
|
||||||
|
|
||||||
for (const list of lists) {
|
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) {
|
||||||
if (!word.active) continue;
|
if (!word.active) continue;
|
||||||
@@ -30,8 +48,7 @@ function highlightWords(lists) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeWords.length === 0) return;
|
if (activeWords.length > 0) {
|
||||||
|
|
||||||
const wordMap = new Map();
|
const wordMap = new Map();
|
||||||
for (const word of activeWords) wordMap.set(word.text.toLowerCase(), word);
|
for (const word of activeWords) wordMap.set(word.text.toLowerCase(), word);
|
||||||
|
|
||||||
@@ -43,25 +60,25 @@ function highlightWords(lists) {
|
|||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.innerHTML = node.nodeValue.replace(pattern, match => {
|
span.innerHTML = node.nodeValue.replace(pattern, match => {
|
||||||
const word = wordMap.get(match.toLowerCase()) || { background: '#ffff00', foreground: '#000000' };
|
const word = wordMap.get(match.toLowerCase()) || { background: '#ffff00', foreground: '#000000' };
|
||||||
return `<mark style="background:${word.background};color:${word.foreground};padding:0 2px;">${match}</mark>`;
|
return `<mark data-gh style="background:${word.background};color:${word.foreground};padding:0 2px;">${match}</mark>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
node.parentNode.replaceChild(span, node);
|
node.parentNode.replaceChild(span, node);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const debouncedProcessNodes = debounce(processNodes, 300);
|
|
||||||
|
|
||||||
debouncedProcessNodes();
|
|
||||||
|
|
||||||
const observer = new MutationObserver(debouncedProcessNodes);
|
|
||||||
observer.observe(document.body, {
|
observer.observe(document.body, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
characterData: true
|
characterData: true
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('scroll', debouncedProcessNodes);
|
const debouncedProcessNodes = debounce(processNodes, 300);
|
||||||
|
|
||||||
|
function setListsAndUpdate(lists) {
|
||||||
|
currentLists = lists;
|
||||||
|
debouncedProcessNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounce helper function
|
// Debounce helper function
|
||||||
@@ -74,6 +91,25 @@ function debounce(func, wait) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initial highlight on load
|
||||||
chrome.storage.local.get("lists", ({ lists }) => {
|
chrome.storage.local.get("lists", ({ lists }) => {
|
||||||
if (Array.isArray(lists)) highlightWords(lists);
|
if (Array.isArray(lists)) setListsAndUpdate(lists);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Listen for updates from the popup and re-apply highlights
|
||||||
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
if (message.type === "WORD_LIST_UPDATED") {
|
||||||
|
chrome.storage.local.get("lists", ({ lists }) => {
|
||||||
|
if (Array.isArray(lists)) setListsAndUpdate(lists);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up observer and scroll handler
|
||||||
|
const observer = new MutationObserver(debouncedProcessNodes);
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
characterData: true
|
||||||
|
});
|
||||||
|
window.addEventListener('scroll', debouncedProcessNodes);
|
||||||
@@ -20,9 +20,17 @@ async function debouncedSave() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
await debouncedSave();
|
await chrome.storage.local.set({ lists });
|
||||||
renderLists();
|
renderLists();
|
||||||
renderWords();
|
renderWords();
|
||||||
|
|
||||||
|
chrome.tabs.query({}, function (tabs) {
|
||||||
|
for (let tab of tabs) {
|
||||||
|
if (tab.id) {
|
||||||
|
chrome.tabs.sendMessage(tab.id, { type: "WORD_LIST_UPDATED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
@@ -177,6 +185,7 @@ wordList.addEventListener("change", e => {
|
|||||||
} else {
|
} else {
|
||||||
selectedCheckboxes.delete(+e.target.dataset.index);
|
selectedCheckboxes.delete(+e.target.dataset.index);
|
||||||
}
|
}
|
||||||
|
renderWords();
|
||||||
} else if (e.target.dataset.activeEdit != null) {
|
} else if (e.target.dataset.activeEdit != null) {
|
||||||
lists[currentListIndex].words[e.target.dataset.activeEdit].active = e.target.checked;
|
lists[currentListIndex].words[e.target.dataset.activeEdit].active = e.target.checked;
|
||||||
save();
|
save();
|
||||||
@@ -277,21 +286,6 @@ wordList.addEventListener("input", e => {
|
|||||||
save();
|
save();
|
||||||
});
|
});
|
||||||
|
|
||||||
wordList.addEventListener("change", e => {
|
|
||||||
if (e.target.type === "checkbox") {
|
|
||||||
if (e.target.dataset.index != null) {
|
|
||||||
if (e.target.checked) {
|
|
||||||
selectedCheckboxes.add(+e.target.dataset.index);
|
|
||||||
} else {
|
|
||||||
selectedCheckboxes.delete(+e.target.dataset.index);
|
|
||||||
}
|
|
||||||
} else if (e.target.dataset.activeEdit != null) {
|
|
||||||
lists[currentListIndex].words[e.target.dataset.activeEdit].active = e.target.checked;
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const exportBtn = document.getElementById("exportBtn");
|
const exportBtn = document.getElementById("exportBtn");
|
||||||
exportBtn.onclick = () => {
|
exportBtn.onclick = () => {
|
||||||
const blob = new Blob([JSON.stringify(lists, null, 2)], { type: "application/json" });
|
const blob = new Blob([JSON.stringify(lists, null, 2)], { type: "application/json" });
|
||||||
|
|||||||
Reference in New Issue
Block a user