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
This commit is contained in:
2026-01-12 17:46:42 +01:00
commit 216a972fef
180 changed files with 88019 additions and 0 deletions

View File

@@ -0,0 +1,320 @@
import { BlacklistStorageManager } from './BlacklistStorageManager.js';
import BrandLogoRegistry from './BrandLogoRegistry.js';
/**
* BlacklistPanelManager - Manages the Blacklist Panel UI and functionality
*
* Provides UI for adding/removing brands from the blacklist and displays
* the current list of blacklisted brands with their logos.
*
* Requirements: 2.1, 2.4, 3.1, 3.2, 3.3, 3.4
*/
export class BlacklistPanelManager {
constructor(blacklistStorage = null, logoRegistry = null) {
this.blacklistStorage = blacklistStorage || new BlacklistStorageManager();
this.logoRegistry = logoRegistry || new BrandLogoRegistry();
this.container = null;
this.eventBus = null;
// Initialize event bus connection
this.initializeEventBus();
}
/**
* Initializes connection to the global event bus
*/
initializeEventBus() {
if (typeof window !== 'undefined' && window.amazonExtEventBus) {
this.eventBus = window.amazonExtEventBus;
this.setupEventListeners();
} else {
setTimeout(() => {
if (typeof window !== 'undefined' && window.amazonExtEventBus) {
this.eventBus = window.amazonExtEventBus;
this.setupEventListeners();
}
}, 100);
}
}
/**
* Sets up event listeners for blacklist updates
*/
setupEventListeners() {
if (!this.eventBus) return;
this.eventBus.on('blacklist:updated', () => {
this.loadBrands();
});
}
/**
* Emits an event through the event bus
* @param {string} eventName - Event name
* @param {*} data - Event data
*/
emitEvent(eventName, data) {
if (this.eventBus) {
this.eventBus.emit(eventName, data);
}
}
/**
* Creates the Blacklist Panel content HTML structure
* Requirement 2.1: Display input field for brand names
* @returns {HTMLElement}
*/
createBlacklistContent() {
const container = document.createElement('div');
container.className = 'amazon-ext-blacklist-content';
container.innerHTML = `
<div class="blacklist-header">
<h2>Blacklist</h2>
<p class="blacklist-description">Markennamen hinzufügen, um Produkte zu markieren</p>
</div>
<div class="add-brand-form">
<input
type="text"
class="brand-input"
placeholder="Markenname eingeben (z.B. Nike, Adidas)..."
aria-label="Brand name input"
/>
<button class="add-brand-btn" type="button">Hinzufügen</button>
</div>
<div class="blacklist-message" style="display: none;"></div>
<div class="brand-list-container">
<div class="brand-list"></div>
</div>
`;
this.container = container;
this.attachEventListeners();
this.loadBrands();
return container;
}
/**
* Attaches event listeners to the Blacklist Panel elements
*/
attachEventListeners() {
if (!this.container) return;
const input = this.container.querySelector('.brand-input');
const addBtn = this.container.querySelector('.add-brand-btn');
if (addBtn) {
addBtn.addEventListener('click', () => this.handleAddBrand());
}
if (input) {
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.handleAddBrand();
}
});
}
}
/**
* Handles adding a new brand to the blacklist
* Requirements: 2.2, 2.4, 2.5, 2.6
*/
async handleAddBrand() {
if (!this.container) return;
const input = this.container.querySelector('.brand-input');
const brandName = input ? input.value.trim() : '';
// Clear previous messages
this.clearMessage();
if (!brandName) {
this.showMessage('Bitte einen Markennamen eingeben', 'error');
if (input) input.focus();
return;
}
try {
await this.blacklistStorage.addBrand(brandName);
// Requirement 2.4: Clear input field after saving
if (input) {
input.value = '';
}
this.showMessage(`"${brandName}" zur Blacklist hinzugefügt`, 'success');
this.loadBrands();
} catch (error) {
// Requirement 2.5: Display message for duplicate entry
if (error.message === 'Brand already exists') {
this.showMessage('Diese Marke ist bereits in der Blacklist', 'error');
} else if (error.message.includes('quota')) {
this.showMessage('Speicher voll. Bitte löschen Sie einige Marken.', 'error');
} else {
this.showMessage('Fehler beim Speichern', 'error');
}
if (input) input.focus();
}
}
/**
* Loads and renders all blacklisted brands
* Requirement 3.1: Display all saved brand names in a list
*/
async loadBrands() {
if (!this.container) return;
try {
const brands = await this.blacklistStorage.getBrands();
this.renderBrandList(brands);
} catch (error) {
console.error('Error loading brands:', error);
this.showMessage('Fehler beim Laden der Marken', 'error');
}
}
/**
* Renders the brand list in the Blacklist Panel
* Requirements: 3.1, 3.2, 3.3
* @param {Array} brands - Array of blacklisted brands
*/
renderBrandList(brands) {
if (!this.container) return;
const listContainer = this.container.querySelector('.brand-list');
if (!listContainer) return;
if (!brands || brands.length === 0) {
listContainer.innerHTML = '<p class="empty-message">Keine Marken in der Blacklist</p>';
return;
}
// Requirement 3.1: Display all saved brand names
// Requirement 3.2: Display each brand with its logo
// Requirement 3.3: Provide delete button for each brand
listContainer.innerHTML = brands.map(brand => `
<div class="brand-item" data-id="${brand.id}">
<div class="brand-logo">
${this.logoRegistry.getLogo(brand.name)}
</div>
<span class="brand-name">${this.escapeHtml(brand.name)}</span>
<button class="delete-brand-btn" data-id="${brand.id}" aria-label="Marke löschen">×</button>
</div>
`).join('');
// Attach delete button event listeners
this.attachDeleteListeners(listContainer);
}
/**
* Attaches event listeners to delete buttons
* @param {HTMLElement} listContainer - Brand list container element
*/
attachDeleteListeners(listContainer) {
const deleteButtons = listContainer.querySelectorAll('.delete-brand-btn');
deleteButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
const brandId = e.target.dataset.id;
if (brandId) {
this.handleDeleteBrand(brandId);
}
});
});
}
/**
* Handles deleting a brand from the blacklist
* Requirement 3.4: Remove brand from storage and update display
* @param {string} brandId - Brand ID to delete
*/
async handleDeleteBrand(brandId) {
try {
const brands = await this.blacklistStorage.getBrands();
const brand = brands.find(b => b.id === brandId);
const brandName = brand ? brand.name : '';
await this.blacklistStorage.deleteBrand(brandId);
if (brandName) {
this.showMessage(`"${brandName}" entfernt`, 'success');
}
this.loadBrands();
} catch (error) {
console.error('Error deleting brand:', error);
this.showMessage('Fehler beim Löschen der Marke', 'error');
}
}
/**
* Shows a feedback message (success or error)
* @param {string} text - Message text
* @param {string} type - Message type ('success' or 'error')
*/
showMessage(text, type) {
if (!this.container) return;
const messageEl = this.container.querySelector('.blacklist-message');
if (!messageEl) return;
messageEl.textContent = text;
messageEl.className = `blacklist-message ${type}`;
messageEl.style.display = 'block';
// Auto-hide after 3 seconds
setTimeout(() => {
if (messageEl) {
messageEl.style.display = 'none';
}
}, 3000);
}
/**
* Clears the feedback message
*/
clearMessage() {
if (!this.container) return;
const messageEl = this.container.querySelector('.blacklist-message');
if (messageEl) {
messageEl.style.display = 'none';
messageEl.textContent = '';
}
}
/**
* Shows the Blacklist Panel (called when menu item is clicked)
*/
showBlacklistPanel() {
this.loadBrands();
}
/**
* Hides the Blacklist Panel (called when menu is closed)
*/
hideBlacklistPanel() {
// Cleanup if needed
}
/**
* Escapes HTML special characters to prevent XSS
* @param {string} text - Text to escape
* @returns {string} Escaped text
*/
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}