Notify content script about wordlists updates

This commit is contained in:
2025-06-23 09:55:45 +03:00
parent 0cffec3df2
commit ad636c0721
2 changed files with 83 additions and 53 deletions

View File

@@ -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);

View File

@@ -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" });