Sweep: i want to save info about brands that was found on the current page and show the counter in the circle the same as Number of brands in popup
vadykoo opened this issue · comments
Here's the PR! #6.
Actions (click)
- ↻ Restart Sweep
- Install Sweep Configs: Pull Request
Step 1: 🔎 Searching
I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.
Some code snippets I looked at (click to expand). If some file is missing from here, you can mention the path in the ticket description.
Lines 1 to 107 in d35f729
const brandForm = document.getElementById("brandForm"); | |
// Function to create checkbox element for each brand category | |
function createCheckbox(name, enabled) { | |
const checkbox = document.createElement("input"); | |
checkbox.type = "checkbox"; | |
checkbox.name = name; | |
checkbox.checked = enabled; | |
const label = document.createElement("label"); | |
label.textContent = name; | |
label.appendChild(checkbox); | |
brandForm.appendChild(label); | |
// Add event listener to toggle brand category and send message to background script | |
checkbox.addEventListener("change", () => { | |
const toggleData = { | |
name, | |
enabled: checkbox.checked, | |
}; | |
chrome.runtime.sendMessage(toggleData, (response) => { | |
if (chrome.runtime.lastError) { | |
console.error(chrome.runtime.lastError.message); | |
} else { | |
console.log(`Brand category '${name}' toggled: ${checkbox.checked}`); | |
} | |
}); | |
}); | |
} | |
// Fetch brandData from local storage and create checkboxes | |
chrome.storage.local.get({ brandData: null }, ({ brandData }) => { | |
if (!brandData) { | |
brandData = defaultBrandData; // Use default brand data if not found in local storage | |
} | |
brandData.forEach(({ name, enabled }) => { | |
createCheckbox(name, enabled); | |
}); | |
}); | |
// Send a message to the background script when the popup is opened | |
document.addEventListener('DOMContentLoaded', () => { | |
chrome.runtime.sendMessage({ action: 'fetchBrandData' }); | |
}); | |
const fetchBrandDataButton = document.getElementById("fetchBrandDataButton"); | |
const brandCount = document.getElementById("brandCount"); | |
fetchBrandDataButton.addEventListener("click", () => { | |
chrome.storage.local.remove('brandData', function() { | |
console.log('brandData has been removed from local storage and updated from GitHub'); | |
}); | |
chrome.runtime.sendMessage({ action: 'fetchBrandData' }, (response) => { | |
if (chrome.runtime.lastError) { | |
console.error(chrome.runtime.lastError.message); | |
} else { | |
// Force fetch the brand data again and update the brand count | |
chrome.storage.local.set({ fetchTime: null }, () => { | |
brandCount.textContent = `Number of brands on server: ${response.brandCount}`; | |
}); | |
} | |
}); | |
}); | |
chrome.storage.local.get({ brandData: null, fetchTime: null }, ({ brandData, fetchTime }) => { | |
if (brandData) { | |
const totalBrandsElement = document.getElementById("totalBrands"); | |
const lastUpdatedElement = document.getElementById("lastUpdated"); | |
// Calculate the total number of brands | |
let totalBrands = 0; | |
brandData.forEach(category => { | |
totalBrands += category.names.length; | |
}); | |
document.getElementById('totalBrands').textContent = totalBrands; | |
if (fetchTime) { | |
const lastUpdatedDate = new Date(fetchTime); | |
lastUpdatedElement.textContent = `Last updated: ${lastUpdatedDate.toLocaleString()}`; | |
} | |
} | |
}); | |
const toggleExtensionButton = document.getElementById("toggleExtension"); | |
function updateToggleButton() { | |
chrome.storage.local.get({ extensionEnabled: true }, ({ extensionEnabled }) => { | |
toggleExtensionButton.textContent = extensionEnabled ? 'ON' : 'OFF'; | |
}); | |
} | |
toggleExtensionButton.addEventListener("click", () => { | |
chrome.storage.local.get({ extensionEnabled: true }, ({ extensionEnabled }) => { | |
// Toggle the extensionEnabled value | |
chrome.storage.local.set({ extensionEnabled: !extensionEnabled }, () => { | |
console.log(`Extension toggled: ${!extensionEnabled}`); | |
updateToggleButton(); | |
}); | |
}); | |
}); | |
boykottRussianBrands/content_script.js
Lines 11 to 333 in d35f729
this.root = new TrieNode(); | |
} | |
insert(word, brand) { | |
let node = this.root; | |
for (let char of word) { | |
if (!node.children[char]) { | |
node.children[char] = new TrieNode(); | |
} | |
node = node.children[char]; | |
} | |
node.endOfWord = brand; | |
} | |
search(word) { | |
let node = this.root; | |
for (let char of word) { | |
if (!node.children[char]) { | |
return null; | |
} | |
node = node.children[char]; | |
} | |
return node.endOfWord; | |
} | |
} | |
function addEmojisToTextNode(textNode, brandData) { | |
if (hasEmoji(textNode)) return; | |
const trie = new Trie(); | |
brandData.forEach((brandCategory) => { | |
if (brandCategory.enabled) { | |
brandCategory.names.forEach((brand) => { | |
if (brand.names) { | |
brand.names.forEach((brandName) => { | |
// Split brand name into words | |
const brandWords = brandName.toLowerCase().split(' '); | |
// Insert each word into the trie | |
brandWords.forEach((word) => { | |
trie.insert(word, { name: brandName, category: brandCategory, brand: brand }); | |
}); | |
}); | |
} | |
}); | |
} | |
}); | |
const words = textNode.nodeValue.split(' '); | |
let matchedBrandWords = []; | |
for (let i = 0; i < words.length; i++) { | |
const word = words[i]; | |
const matchedBrand = trie.search(word.toLowerCase()); | |
if (matchedBrand) { | |
matchedBrandWords.push(word); | |
if (matchedBrandWords.join(' ').toLowerCase() === matchedBrand.name.toLowerCase()) { | |
const parent = textNode.parentNode; | |
if (!parent) { | |
break; | |
} | |
const wordIndex = textNode.nodeValue.indexOf(matchedBrandWords.join(' ')); | |
const preMatchTextNode = document.createTextNode(textNode.nodeValue.slice(0, wordIndex)); | |
const postMatchTextNode = document.createTextNode(textNode.nodeValue.slice(wordIndex + matchedBrandWords.join(' ').length)); | |
if (parent && matchedBrand) { | |
parent.insertBefore(preMatchTextNode, textNode); | |
const span = createBrandSpan(matchedBrandWords.join(' '), matchedBrand.category, matchedBrand.brand); | |
parent.insertBefore(span, textNode); | |
const remainingText = textNode.nodeValue.slice(wordIndex + matchedBrandWords.join(' ').length); | |
if (remainingText) { | |
const remainingTextNode = document.createTextNode(remainingText); | |
parent.insertBefore(remainingTextNode, textNode); | |
} | |
parent.removeChild(textNode); | |
} | |
textNode = postMatchTextNode; | |
matchedBrandWords = []; | |
} | |
} else { | |
matchedBrandWords = []; | |
} | |
} | |
} | |
// Function to check if the text node already contains an emoji | |
function hasEmoji(textNode) { | |
const emojiRegex = /(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)/gu; | |
return emojiRegex.test(textNode.nodeValue); | |
} | |
// Function to create a tooltip | |
function createTooltip(brand) { | |
if (!brand.description || !brand.linkSource) { | |
return null; | |
} | |
const tooltip = document.createElement('div'); | |
tooltip.style.all = 'initial'; // Reset all inherited styles | |
tooltip.style.display = 'none'; | |
tooltip.style.position = 'fixed'; // Change this line | |
tooltip.style.top = '100%'; | |
tooltip.style.left = '0'; | |
tooltip.style.width = '240px'; | |
tooltip.style.padding = '16px'; | |
tooltip.style.background = '#e2f8ee'; | |
tooltip.style.color = '#414141'; | |
tooltip.style.fontSize = '14px'; | |
tooltip.style.borderRadius = '8px'; | |
tooltip.style.zIndex = '9999'; | |
let tooltipHTML = ''; | |
if (brand.description) { | |
tooltipHTML += `<p>${brand.description}</p>`; | |
} | |
if (brand.linkSource) { | |
tooltipHTML += `<a href="${brand.linkSource}" target="_blank">Дізнатись більше</a>`; | |
} | |
tooltip.innerHTML = tooltipHTML; | |
tooltip.classList.add('brand-tooltip'); // Add a class to the tooltip | |
// Create a close button | |
const closeButton = document.createElement('button'); | |
closeButton.textContent = 'X'; | |
closeButton.style.position = 'absolute'; | |
closeButton.style.top = '0'; | |
closeButton.style.right = '0'; | |
closeButton.style.background = 'none'; | |
closeButton.style.border = 'none'; | |
closeButton.style.fontSize = '16px'; | |
closeButton.style.cursor = 'pointer'; | |
// Add an event listener to the close button to hide the tooltip when clicked | |
closeButton.addEventListener('click', (event) => { | |
event.stopPropagation(); | |
tooltip.style.display = 'none'; | |
}); | |
// Add the close button to the tooltip | |
tooltip.appendChild(closeButton); | |
return tooltip; | |
} | |
// Function to create a span element for the brand name and emoji | |
function createBrandSpan(match, brandCategory, brand) { | |
const span = document.createElement('span'); | |
span.textContent = `${match} ${brandCategory.emoji}`; | |
span.style.position = 'relative'; | |
span.classList.add('brand-span'); // Add a class to the span for identification | |
span.dataset.brand = JSON.stringify(brand); // Store the brand data in the dataset | |
return span; | |
} | |
// Function to traverse and add emojis to all text nodes on the page | |
function traverseAndAddEmojis(node, brandData) { | |
if (node.nodeType === Node.TEXT_NODE) { | |
addEmojisToTextNode(node, brandData); | |
} else if (node.nodeType === Node.ELEMENT_NODE) { | |
let i = 0; | |
function processNextChild() { | |
if (i < node.childNodes.length) { | |
traverseAndAddEmojis(node.childNodes[i], brandData); | |
i++; | |
processNextChild(); | |
} | |
} | |
requestIdleCallback(processNextChild); | |
} | |
} | |
// Retrieve brandData from local storage or use default values | |
chrome.storage.local.get({ brandData: null, extensionEnabled: true }, ({ brandData, extensionEnabled }) => { | |
if (!extensionEnabled) { | |
console.log('Extension is disabled'); | |
return; | |
} | |
let observer; | |
function processPage() { | |
// Disconnect the old observer, if it exists | |
if (observer) { | |
observer.disconnect(); | |
} | |
let isProcessing = false; | |
let pendingMutations = false; | |
requestIdleCallback(() => { | |
traverseAndAddEmojis(document.body, brandData); | |
}); | |
observer = new MutationObserver((mutationsList) => { | |
if (isProcessing) { | |
pendingMutations = true; | |
return; | |
} | |
isProcessing = true; | |
mutationsList.forEach((mutation) => { | |
if (mutation.type === "childList") { | |
mutation.addedNodes.forEach((node) => { | |
if (node.nodeType === Node.TEXT_NODE) { | |
addEmojisToTextNode(node, brandData); | |
} else if (node.nodeType === Node.ELEMENT_NODE) { | |
traverseAndAddEmojis(node, brandData); | |
} | |
}); | |
} | |
}); | |
isProcessing = false; | |
if (pendingMutations) { | |
pendingMutations = false; | |
observer.takeRecords().forEach(mutation => { | |
if (mutation.type === "childList") { | |
mutation.addedNodes.forEach((node) => { | |
if (node.nodeType === Node.TEXT_NODE) { | |
addEmojisToTextNode(node, brandData); | |
} else if (node.nodeType === Node.ELEMENT_NODE) { | |
traverseAndAddEmojis(node, brandData); | |
} | |
}); | |
} | |
}); | |
} | |
}); | |
observer.observe(document, { childList: true, subtree: true }); | |
} | |
// Call processPage when the page loads | |
window.addEventListener("load", processPage); | |
// Also call processPage when the URL changes | |
window.addEventListener("hashchange", processPage); | |
window.addEventListener("popstate", processPage); | |
}); | |
let hideTooltipTimeout; | |
function addTooltipEventListeners(tooltip, brandSpan) { | |
let isTooltipHovered = false; | |
let isBrandSpanHovered = false; | |
// Mouseover event listener for the tooltip | |
tooltip.addEventListener('mouseover', () => { | |
isTooltipHovered = true; | |
clearTimeout(hideTooltipTimeout); // Cancel the tooltip hide timeout | |
}); | |
// Mouseout event listener for the tooltip | |
tooltip.addEventListener('mouseout', () => { | |
isTooltipHovered = false; | |
checkAndHideTooltip(); | |
}); | |
// Mouseover event listener for the brand span | |
brandSpan.addEventListener('mouseover', () => { | |
isBrandSpanHovered = true; | |
clearTimeout(hideTooltipTimeout); // Cancel the tooltip hide timeout | |
}); | |
// Mouseout event listener for the brand span | |
brandSpan.addEventListener('mouseout', () => { | |
isBrandSpanHovered = false; | |
checkAndHideTooltip(); | |
}); | |
// Prevent click events from propagating to underlying elements | |
tooltip.addEventListener('click', (event) => { | |
event.stopPropagation(); | |
}); | |
// Add click event listener to open the link in a new tab/window | |
const link = tooltip.querySelector('a'); | |
if (link) { | |
link.addEventListener('click', (event) => { | |
event.stopPropagation(); // Prevent click event from reaching underlying elements | |
window.open(link.href, '_blank'); // Open the link in a new tab/window | |
}); | |
} | |
function checkAndHideTooltip() { | |
if (!isTooltipHovered && !isBrandSpanHovered) { | |
hideTooltipTimeout = setTimeout(() => { | |
tooltip.style.display = 'none'; | |
}, 500); // 500ms delay before hiding the tooltip | |
} | |
} | |
} | |
document.body.addEventListener('mouseover', (event) => { | |
if (event.target.classList.contains('brand-span')) { | |
// Check if a tooltip is already displayed | |
const existingTooltip = event.target.querySelector('div'); | |
if (existingTooltip && existingTooltip.style.display === 'block') { | |
return; | |
} | |
// If not, create a new tooltip | |
const brand = JSON.parse(event.target.dataset.brand); | |
const tooltip = createTooltip(brand); | |
if (tooltip) { | |
// Calculate the position of the tooltip | |
const rect = event.target.getBoundingClientRect(); | |
tooltip.style.left = `${rect.left}px`; | |
tooltip.style.top = `${rect.bottom}px`; | |
event.target.appendChild(tooltip); | |
addTooltipEventListeners(tooltip, event.target); // Add event listeners to the tooltip and brand span | |
clearTimeout(hideTooltipTimeout); | |
tooltip.style.display = 'block'; | |
} | |
} |
boykottRussianBrands/background.js
Lines 1 to 111 in d35f729
const defaultBrandData = [ | |
// { | |
// name: "Ukrainian Brands", | |
// enabled: true, | |
// names: ["Чумак", "Prestigio"], // Add more Ukrainian brands here | |
// emoji: "🇺🇦", | |
// }, | |
{ | |
name: "Russian Brands", | |
enabled: true, | |
names: [], // Add more Russian brands here | |
emoji: "❌ ", | |
}, | |
// { | |
// name: "Бренди досі активно працюють в росії", | |
// enabled: false, | |
// names: [], // Add more Russian brands here | |
// emoji: "🟨", | |
// }, | |
// Add more brand categories and their corresponding emojis here | |
]; | |
function saveDefaultBrandDataToStorage() { | |
chrome.storage.local.get({ brandData: null }, ({ brandData }) => { | |
if (!brandData) { | |
// If brandData is not in local storage, use the defaultBrandData | |
brandData = defaultBrandData; | |
// Save the defaultBrandData to local storage | |
chrome.storage.local.set({ brandData }); | |
} | |
}); | |
} | |
// Call the function to save defaultBrandData to local storage | |
fetchBrandDataFromGithub(); | |
saveDefaultBrandDataToStorage(); | |
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | |
if (message.action === 'fetchBrandData') { | |
fetchBrandDataFromGithub().then(brandData => { | |
console.log(brandData); | |
sendResponse({ brandCount: brandData[0].names.length }); | |
}); | |
return true; // Indicate that the response will be sent asynchronously | |
} | |
// Check if the message is from the popup | |
if (sender.tab === undefined) { | |
// Save the updated brandData to local storage | |
chrome.storage.local.get({ brandData: null }, ({ brandData }) => { | |
if (!brandData) { | |
brandData = defaultBrandData; // Use default brand data if not found in local storage | |
} | |
const { name, enabled } = message; | |
const brandCategory = brandData.find((category) => category.name === name); | |
if (brandCategory) { | |
brandCategory.enabled = enabled; | |
// Save the updated brandData to local storage | |
chrome.storage.local.set({ brandData }, () => { | |
sendResponse({ success: true }); | |
}); | |
} else { | |
sendResponse({ success: false }); | |
} | |
}); | |
// Return true to indicate that the response will be sent asynchronously | |
return true; | |
} | |
}); | |
function fetchBrandDataFromGithub() { | |
return fetch('https://raw.githubusercontent.com/vadykoo/russianBrandsInUkraine/master/russianInternationalBrandsNew.json') | |
.then(response => response.json()) | |
.then(fetchedBrandData => { | |
return new Promise((resolve, reject) => { | |
chrome.storage.local.get({ brandData: null, fetchTime: null }, ({ brandData, fetchTime }) => { | |
const currentTime = Date.now(); | |
if (!brandData || !fetchTime || currentTime - fetchTime > 24 * 60 * 60 * 1000) { | |
// If brandData is not in local storage or it's older than one day, fetch it again | |
if (!brandData) { | |
brandData = defaultBrandData; // Use default brand data if not found in local storage | |
} | |
// Update the names in the brandData with the fetched data | |
for (let brandCategory of brandData) { | |
if (fetchedBrandData[brandCategory.name]) { | |
// Assuming brandCategory.names is the array you want to filter | |
const uniqueNames = new Set(brandCategory.names); | |
brandCategory.names = [...uniqueNames]; | |
brandCategory.names = fetchedBrandData[brandCategory.name].map(brand => ({ | |
names: brand.name, // brand.name is now an array of brand names | |
description: brand.description, | |
linkSource: brand.linkSource | |
})); | |
} | |
} | |
// Save the updated brandData to local storage | |
chrome.storage.local.set({ brandData }, () => { | |
resolve(brandData); | |
}); | |
} else { | |
resolve(brandData); | |
} | |
}); | |
}); | |
}) | |
.catch(error => console.error('Error:', error)); | |
} |
boykottRussianBrands/popup.html
Lines 1 to 53 in d35f729
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Russian Brands Marker</title> | |
<style> | |
body { | |
background-color: lightblue; | |
} | |
button { | |
background-color: white; | |
border: 2px solid black; | |
color: black; | |
padding: 10px 24px; | |
text-align: center; | |
text-decoration: none; | |
display: inline-block; | |
font-size: 16px; | |
margin: 4px 2px; | |
cursor: pointer; | |
} | |
#totalBrands { | |
display: inline-block; | |
border: 2px solid black; | |
border-radius: 50%; | |
padding: 10px; | |
background-color: white; | |
} | |
label { | |
display: block; | |
margin-bottom: 8px; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Налаштування/Settings</h1> | |
<button id="toggleExtension">Toggle Extension</button> | |
<form id="brandForm"> | |
<!-- Checkbox elements will be dynamically generated here --> | |
</form> | |
<p id="brandText"><span>Number of brands: </span><span id="totalBrands" class="circle"></span></p> | |
<!-- <p id="lastUpdated">Last updated: </p> --> | |
<button id="fetchBrandDataButton">Оновити список брендів/Update list</button> | |
<p id="brandCount"></p> | |
<a href="https://forms.gle/oBgNEt5z7QcMbhjd8" target="_blank">Запропонувати бренд чи залишити відгук</a> | |
<!-- <script src="constants.js"></script> --> | |
<script src="popup.js"></script> |
Step 2: ⌨️ Coding
-
content_script.js
✅ Commit304c915
• Add a counter variable at the beginning of the `addEmojisToTextNode` function. • Increment this counter each time a brand is found. • After the page has been processed, save this counter to the Chrome storage with a key specific to the current page URL. • Also, update a global counter in the storage to keep track of the total number of times each brand has been found across all pages.Sandbox Execution Logs
trunk init
1/3 ✓⡿ Downloading Trunk 1.16.1... ⡿ Downloading Trunk 1.16.1... ⢿ Downloading Trunk 1.16.1... ⣻ Downloading Trunk 1.16.1... ⣽ Downloading Trunk 1.16.1... ⣾ Downloading Trunk 1.16.1... ⣷ Downloading Trunk 1.16.1... ⣯ Downloading Trunk 1.16.1... ⣟ Downloading Trunk 1.16.1... ⡿ Downloading Trunk 1.16.1... ⢿ Downloading Trunk 1.16.1... ⣻ Downloading Trunk 1.16.1... ⣽ Downloading Trunk 1.16.1... ⣾ Downloading Trunk 1.16.1... ✔ Downloading Trunk 1.16.1... done ⡿ Verifying Trunk sha256... ✔ Verifying Trunk sha256... done ⡿ Unpacking Trunk... ✔ Unpacking Trunk... done ✔ 7 linters were enabled (.trunk/trunk.yaml) checkov 2.4.9 (1 json, 1 yaml file) git-diff-check (11 files) oxipng 8.0.0 (3 png files) prettier 3.0.3 (1 html, 3 javascript, 1 json, 1 yaml file) trivy 0.45.1 (1 yaml file) trufflehog 3.57.0 (11 files) yamllint 1.32.0 (1 yaml file) (created .yamllint.yaml) Next Steps 1. Read documentation Our documentation can be found at https://docs.trunk.io 2. Get help and give feedback Join the Trunk community at https://slack.trunk.io
trunk fmt content_script.js
2/3 ✓✔ Formatted content_script.js Re-checking autofixed files... Checked 1 file ✔ No issues
trunk check --fix content_script.js
3/3 ✓Checked 1 file ✔ No issues
-
popup.js
✅ Commitbb4625a
• Retrieve the counter for the current page from the Chrome storage and display it in the popup. • Also, retrieve the global counter from the storage and display it in the popup.Sandbox Execution Logs
trunk init
1/3 ✓⡿ Downloading Trunk 1.16.1... ⡿ Downloading Trunk 1.16.1... ⢿ Downloading Trunk 1.16.1... ⣻ Downloading Trunk 1.16.1... ⣽ Downloading Trunk 1.16.1... ⣾ Downloading Trunk 1.16.1... ⣷ Downloading Trunk 1.16.1... ⣯ Downloading Trunk 1.16.1... ⣟ Downloading Trunk 1.16.1... ⡿ Downloading Trunk 1.16.1... ⢿ Downloading Trunk 1.16.1... ⣻ Downloading Trunk 1.16.1... ⣽ Downloading Trunk 1.16.1... ⣾ Downloading Trunk 1.16.1... ✔ Downloading Trunk 1.16.1... done ⡿ Verifying Trunk sha256... ✔ Verifying Trunk sha256... done ⡿ Unpacking Trunk... ✔ Unpacking Trunk... done ✔ 7 linters were enabled (.trunk/trunk.yaml) checkov 2.4.9 (1 json, 1 yaml file) git-diff-check (11 files) oxipng 8.0.0 (3 png files) prettier 3.0.3 (1 html, 3 javascript, 1 json, 1 yaml file) trivy 0.45.1 (1 yaml file) trufflehog 3.57.0 (11 files) yamllint 1.32.0 (1 yaml file) (created .yamllint.yaml) Next Steps 1. Read documentation Our documentation can be found at https://docs.trunk.io 2. Get help and give feedback Join the Trunk community at https://slack.trunk.io
trunk fmt popup.js
2/3 ✓✔ Formatted popup.js Re-checking autofixed files... Checked 1 file ✔ No issues
trunk check --fix popup.js
3/3 ✓Checked 1 file ✔ No issues
Step 3: 🔁 Code Review
I have finished reviewing the code for completeness. I did not find errors for sweep/brand-counter
.
.
🎉 Latest improvements to Sweep:
- Getting Sweep to run linters before committing! Check out Sweep Sandbox Configs to set it up.
- Added support for self-hosting! Check out Self-hosting Sweep to get started.
- [Self Hosting] Multiple options to compute vector embeddings, configure your .env file using VECTOR_EMBEDDING_SOURCE
💡 To recreate the pull request edit the issue title or description. To tweak the pull request, leave a comment on the pull request.
Join Our Discord