From ad636c0721d5026eb5d56931d23c3a1bc153a608 Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Mon, 23 Jun 2025 09:55:45 +0300 Subject: [PATCH] Notify content script about wordlists updates --- content.js | 110 ++++++++++++++++++++++++++++++++----------------- popup/popup.js | 26 +++++------- 2 files changed, 83 insertions(+), 53 deletions(-) diff --git a/content.js b/content.js index 6f48de7..9a24830 100644 --- a/content.js +++ b/content.js @@ -1,37 +1,54 @@ +let currentLists = []; + function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -function highlightWords(lists) { - const processNodes = () => { - const textNodes = []; - const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { - acceptNode: node => { - if (node.parentNode && node.parentNode.nodeName === 'MARK') return NodeFilter.FILTER_REJECT; - if (node.parentNode && ['SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME'].includes(node.parentNode.nodeName)) return NodeFilter.FILTER_REJECT; - if (!node.nodeValue.trim()) return NodeFilter.FILTER_SKIP; - return NodeFilter.FILTER_ACCEPT; - } - }); - - while (walker.nextNode()) textNodes.push(walker.currentNode); - - const activeWords = []; - - for (const list of lists) { - if (!list.active) continue; - for (const word of list.words) { - if (!word.active) continue; - activeWords.push({ - text: word.wordStr, - background: word.background || list.background, - foreground: word.foreground || list.foreground - }); - } +function clearHighlights() { + // Remove all elements added by the highlighter + const marks = document.querySelectorAll('mark[data-gh]'); + for (const mark of marks) { + // Replace the with its text content + const parent = mark.parentNode; + if (parent) { + parent.replaceChild(document.createTextNode(mark.textContent), mark); + parent.normalize(); // Merge adjacent text nodes } + } +} - if (activeWords.length === 0) return; +function processNodes() { + observer.disconnect(); + clearHighlights(); + + const textNodes = []; + const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { + acceptNode: node => { + if (node.parentNode && node.parentNode.nodeName === 'MARK') return NodeFilter.FILTER_REJECT; + if (node.parentNode && ['SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME'].includes(node.parentNode.nodeName)) return NodeFilter.FILTER_REJECT; + if (!node.nodeValue.trim()) return NodeFilter.FILTER_SKIP; + return NodeFilter.FILTER_ACCEPT; + } + }); + + while (walker.nextNode()) textNodes.push(walker.currentNode); + + const activeWords = []; + + for (const list of currentLists) { + if (!list.active) continue; + for (const word of list.words) { + if (!word.active) continue; + activeWords.push({ + text: word.wordStr, + background: word.background || list.background, + foreground: word.foreground || list.foreground + }); + } + } + + if (activeWords.length > 0) { const wordMap = new Map(); for (const word of activeWords) wordMap.set(word.text.toLowerCase(), word); @@ -43,25 +60,25 @@ function highlightWords(lists) { const span = document.createElement('span'); span.innerHTML = node.nodeValue.replace(pattern, match => { const word = wordMap.get(match.toLowerCase()) || { background: '#ffff00', foreground: '#000000' }; - return `${match}`; + return `${match}`; }); node.parentNode.replaceChild(span, node); } - }; + } - const debouncedProcessNodes = debounce(processNodes, 300); - - debouncedProcessNodes(); - - const observer = new MutationObserver(debouncedProcessNodes); observer.observe(document.body, { childList: true, subtree: true, characterData: true }); +} - window.addEventListener('scroll', debouncedProcessNodes); +const debouncedProcessNodes = debounce(processNodes, 300); + +function setListsAndUpdate(lists) { + currentLists = lists; + debouncedProcessNodes(); } // Debounce helper function @@ -74,6 +91,25 @@ function debounce(func, wait) { }; } +// Initial highlight on load chrome.storage.local.get("lists", ({ lists }) => { - if (Array.isArray(lists)) highlightWords(lists); -}); \ No newline at end of file + 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); \ No newline at end of file diff --git a/popup/popup.js b/popup/popup.js index 1af4b43..2430ac3 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -20,9 +20,17 @@ async function debouncedSave() { } async function save() { - await debouncedSave(); + await chrome.storage.local.set({ lists }); renderLists(); 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() { @@ -177,6 +185,7 @@ wordList.addEventListener("change", e => { } else { selectedCheckboxes.delete(+e.target.dataset.index); } + renderWords(); } else if (e.target.dataset.activeEdit != null) { lists[currentListIndex].words[e.target.dataset.activeEdit].active = e.target.checked; save(); @@ -277,21 +286,6 @@ wordList.addEventListener("input", e => { 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"); exportBtn.onclick = () => { const blob = new Blob([JSON.stringify(lists, null, 2)], { type: "application/json" });