- 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
217 lines
7.1 KiB
JavaScript
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();
|
|
}
|
|
})(); |