Files
ebaysnipeextension/src/BlacklistPanelManager.js
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

321 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}