- // ==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();
- });