mydealz Script

mydealz Deal-Management: Deals filtern und ausblenden, alle Einstellungen per UI verwalten.

Versión del día 01/01/2025. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name         mydealz Script
// @namespace    http://tampermonkey.net/
// @version      1.8.1
// @description  mydealz Deal-Management: Deals filtern und ausblenden, alle Einstellungen per UI verwalten.
// @author       Moritz Baumeister (https://www.mydealz.de/profile/BobBaumeister), Flo (https://www.mydealz.de/profile/Basics0119)
// @license      MIT
// @match        https://www.mydealz.de/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=mydealz.de
// @grant        none
// ==/UserScript==

// Versions-Änderungen:
// Fix: Touchscreen: UI lässt sich nun auch auf Geräten mit Touchscreen korrekt benutzen. UI schließt sich jetzt nicht mehr komplett, nachdem ein Wort aus der Liste gelöscht wurde.

// Einbinden von Font Awesome für Icons
const fontAwesomeLink = document.createElement('link');
fontAwesomeLink.rel = 'stylesheet';
fontAwesomeLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css';
document.head.appendChild(fontAwesomeLink);

// Add constant for touch detection
const IS_TOUCH_DEVICE = ('ontouchstart' in window) ||
      (navigator.maxTouchPoints > 0) ||
      (navigator.msMaxTouchPoints > 0);

// --- 1. Initial Setup ---
const EXCLUDE_WORDS_KEY = 'excludeWords';
const EXCLUDE_MERCHANTS_KEY = 'excludeMerchantIDs';
const HIDDEN_DEALS_KEY = 'hiddenDeals';
const EXCLUDE_MERCHANTS_DATA_KEY = 'excludeMerchantsData';
const MERCHANT_PAGE_SELECTOR = '.merchant-banner';

// Load data immediately
let excludeWords, excludeMerchantIDs, hiddenDeals;
let dealThatOpenedSettings = null; // Add at top with other globals
let settingsDiv = null;
let isSettingsOpen = false;
let merchantListDiv = null;
let wordsListDiv = null;
let uiClickOutsideHandler = null; // Add at top with other globals
let activeSubUI = null; // Add at top with other globals

try {
    excludeWords = JSON.parse(localStorage.getItem(EXCLUDE_WORDS_KEY)) || [];
    excludeMerchantIDs = JSON.parse(localStorage.getItem(EXCLUDE_MERCHANTS_KEY)) || [];
    hiddenDeals = JSON.parse(localStorage.getItem(HIDDEN_DEALS_KEY)) || [];
} catch (error) {
    excludeWords = [];
    excludeMerchantIDs = [];
    hiddenDeals = [];
}

// --- 1. Core Functions ---
function processArticles() {
    const deals = document.querySelectorAll('article.thread--deal, article.thread--voucher');
    deals.forEach(deal => {
        const dealId = deal.getAttribute('id');

        // Check if manually hidden
        if (hiddenDeals.includes(dealId)) {
            hideDeal(deal);
            return;
        }

        // Check if should be hidden by rules
        if (shouldExcludeArticle(deal)) {
            hideDeal(deal);
            return;
        }

        // Show deal if not excluded
        deal.style.display = 'block';
        deal.style.opacity = '1';
    });
}

// Define observer
const observer = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
        if (mutation.addedNodes.length) {
            processArticles();
            addSettingsButton();
            addHideButtons();
        }
    });
});

// Initialize everything
(function init() {
    processArticles();
    addSettingsButton();
    addHideButtons();

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();

// --- 2. Hilfsfunktionen ---
function shouldExcludeArticle(article) {
    const titleElement = article.querySelector('.thread-title');
    const merchantLink = article.querySelector('a[href*="merchant-id="]');

    if (titleElement && excludeWords.some(word => titleElement.textContent.toLowerCase().includes(word.toLowerCase()))) {
        return true;
    }

    if (merchantLink) {
        const merchantIDMatch = merchantLink.getAttribute('href').match(/merchant-id=(\d+)/);
        if (merchantIDMatch) {
            const merchantID = merchantIDMatch[1];
            if (excludeMerchantIDs.includes(merchantID)) {
                return true;
            }
        }
    }

    return false;
}

function hideDeal(deal) {
    deal.style.display = 'none';
}

// Funktion zum Speichern der ausgeblendeten Deals
function saveHiddenDeals() {
    localStorage.setItem(HIDDEN_DEALS_KEY, JSON.stringify(hiddenDeals));
}

// Speichern der `excludeWords` und `excludeMerchantIDs`
function saveExcludeWords(words) {
    localStorage.setItem('excludeWords', JSON.stringify(words));
}
function loadExcludeWords() {
    return JSON.parse(localStorage.getItem('excludeWords')) || [];
}

// Update save function to handle merchant data objects
function saveExcludeMerchants(merchantsData) {
    // Filter out invalid entries
    const validMerchants = merchantsData.filter(m =>
                                                m &&
                                                typeof m.id !== 'undefined' &&
                                                m.id !== null &&
                                                typeof m.name !== 'undefined' &&
                                                m.name !== null
                                               );

    // Extract IDs for backwards compatibility
    const ids = validMerchants.map(m => m.id);

    localStorage.setItem(EXCLUDE_MERCHANTS_KEY, JSON.stringify(ids));
    localStorage.setItem(EXCLUDE_MERCHANTS_DATA_KEY, JSON.stringify(validMerchants));

    // Update global array
    excludeMerchantIDs = ids;
}

// Load function for merchant data
function loadExcludeMerchants() {
    const merchantsData = JSON.parse(localStorage.getItem(EXCLUDE_MERCHANTS_DATA_KEY)) || [];
    const legacyIds = JSON.parse(localStorage.getItem(EXCLUDE_MERCHANTS_KEY)) || [];

    // Filter out invalid entries
    const validMerchants = merchantsData.filter(m =>
                                                m &&
                                                typeof m.id !== 'undefined' &&
                                                m.id !== null &&
                                                typeof m.name !== 'undefined' &&
                                                m.name !== null
                                               );

    // Convert legacy IDs if needed
    if (validMerchants.length === 0 && legacyIds.length > 0) {
        return legacyIds
            .filter(id => id && typeof id !== 'undefined')
            .map(id => ({ id, name: id }));
    }

    return validMerchants;
}

// Clean up existing data on script init
(function cleanupMerchantData() {
    const merchants = loadExcludeMerchants();
    saveExcludeMerchants(merchants);
})();

// Fügt Event Listener hinzu, um Auto-Speichern zu ermöglichen
function addAutoSaveListeners() {
    // Event Listener für Eingabefelder
    const excludeWordsInput = document.getElementById('excludeWordsInput');
    excludeWordsInput.addEventListener('input', () => {
        const newWords = excludeWordsInput.value.split('\n').map(w => w.trim()).filter(Boolean);
        saveExcludeWords(newWords);
        excludeWords = newWords;
        processArticles();
    });

    const excludeMerchantIDsInput = document.getElementById('excludeMerchantIDsInput');
    excludeMerchantIDsInput.addEventListener('input', () => {
        const newMerchantIDs = excludeMerchantIDsInput.value.split('\n').map(id => id.trim()).filter(Boolean);
        saveExcludeMerchants(newMerchantIDs);
        excludeMerchantIDs = newMerchantIDs;
        processArticles();
    });
}

// Remove highlighting-related event listeners

// UI-Button zum Öffnen der Einstellungen wird nur einmal erstellt
const settingsButton = document.createElement('button');
settingsButton.innerHTML = '⚙️';
settingsButton.style.padding = '10px';
settingsButton.style.background = 'transparent'; // Hintergrund transparent machen
settingsButton.style.color = 'white';
settingsButton.style.border = 'none';
settingsButton.style.borderRadius = '5px';
settingsButton.style.cursor = 'pointer';

// UI-Button zum Verbergen der Deals wird nur einmal erstellt
const hideButton = document.createElement('button');
hideButton.innerHTML = '❌';
hideButton.style.marginLeft = '10px';
hideButton.title = 'Deal verbergen'; // Tooltip hinzufügen
hideButton.style.padding = '10px';
hideButton.style.background = 'transparent'; // Hintergrund transparent machen
hideButton.style.color = 'white';
hideButton.style.border = 'none';
hideButton.style.borderRadius = '5px';
hideButton.style.cursor = 'pointer';

// Funktion, um das Verbergen der Deals zu ermöglichen
hideButton.addEventListener('click', function(event) {
    const deal = event.target.closest('article');
    if (deal) {
        const dealId = deal.getAttribute('id');
        hiddenDeals.push(dealId);
        saveHiddenDeals();
        hideDeal(deal);
    }
});

// Add debug logging to settings button click handler
function addSettingsButton() {
    const deals = document.querySelectorAll('article.thread--deal, article.thread--voucher');

    deals.forEach(deal => {
        if (deal.hasAttribute('data-settings-added')) return;

        const settingsBtn = document.createElement('button');
        settingsBtn.innerHTML = '⚙️';
        settingsBtn.className = 'vote-button overflow--visible settings-button';
        settingsBtn.title = 'Einstellungen';
        settingsBtn.style.cssText = `
            border: none;
            cursor: pointer;
            height: 32px;
            width: 32px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 16px;
            padding: 0;
            background: transparent;
            z-index: 1000;
        `;

        settingsBtn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            dealThatOpenedSettings = deal;
            createSettingsUI();
            return false;
        };

        const settingsContainer = document.createElement('div');
        settingsContainer.className = 'vote-box settings-container';
        settingsContainer.style.cssText = `
            display: flex;
            align-items: center;
            justify-content: center;
            margin-left: 8px;
            height: 32px;
            width: 32px;
            border-radius: 50%;
            background: #f2f2f2;
            border: 1px solid #e4e4e4;
            padding: 4px;
            z-index: 1000;
        `;

        settingsContainer.appendChild(settingsBtn);

        const voteBox = deal.querySelector('.vote-box');
        if (voteBox && voteBox.parentNode) {
            voteBox.parentNode.insertBefore(settingsContainer, voteBox.nextSibling);
            deal.setAttribute('data-settings-added', 'true');
        }
    });
}

// Add to document ready handler
document.addEventListener('DOMContentLoaded', () => {
    processArticles();
    addSettingsButton();
});

function addHideButtons() {
    const deals = document.querySelectorAll('article:not([data-button-added])');

    deals.forEach(deal => {
        if (deal.hasAttribute('data-button-added')) return;

        const voteTemp = deal.querySelector('.cept-vote-temp');
        if (!voteTemp) return;

        // Remove popover
        const popover = voteTemp.querySelector('.popover-origin');
        if (popover) popover.remove();

        const hideButtonContainer = document.createElement('div');
        hideButtonContainer.style.cssText = `
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            display: none;
            z-index: 10001;
            pointer-events: none;
        `;

        const hideButton = document.createElement('button');
        hideButton.innerHTML = '❌';
        hideButton.className = 'vote-button overflow--visible';
        hideButton.title = 'Deal verbergen';
        hideButton.style.cssText = `
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            z-index: 10002;
            background: rgba(255, 255, 255, 0.9);
            border: none;
            border-radius: 50%;
            cursor: pointer;
            padding: 8px;
            width: 32px;
            height: 32px;
            display: flex;
            align-items: center;
            justify-content: center;
            pointer-events: all;
        `;

        // Touch device handling
        if ('ontouchstart' in window) {
            let buttonVisible = false;

            voteTemp.addEventListener('touchstart', (e) => {
                e.preventDefault();
                e.stopPropagation();

                if (!buttonVisible) {
                    buttonVisible = true;
                    hideButtonContainer.style.display = 'block';
                } else {
                    // Second touch on container: hide deal
                    const dealId = deal.getAttribute('id');
                    hiddenDeals.push(dealId);
                    saveHiddenDeals();
                    hideDeal(deal);
                }
            }, true);

            // Reset visibility when touch moves outside
            voteTemp.addEventListener('touchend', () => {
                if (!buttonVisible) {
                    hideButtonContainer.style.display = 'none';
                }
            }, true);
        } else {
            // Desktop hover behavior
            voteTemp.addEventListener('mouseenter', () => {
                hideButtonContainer.style.display = 'block';
            }, true);

            voteTemp.addEventListener('mouseleave', () => {
                hideButtonContainer.style.display = 'none';
            }, true);

            hideButton.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                const dealId = deal.getAttribute('id');
                hiddenDeals.push(dealId);
                saveHiddenDeals();
                hideDeal(deal);
                return false;
            };
        }

        hideButtonContainer.appendChild(hideButton);
        voteTemp.appendChild(hideButtonContainer);
        deal.setAttribute('data-button-added', 'true');
    });
}

// HTML Decoder Funktion hinzufügen
function decodeHtml(html) {
    const txt = document.createElement('textarea');
    txt.innerHTML = html;
    return txt.value;
}

// --- 3. Backup- und Restore-Funktionen ---
function backupData() {
    const backup = {
        excludeWords: excludeWords,
        excludeMerchantIDs: excludeMerchantIDs,
        merchantsData: loadExcludeMerchants()
    };

    const now = new Date();
    const timestamp = now.getFullYear() + '-' +
          String(now.getMonth() + 1).padStart(2, '0') + '-' +
          String(now.getDate()).padStart(2, '0') + '_' +
          String(now.getHours()).padStart(2, '0') + '.' +
          String(now.getMinutes()).padStart(2, '0');

    const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `mydealz_backup_${timestamp}.json`;
    a.click();
    URL.revokeObjectURL(url);
}

// Restore-Funktion
function restoreData(event) {
    const file = event.target.files[0];
    if (file && file.type === 'application/json') {
        const reader = new FileReader();
        reader.onload = function(e) {
            const restoredData = JSON.parse(e.target.result);
            if (restoredData.excludeWords && (restoredData.excludeMerchantIDs || restoredData.merchantsData)) {
                // Restore words
                saveExcludeWords(restoredData.excludeWords);
                excludeWords = restoredData.excludeWords;

                // Restore merchant data
                const merchantsData = restoredData.merchantsData ||
                      restoredData.excludeMerchantIDs.map(id => ({ id, name: id }));

                saveExcludeMerchants(merchantsData);

                // Update UI
                document.getElementById('excludeWordsInput').value = excludeWords.join('\n');

                // Refresh deals
                processArticles();
            } else {
                alert('Die Backup-Datei ist nicht im richtigen Format.');
            }
        };
        reader.readAsText(file);
    } else {
        alert('Bitte wählen Sie eine gültige JSON-Datei aus.');
    }
}

// --- 4. Benutzeroberfläche (UI) ---
function getSubUIPosition() {
    if (IS_TOUCH_DEVICE) {
        return `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        `;
    }
    return `
        position: fixed;
        top: 50%;
        left: calc(50% + 310px);
        transform: translate(-50%, -50%);
    `;
}

function createMerchantListUI() {
    // Remove existing instance if it exists
    if (merchantListDiv && merchantListDiv.parentNode) {
        merchantListDiv.remove();
    }

    merchantListDiv = document.createElement('div');
    merchantListDiv.style.cssText = `
        ${getSubUIPosition()}
        padding: 15px;
        background: #f9f9f9;
        border: 1px solid #ccc;
        border-radius: 5px;
        z-index: 1001; // Higher than settings UI
        width: 300px;
    `;

    const currentMerchants = loadExcludeMerchants();

    const merchantListHTML = currentMerchants.map(merchant => `
        <div class="merchant-item" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; padding: 5px; background: #f0f0f0; border-radius: 3px;">
            <div style="display: flex; flex-direction: column;">
                <span style="font-weight: bold;">${merchant.name}</span>
                <span style="color: #666; font-size: 0.8em;">ID: ${merchant.id}</span>
            </div>
            <button class="delete-merchant" data-id="${merchant.id}" style="background: none; border: none; cursor: pointer; color: #666;">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `).join('');

    merchantListDiv.innerHTML = `
        <h4 style="margin-bottom: 10px;">Ausgeblendete Händler</h4>
        <input type="text" id="merchantSearch" placeholder="Händler suchen..." style="width: 100%; padding: 5px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 3px;">
        <div style="margin-bottom: 15px;">
            <div id="merchantList" style="margin-bottom: 10px; max-height: 200px; overflow-y: auto; padding-right: 5px;">
                ${merchantListHTML}
            </div>
            <button id="clearMerchantListButton" style="width: 100%; padding: 5px 10px; background: #f0f0f0; border: 1px solid #ccc; border-radius: 3px; cursor: pointer; margin-top: 10px;">
                <i class="fas fa-trash"></i> Alle Händler entfernen
            </button>
        </div>
        <div style="text-align: right;">
            <button id="closeMerchantListButton" style="padding: 8px 12px; background: none; border: none; cursor: pointer;" title="Schließen">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `;

    // Add the div to the document body
    document.body.appendChild(merchantListDiv);
    setupClickOutsideHandler();

    // Add search functionality
    const searchInput = document.getElementById('merchantSearch');
    searchInput.addEventListener('input', (e) => {
        const searchTerm = e.target.value.toLowerCase();
        document.querySelectorAll('.merchant-item').forEach(item => {
            const merchantName = item.querySelector('span').textContent.toLowerCase();
            const merchantId = item.querySelector('span:last-child').textContent.toLowerCase();
            if (merchantName.includes(searchTerm) || merchantId.includes(searchTerm)) {
                item.style.display = 'flex';
            } else {
                item.style.display = 'none';
            }
        });
    });

    // Add clear all button handler
    document.getElementById('clearMerchantListButton').addEventListener('click', () => {
        if (confirm('Möchten Sie wirklich alle Händler aus der Liste entfernen?')) {
            saveExcludeMerchants([]);
            document.getElementById('merchantList').innerHTML = '';
            processArticles();
        }
    });

    // Update delete button handlers
    document.querySelectorAll('.delete-merchant').forEach(button => {
        button.addEventListener('click', function(e) {
            // Prevent event bubbling
            e.preventDefault();
            e.stopPropagation();

            // Get merchant ID to delete
            const deleteButton = e.target.closest('.delete-merchant');
            if (!deleteButton) return;

            const idToDelete = deleteButton.dataset.id;

            // Update merchant data
            const merchantsData = loadExcludeMerchants();
            const updatedMerchants = merchantsData.filter(m => m.id !== idToDelete);

            // Save updated data
            saveExcludeMerchants(updatedMerchants);

            // Remove from UI
            deleteButton.closest('.merchant-item').remove();

            // Refresh deals
            processArticles();
        });
    });

    // Update close button handlers in createMerchantListUI
    document.getElementById('closeMerchantListButton').addEventListener('click', (e) => {
        e.stopPropagation(); // Prevent event bubbling
        closeActiveSubUI();
    });
}

function createExcludeWordsUI() {
    // Remove existing instance if it exists
    if (wordsListDiv && wordsListDiv.parentNode) {
        wordsListDiv.remove();
    }

    wordsListDiv = document.createElement('div');
    wordsListDiv.style.cssText = `
        ${getSubUIPosition()}
        padding: 15px;
        background: #f9f9f9;
        border: 1px solid #ccc;
        border-radius: 5px;
        z-index: 1001; // Higher than settings UI
        width: 300px;
    `;

    const currentWords = loadExcludeWords();

    const wordsListHTML = currentWords.map(word => `
        <div class="word-item" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; padding: 5px; background: #f0f0f0; border-radius: 3px;">
            <span style="word-break: break-word;">${word}</span>
            <button class="delete-word" data-word="${word}" style="background: none; border: none; cursor: pointer; color: #666;">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `).join('');

    wordsListDiv.innerHTML = `
        <h4 style="margin-bottom: 10px;">Ausgeblendete Wörter</h4>
        <input type="text" id="wordSearch" placeholder="Wörter suchen..." style="width: 100%; padding: 5px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 3px;">
        <div style="margin-bottom: 15px;">
            <div id="wordsList" style="margin-bottom: 10px; max-height: 200px; overflow-y: auto; padding-right: 5px;">
                ${wordsListHTML}
            </div>
            <button id="clearWordsListButton" style="width: 100%; padding: 5px 10px; background: #f0f0f0; border: 1px solid #ccc; border-radius: 3px; cursor: pointer; margin-top: 10px;">
                <i class="fas fa-trash"></i> Alle Wörter entfernen
            </button>
        </div>
        <div style="text-align: right;">
            <button id="closeWordsListButton" style="padding: 8px 12px; background: none; border: none; cursor: pointer;" title="Schließen">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `;

    // Add the div to the document body
    document.body.appendChild(wordsListDiv);
    setupClickOutsideHandler();

    // Add search functionality
    const searchInput = document.getElementById('wordSearch');
    searchInput.addEventListener('input', (e) => {
        const searchTerm = e.target.value.toLowerCase();
        document.querySelectorAll('.word-item').forEach(item => {
            const word = item.querySelector('span').textContent.toLowerCase();
            item.style.display = word.includes(searchTerm) ? 'flex' : 'none';
        });
    });

    // Add clear all button handler
    document.getElementById('clearWordsListButton').addEventListener('click', () => {
        if (confirm('Möchten Sie wirklich alle Wörter aus der Liste entfernen?')) {
            saveExcludeWords([]);
            document.getElementById('wordsList').innerHTML = '';
            excludeWords = [];
            processArticles();
        }
    });

    // Add delete handlers
    document.querySelectorAll('.delete-word').forEach(button => {
        button.addEventListener('click', (e) => {
            // Prevent event bubbling
            e.preventDefault();
            e.stopPropagation();

            const deleteButton = e.target.closest('.delete-word');
            if (!deleteButton) return;

            const wordToDelete = deleteButton.dataset.word;
            excludeWords = excludeWords.filter(word => word !== wordToDelete);
            saveExcludeWords(excludeWords);

            // Remove only the specific word item
            deleteButton.closest('.word-item').remove();

            // Update deals without closing UI
            processArticles();
        });
    });

    // Update close button handlers in createExcludeWordsUI
    document.getElementById('closeWordsListButton').addEventListener('click', (e) => {
        e.stopPropagation(); // Prevent event bubbling
        closeActiveSubUI();
    });
}

function getWordsFromTitle(dealElement) {
    const title = dealElement.querySelector('.thread-title').textContent;
    return title.split(/\s+/)
        .map(word => word.replace(/[^a-zA-Z0-9äöüÄÖÜß]/g, ''))
        .filter(word => word.length > 2);
}

// Add debug logging to settings UI creation
function createSettingsUI() {
    if (isSettingsOpen) return;
    isSettingsOpen = true;

    settingsDiv = document.createElement('div');
    settingsDiv.style.cssText = `
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        padding: 15px;
        background: #f9f9f9;
        border: 1px solid #ccc;
        border-radius: 5px;
        z-index: 1000;
        max-width: 300px;
        overflow: auto;
    `;

    // Get merchant info from current deal
    let merchantName = null;
    let showMerchantButton = false;

    if (dealThatOpenedSettings) {
        const merchantLink = dealThatOpenedSettings.querySelector('a[data-t="merchantLink"]');
        if (merchantLink) {
            merchantName = merchantLink.textContent.trim();
            showMerchantButton = true;
        }
    }

    // Process articles when opening settings
    processArticles();

    const currentExcludeWords = JSON.parse(localStorage.getItem(EXCLUDE_WORDS_KEY)) || [];
    const currentExcludeMerchantIDs = JSON.parse(localStorage.getItem(EXCLUDE_MERCHANTS_KEY)) || [];

    const dealWords = dealThatOpenedSettings ? getWordsFromTitle(dealThatOpenedSettings) : [];

    // Conditional merchant button HTML - only show if merchant exists
    const merchantButtonHtml = showMerchantButton ? `
        <button id="hideMerchantButton" style="width: 100%; margin-top: 5px; padding: 5px 10px; background: #f0f0f0; border: 1px solid #ccc; border-radius: 3px; cursor: pointer;">
            <i class="fas fa-store-slash"></i> Alle Deals von <span style="font-weight: bold">${merchantName}</span> ausblenden
        </button>
    ` : '';

    settingsDiv.innerHTML = `
    <h4 style="margin-bottom: 15px;">Einstellungen zum Ausblenden</h4>

    <!-- Word Input Section -->
    <div style="margin-bottom: 20px;">
        <div style="display: flex; align-items: center; gap: 5px;">
            <input list="wordSuggestions"
                id="newWordInput"
                placeholder="Neues Wort..."
                title="Deals mit hier eingetragenen Wörtern im Titel werden ausgeblendet."
                style="flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 3px;">
            <datalist id="wordSuggestions">
                ${dealWords.map(word => `<option value="${word}">`).join('')}
        </datalist>
        <button id="addWordButton"
            style="padding: 8px; background: #f0f0f0; border: 1px solid #ccc; border-radius: 3px; cursor: pointer;">
            <i class="fas fa-plus"></i>
        </button>
    </div>
</div>

    <!-- Merchant Hide Button -->
    ${merchantButtonHtml}

    <!-- List Management Section -->
    <div style="margin-top: 20px; display: flex; flex-direction: column; gap: 10px;">
        <button id="showWordsListButton"
            style="width: 100%; padding: 8px; background: #f0f0f0; border: 1px solid #ccc; border-radius: 3px; cursor: pointer;">
            <i class="fas fa-list"></i> Wortfilter verwalten
        </button>
        <button id="showMerchantListButton"
            style="width: 100%; padding: 8px; background: #f0f0f0; border: 1px solid #ccc; border-radius: 3px; cursor: pointer;">
            <i class="fas fa-store"></i> Händlerfilter verwalten
        </button>
    </div>

    <!-- Action Buttons -->
    <div style="margin-top: 20px; text-align: right; display: flex; justify-content: flex-end; gap: 5px;">
        <button id="createBackupButton" style="padding: 8px; background: none; border: none; cursor: pointer;" title="Backup erstellen">
            <i class="fas fa-file-export"></i>
        </button>
        <button id="restoreBackupButton" style="padding: 8px; background: none; border: none; cursor: pointer;" title="Wiederherstellen">
            <i class="fas fa-file-import"></i>
        </button>
        <input type="file" id="restoreFileInput" style="display: none;" />
        <button id="closeSettingsButton" style="padding: 8px; background: none; border: none; cursor: pointer;" title="Schließen">
            <i class="fas fa-times"></i>
        </button>
    </div>`;

    // Explicitly add to DOM
    document.body.appendChild(settingsDiv);
    setupClickOutsideHandler();

    // Add word input handler
    document.getElementById('addWordButton').addEventListener('click', () => {
        const newWord = document.getElementById('newWordInput').value.trim();
        if (newWord && !excludeWords.includes(newWord)) {
            // Add new word at start of array
            excludeWords.unshift(newWord);
            saveExcludeWords(excludeWords);
            document.getElementById('newWordInput').value = '';
            processArticles();
        }
    });

    // Add enter key handler for input
    document.getElementById('newWordInput').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            document.getElementById('addWordButton').click();
        }
    });

    // Only add merchant button listener if button exists
    const hideMerchantButton = document.getElementById('hideMerchantButton');
    if (hideMerchantButton && showMerchantButton) {
        hideMerchantButton.addEventListener('click', () => {
            if (!dealThatOpenedSettings) return;

            const merchantLink = dealThatOpenedSettings.querySelector('a[href*="merchant-id="]');
            if (!merchantLink) return;

            const merchantIDMatch = merchantLink.getAttribute('href').match(/merchant-id=(\d+)/);
            if (!merchantIDMatch) return;

            const merchantID = merchantIDMatch[1];
            const merchantName = dealThatOpenedSettings.querySelector('a[data-t="merchantLink"]').textContent.trim();

            // Load current data
            const merchantsData = loadExcludeMerchants();

            // Check if ID already exists
            if (!merchantsData.some(m => m.id === merchantID)) {
                // Add new merchant at start of array
                merchantsData.unshift({ id: merchantID, name: merchantName });
                saveExcludeMerchants(merchantsData);
                processArticles();
            }
        });
    }

    // Add merchant list button listener
    document.getElementById('showMerchantListButton').addEventListener('click', () => {
        closeActiveSubUI();
        createMerchantListUI();
        activeSubUI = 'merchant';
    });

    // Add words list button listener
    document.getElementById('showWordsListButton').addEventListener('click', () => {
        closeActiveSubUI();
        createExcludeWordsUI();
        activeSubUI = 'words';
    });

    // Always ensure close button works
    document.getElementById('closeSettingsButton').addEventListener('click', (e) => {
        e.stopPropagation(); // Prevent event bubbling
        if (settingsDiv?.parentNode) {
            settingsDiv.remove();
            isSettingsOpen = false;
        }
        // Close merchant list if open
        if (merchantListDiv && merchantListDiv.parentNode) {
            merchantListDiv.remove();
        }
        // Close words list if open
        if (wordsListDiv && wordsListDiv.parentNode) {
            wordsListDiv.remove();
        }
    });

    // Backup/Restore Event Listeners
    document.getElementById('createBackupButton').addEventListener('click', backupData);

    document.getElementById('restoreBackupButton').addEventListener('click', () => {
        document.getElementById('restoreFileInput').click();
    });

    document.getElementById('restoreFileInput').addEventListener('change', restoreData);
}

function initObserver() {
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
}

// Call initial setup
document.addEventListener('DOMContentLoaded', () => {
    processArticles();
    initObserver();
});

// Update merchant list UI - add new item at top
function addMerchantToList(merchant, merchantList) {
    const div = document.createElement('div');
    div.className = 'merchant-item';
    div.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; padding: 5px; background: #f0f0f0; border-radius: 3px;';
    div.innerHTML = `
        <div style="display: flex; flex-direction: column;">
            <span style="font-weight: bold;">${merchant.name}</span>
            <span style="color: #666; font-size: 0.8em;">ID: ${merchant.id}</span>
        </div>
        <button class="delete-merchant" data-id="${merchant.id}" style="background: none; border: none; cursor: pointer; color: #666;">
            <i class="fas fa-times"></i>
        </button>
    `;

    // Insert at beginning of list
    merchantList.insertBefore(div, merchantList.firstChild);
}

// Update word list UI - add new item at top
function addWordToList(word, wordsList) {
    const div = document.createElement('div');
    div.className = 'word-item';
    div.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; padding: 5px; background: #f0f0f0; border-radius: 3px;';
    div.innerHTML = `
        <span style="word-break: break-word;">${word}</span>
        <button class="delete-word" data-word="${word}" style="background: none; border: none; cursor: pointer; color: #666;">
            <i class="fas fa-times"></i>
        </button>
    `;

    // Insert at beginning of list
    wordsList.insertBefore(div, wordsList.firstChild);
}

function setupClickOutsideHandler() {
    if (uiClickOutsideHandler) {
        document.removeEventListener('click', uiClickOutsideHandler);
    }

    uiClickOutsideHandler = (e) => {
        const settingsOpen = settingsDiv?.parentNode;
        const merchantsOpen = merchantListDiv?.parentNode;
        const wordsOpen = wordsListDiv?.parentNode;

        if (e.target.closest('.settings-button') ||
            e.target.closest('#showMerchantListButton') ||
            e.target.closest('#showWordsListButton')) {
            return;
        }

        const clickedOutside = (!settingsOpen || !settingsDiv.contains(e.target)) &&
              (!merchantsOpen || !merchantListDiv.contains(e.target)) &&
              (!wordsOpen || !wordsListDiv.contains(e.target));

        if (clickedOutside) {
            if (settingsOpen) {
                settingsDiv.remove();
                isSettingsOpen = false;
            }
            if (merchantsOpen) merchantListDiv.remove();
            if (wordsOpen) wordsListDiv.remove();

            document.removeEventListener('click', uiClickOutsideHandler);
            uiClickOutsideHandler = null;
        }
    };

    setTimeout(() => document.addEventListener('click', uiClickOutsideHandler), 100);
}

// Add helper function to close sub-UIs
function closeActiveSubUI() {
    if (activeSubUI === 'merchant' && merchantListDiv?.parentNode) {
        merchantListDiv.remove();
    } else if (activeSubUI === 'words' && wordsListDiv?.parentNode) {
        wordsListDiv.remove();
    }
    activeSubUI = null;
}

// Add new function to handle merchant pages
function addMerchantPageHideButton() {
    // Check if we're on a merchant page
    const urlParams = new URLSearchParams(window.location.search);
    const merchantId = urlParams.get('merchant-id');
    if (!merchantId) return;

    // Find merchant header
    const merchantBanner = document.querySelector(MERCHANT_PAGE_SELECTOR);
    if (!merchantBanner) return;

    // Get merchant name from page
    const merchantName = document.querySelector('.merchant-banner__title')?.textContent.trim();
    if (!merchantName) return;

    // Create hide button container
    const hideButtonContainer = document.createElement('div');
    hideButtonContainer.style.cssText = `
        display: inline-flex;
        align-items: center;
        margin-left: 10px;
    `;

    // Create hide button
    const hideButton = document.createElement('button');
    hideButton.innerHTML = '<i class="fas fa-store-slash"></i>';
    hideButton.title = `Alle Deals von ${merchantName} ausblenden`;
    hideButton.style.cssText = `
        padding: 8px;
        background: #f0f0f0;
        border: 1px solid #ccc;
        border-radius: 3px;
        cursor: pointer;
    `;

    // Add click handler
    hideButton.addEventListener('click', () => {
        const merchantsData = loadExcludeMerchants();

        // Check if ID already exists
        if (!merchantsData.some(m => m.id === merchantId)) {
            // Add new merchant at start of array
            merchantsData.unshift({ id: merchantId, name: merchantName });
            saveExcludeMerchants(merchantsData);
            processArticles();
        }
    });

    // Add button to page
    hideButtonContainer.appendChild(hideButton);
    merchantBanner.appendChild(hideButtonContainer);
}

// Call function on page load
document.addEventListener('DOMContentLoaded', () => {
    addMerchantPageHideButton();
});