From ea20446a797a00cf45dbe7bf6f06574a79c4d8a6 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Thu, 15 Jan 2026 13:57:22 +0100 Subject: [PATCH] fix: watermark thumbnails, custom logo display, and German translations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix thumbnail display when watermarks enabled globally on existing galleries - Backend: Apply watermarks to thumbnails at the thumbnail endpoint - Frontend: Remove hack that redirected thumbnails to photo endpoint - Fix custom logo display in gallery hero sections - Only apply brightness/invert filter to default PicPeak logo - Custom logos now display as-is with drop-shadow only - Add German translations for Event Creation and Image Protection settings - settings.events: Pflichtfelder, Kundenname/E-Mail erforderlich, etc. - settings.imageSecurity: Bildschutz, Ratenbegrenzung, Sicherheitsüberwachung - Protection level options in both EN and DE locales --- backend/src/routes/gallery.js | 13 +++++- .../src/components/gallery/GalleryLayout.tsx | 5 +- .../src/components/gallery/GalleryView.tsx | 6 +-- .../gallery/layouts/HeroGalleryLayout.tsx | 7 ++- frontend/src/i18n/locales/de.json | 46 +++++++++++++++++++ frontend/src/i18n/locales/en.json | 4 ++ 6 files changed, 73 insertions(+), 8 deletions(-) diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js index 4b5b7a40..1464fca3 100644 --- a/backend/src/routes/gallery.js +++ b/backend/src/routes/gallery.js @@ -829,8 +829,17 @@ router.get('/:slug/thumbnail/:photoId', 'X-Protected-Thumbnail': 'true' }); - // Send file - res.sendFile(path.resolve(thumbPath)); + // Check if watermarks are enabled and apply to thumbnail + const watermarkSettings = await watermarkService.getWatermarkSettings(); + + if (watermarkSettings && watermarkSettings.enabled) { + // Apply watermark to thumbnail + const watermarkedBuffer = await watermarkService.applyWatermark(thumbPath, watermarkSettings); + res.send(watermarkedBuffer); + } else { + // Send file without watermark + res.sendFile(path.resolve(thumbPath)); + } } catch (error) { logger.error('Error serving thumbnail:', { error: error.message, diff --git a/frontend/src/components/gallery/GalleryLayout.tsx b/frontend/src/components/gallery/GalleryLayout.tsx index 87738879..38dd25a8 100644 --- a/frontend/src/components/gallery/GalleryLayout.tsx +++ b/frontend/src/components/gallery/GalleryLayout.tsx @@ -366,7 +366,10 @@ export const GalleryLayout: React.FC = ({ className={`${heroLogoSize.className} w-auto object-contain mx-auto`} style={{ ...(heroLogoSize.style || {}), - filter: 'brightness(0) invert(1) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' + // Only apply brightness/invert filter to default logo; custom logos display as-is + filter: brandingSettings?.logo_url + ? 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' + : 'brightness(0) invert(1) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' }} /> {shouldShowCompanyName() && brandingSettings?.company_name && ( diff --git a/frontend/src/components/gallery/GalleryView.tsx b/frontend/src/components/gallery/GalleryView.tsx index d22a2e24..333f2d08 100644 --- a/frontend/src/components/gallery/GalleryView.tsx +++ b/frontend/src/components/gallery/GalleryView.tsx @@ -380,12 +380,12 @@ export const GalleryView: React.FC = ({ slug, event }) => { } }); - // Transform URLs for watermarks if enabled + // Transform full-size URLs for watermarks if enabled + // Note: Thumbnails are watermarked server-side at the thumbnail endpoint if (watermarkEnabled) { photos = photos.map(photo => ({ ...photo, - url: `/gallery/${slug}/photo/${photo.id}`, - thumbnail_url: `/gallery/${slug}/photo/${photo.id}` // Use watermarked version for thumbnails too + url: `/gallery/${slug}/photo/${photo.id}` })); } diff --git a/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx b/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx index d3ec0487..e06c725a 100644 --- a/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx +++ b/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx @@ -142,8 +142,11 @@ export const HeroGalleryLayout: React.FC = ({ } alt="Event logo" className="h-20 sm:h-24 lg:h-32 mx-auto" - style={{ - filter: 'brightness(0) invert(1) drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5))' + style={{ + // Only apply brightness/invert filter to default logo; custom logos display as-is + filter: eventLogo + ? 'drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5))' + : 'brightness(0) invert(1) drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5))' }} /> diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 5f24300f..63f4c16a 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -778,6 +778,10 @@ "enableDevtoolsProtection": "Entwicklertools erkennen", "useCanvasRendering": "Canvas-Rendering (erweiterter Schutz)", "protectionInfo": "Schutzfunktionen helfen, unerlaubte Downloads zu verhindern, können jedoch nicht alle Methoden blockieren.", + "protectionLevelBasic": "Einfach - Nur Rechtsklick-Sperre", + "protectionLevelStandard": "Standard - Tastenkombinationen blockiert", + "protectionLevelEnhanced": "Erweitert - Bildschirmfoto-Erkennung", + "protectionLevelMaximum": "Maximum - DevTools-Erkennung & Canvas-Rendering", "heroPhoto": "Hero-Foto", "heroPhotoHelp": "Wählen Sie ein hervorgehobenes Foto für das Hero-Galerie-Layout", "selectHeroPhoto": "Hero-Foto auswählen", @@ -1032,6 +1036,48 @@ "saveSettings": "Sicherheitseinstellungen speichern", "saveSecuritySettings": "Sicherheitseinstellungen speichern" }, + "events": { + "title": "Veranstaltungserstellung", + "requiredFields": "Pflichtfelder", + "requiredFieldsDescription": "Konfigurieren Sie, welche Kontaktfelder beim Erstellen neuer Veranstaltungen erforderlich sind.", + "requireCustomerName": "Kundenname erforderlich", + "requireCustomerNameHelp": "Kundenname muss für neue Veranstaltungen angegeben werden", + "requireCustomerEmail": "Kunden-E-Mail erforderlich", + "requireCustomerEmailHelp": "Kunden-E-Mail muss für neue Veranstaltungen angegeben werden", + "customerEmailWarning": "Erforderlich für das Versenden von Galerie-Einladungen", + "requireAdminEmail": "Admin-E-Mail erforderlich", + "requireAdminEmailHelp": "Admin-E-Mail muss für neue Veranstaltungen angegeben werden", + "adminEmailWarning": "Erforderlich für den Erhalt von Veranstaltungsbenachrichtigungen", + "saveSettings": "Veranstaltungseinstellungen speichern", + "noteTitle": "Hinweis", + "noteText": "Diese Einstellungen betreffen nur die Erstellung neuer Veranstaltungen. Bestehende Veranstaltungen sind nicht betroffen. Standardmäßig sind alle Felder erforderlich." + }, + "imageSecurity": { + "title": "Bildschutz", + "saveSuccess": "Bildschutz-Einstellungen gespeichert", + "saveError": "Fehler beim Speichern der Einstellungen", + "loadError": "Fehler beim Laden der Bildschutz-Einstellungen", + "defaultProtection": "Standard-Schutzeinstellungen", + "defaultProtectionHelp": "Diese Einstellungen gelten für alle neuen Veranstaltungen. Einzelne Veranstaltungen können diese Standardwerte überschreiben.", + "protectionLevel": "Standard-Schutzstufe", + "imageQuality": "Standard-Bildqualität", + "fragmentationLevel": "Fragmentierungsstufe", + "enableDevtools": "DevTools-Erkennung standardmäßig aktivieren", + "enableCanvas": "Canvas-Rendering standardmäßig aktivieren (erweiterter Schutz)", + "rateLimiting": "Ratenbegrenzung", + "rateLimitingHelp": "Begrenzen Sie, wie viele Bilder angefordert werden können, um Scraping zu verhindern.", + "requestsPerMinute": "Anfragen pro Minute", + "requestsPer5Minutes": "Anfragen pro 5 Min.", + "requestsPerHour": "Anfragen pro Stunde", + "securityMonitoring": "Sicherheitsüberwachung", + "suspiciousThreshold": "Schwellenwert für verdächtige Aktivitäten", + "autoBlockThreshold": "Automatische Sperrschwelle", + "enableMonitoring": "Sicherheitsüberwachung aktivieren", + "blockSuspiciousIps": "Verdächtige IPs automatisch blockieren", + "logEvents": "Sicherheitsereignisse in Datenbank protokollieren", + "infoTitle": "Über Bildschutz", + "infoText": "Diese Schutzfunktionen helfen, gelegentliches Herunterladen und Kopieren zu verhindern, können aber nicht alle Methoden blockieren. Entschlossene Benutzer finden möglicherweise trotzdem Wege, Bilder zu erfassen. Erwägen Sie die Verwendung von Wasserzeichen und rechtlichen Vereinbarungen für umfassenden Schutz." + }, "categories": { "title": "Kategorien", "about": "Über Fotokategorien", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 3cfdc0ba..080206a6 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -452,6 +452,10 @@ "enableDevtoolsProtection": "Detect developer tools", "useCanvasRendering": "Canvas rendering (advanced protection)", "protectionInfo": "Protection features help prevent unauthorized downloads but cannot block all methods.", + "protectionLevelBasic": "Basic - Right-click blocking only", + "protectionLevelStandard": "Standard - Keyboard shortcuts blocked", + "protectionLevelEnhanced": "Enhanced - Print screen detection", + "protectionLevelMaximum": "Maximum - DevTools detection & canvas rendering", "heroPhoto": "Hero Photo", "heroPhotoHelp": "Select a featured photo for the hero gallery layout", "selectHeroPhoto": "Select Hero Photo",