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:
320
src/BlacklistPanelManager.js
Normal file
320
src/BlacklistPanelManager.js
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user