Files
Kenso Grimm 216a972fef chore: initialize project repository with core extension files
- Add .gitignore to exclude node_modules, dist, logs, and system files
- Add comprehensive project documentation including README, deployment guide, and development setup
- Add .kiro project specifications for amazon-product-bar-extension, appwrite-cloud-storage, appwrite-userid-repair, blacklist-feature, and enhanced-item-management
- Add .kiro steering documents for product, structure, styling, and tech guidelines
- Add VSCode settings configuration for consistent development environment
- Add manifest.json and babel/vite configuration for extension build setup
- Add complete source code implementation including AppWrite integration, storage managers, UI components, and services
- Add comprehensive test suite with Jest configuration and 30+ test files covering all major modules
- Add test HTML files for integration testing and validation
- Add coverage reports and build validation scripts
- Add AppWrite setup and repair documentation for database schema management
- Add migration guides and responsive accessibility implementation documentation
- Establish foundation for Amazon product bar extension with full feature set including blacklist management, enhanced item workflows, and real-time synchronization
2026-01-12 17:46:42 +01:00

217 lines
7.1 KiB
JavaScript

// Amazon Product Bar Extension - Content Script
// This script runs on Amazon search result pages
(function() {
'use strict';
console.log('Amazon Product Bar Extension loaded');
/**
* Checks if the current URL is an Amazon search results page
* @param {string} url - The URL to check
* @returns {boolean} - True if it's a search results page
*/
function isSearchResultsPage(url) {
// Check for Amazon search patterns: /s? or /s/ or search in URL
return url.includes('/s?') || url.includes('/s/') || url.includes('field-keywords') || url.includes('k=');
}
/**
* Finds all product cards within a container element
* @param {Element} container - The container to search within
* @returns {Element[]} - Array of product card elements
*/
function findAllProductCards(container) {
// Try multiple selectors for Amazon product cards
let productCards = container.querySelectorAll('[data-component-type="s-search-result"]');
// Fallback selectors if the main one doesn't work
if (productCards.length === 0) {
productCards = container.querySelectorAll('[data-asin]:not([data-asin=""])');
}
if (productCards.length === 0) {
productCards = container.querySelectorAll('.s-result-item');
}
return Array.from(productCards);
}
/**
* Finds the image container within a product card
* @param {Element} productCard - The product card element
* @returns {Element|null} - The image container element or null if not found
*/
function findImageContainer(productCard) {
// Try multiple selectors for image containers
let imageContainer = productCard.querySelector('.s-image');
if (!imageContainer) {
imageContainer = productCard.querySelector('.a-link-normal img');
if (imageContainer) {
imageContainer = imageContainer.parentElement;
}
}
if (!imageContainer) {
imageContainer = productCard.querySelector('img[data-image-latency]');
if (imageContainer) {
imageContainer = imageContainer.parentElement;
}
}
if (!imageContainer) {
const imgElement = productCard.querySelector('img');
if (imgElement) {
imageContainer = imgElement.parentElement;
}
}
return imageContainer;
}
/**
* Checks if a product card already has a bar injected
* @param {Element} productCard - The product card element to check
* @returns {boolean} - True if the product card already has a bar
*/
function hasBar(productCard) {
// Check if the product card has been processed by looking for our marker attribute
return productCard.hasAttribute('data-ext-processed') ||
productCard.querySelector('.amazon-ext-product-bar') !== null;
}
/**
* Injects a product bar below the image container
* @param {Element} imageContainer - The image container element
*/
function injectBar(imageContainer) {
// Get the parent product card to check for duplicates
const productCard = imageContainer.closest('[data-component-type="s-search-result"]') ||
imageContainer.closest('[data-asin]') ||
imageContainer.closest('.s-result-item');
// Skip if no product card found or bar already exists
if (!productCard || hasBar(productCard)) {
return;
}
// Create the product bar element
const productBar = document.createElement('div');
productBar.className = 'amazon-ext-product-bar';
productBar.setAttribute('data-ext-processed', 'true');
productBar.textContent = '🔥 Product Bar Active'; // Temporary content to make it visible
// Insert the bar after the image container
if (imageContainer.nextSibling) {
imageContainer.parentNode.insertBefore(productBar, imageContainer.nextSibling);
} else {
imageContainer.parentNode.appendChild(productBar);
}
// Mark the product card as processed to prevent duplicates
productCard.setAttribute('data-ext-processed', 'true');
console.log('Product bar injected for product card');
}
/**
* Processes product cards in a given container
* @param {Element} container - The container to process
*/
function processProductCards(container) {
const productCards = findAllProductCards(container);
console.log(`Found ${productCards.length} product cards to process`);
productCards.forEach(productCard => {
// Skip if already processed
if (hasBar(productCard)) {
return;
}
const imageContainer = findImageContainer(productCard);
if (imageContainer) {
injectBar(imageContainer);
} else {
console.warn('No image container found for product card');
}
});
}
/**
* Creates and configures a MutationObserver for dynamic content
* @returns {MutationObserver} - The configured observer
*/
function createDOMObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
// Only process added nodes
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((node) => {
// Only process element nodes
if (node.nodeType === Node.ELEMENT_NODE) {
// Check if the added node is a product card itself
if (node.matches && (node.matches('[data-component-type="s-search-result"]') ||
node.matches('[data-asin]') ||
node.matches('.s-result-item'))) {
const imageContainer = findImageContainer(node);
if (imageContainer) {
injectBar(imageContainer);
}
} else {
// Check if the added node contains product cards
processProductCards(node);
}
}
});
}
});
});
return observer;
}
/**
* Starts observing the DOM for changes
*/
function startObserving() {
const observer = createDOMObserver();
// Observe the entire document body for changes
// This will catch infinite scroll additions and other dynamic content
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('DOM observer started for dynamic content detection');
// Return observer for potential cleanup
return observer;
}
// Initialize the extension
function initialize() {
// Check if we're on a search results page
if (!isSearchResultsPage(window.location.href)) {
console.log('Not a search results page, extension inactive');
return;
}
console.log('Initializing Amazon Product Bar Extension');
// Process existing product cards on page load
processProductCards(document.body);
// Start observing for dynamic content
startObserving();
}
// Wait for DOM to be ready, then initialize
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
// DOM is already ready
initialize();
}
})();