diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 5ef8571..c480034 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -15684,4 +15684,82 @@ body.scroller-active #gchat-reopen-bubble { .media-object.revealed::after { opacity: 0 !important; pointer-events: none !important; +} + +/* ============================================= + NOTIFICATION THUMBNAIL BLUR + Applies the same blur treatment to .notif-thumb + when the corresponding blur-active class is set. + ============================================= */ +.blur-nsfw-active .notif-thumb[data-mode="nsfw"] img, +.blur-nsfl-active .notif-thumb[data-mode="nsfl"] img, +.blur-sfw-active .notif-thumb[data-mode="sfw"] img, +.blur-untagged-active .notif-thumb[data-mode="untagged"] img { + filter: blur(8px) !important; + transform: scale(1.08) !important; + transition: filter 0.2s ease, transform 0.2s ease !important; +} + +/* Reveal on hover — only when hovering directly on the thumb */ +.notif-thumb[data-mode]:hover img { + filter: none !important; + transform: scale(1.1) !important; +} + +/* Overlay label */ +.blur-nsfw-active .notif-thumb[data-mode="nsfw"], +.blur-nsfl-active .notif-thumb[data-mode="nsfl"], +.blur-sfw-active .notif-thumb[data-mode="sfw"], +.blur-untagged-active .notif-thumb[data-mode="untagged"] { + position: relative !important; +} + +.blur-nsfw-active .notif-thumb[data-mode="nsfw"]::after, +.blur-nsfl-active .notif-thumb[data-mode="nsfl"]::after, +.blur-sfw-active .notif-thumb[data-mode="sfw"]::after, +.blur-untagged-active .notif-thumb[data-mode="untagged"]::after { + content: attr(data-mode) !important; + text-transform: uppercase !important; + position: absolute !important; + inset: 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + font-weight: 800 !important; + font-size: 10px !important; + letter-spacing: 1.5px !important; + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9) !important; + background: rgba(10, 10, 12, 0.55) !important; + pointer-events: none !important; + opacity: 1 !important; + transition: opacity 0.2s ease !important; + z-index: 2 !important; +} + +.blur-nsfw-active .notif-thumb[data-mode="nsfw"]::after { color: #ffffff !important; } +.blur-nsfl-active .notif-thumb[data-mode="nsfl"]::after { color: #ff3b30 !important; } +.blur-sfw-active .notif-thumb[data-mode="sfw"]::after { color: #30d158 !important; } +.blur-untagged-active .notif-thumb[data-mode="untagged"]::after { color: #ff9f0a !important; } + +/* Hide overlay on hover — only when hovering directly on the thumb */ +.notif-thumb[data-mode]:hover::after { + opacity: 0 !important; +} + +/* Revealed state (set by JS tap-to-reveal for mobile) */ +.notif-thumb[data-mode].revealed img { + filter: none !important; +} +.notif-thumb[data-mode].revealed::after { + opacity: 0 !important; + pointer-events: none !important; +} + +/* ALL mode: suppress all notification thumbnail blur immediately */ +html.mode-all .notif-thumb[data-mode] img { + filter: none !important; + transform: none !important; +} +html.mode-all .notif-thumb[data-mode]::after { + display: none !important; } \ No newline at end of file diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index 83269dd..794b42c 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -504,6 +504,16 @@ window.cancelAnimFrame = (function () { } } + // Reflect ALL mode on immediately (controls notif-thumb blur suppression) + if (window.activeMode === 3) htmlEl.classList.add('mode-all'); + document.addEventListener('f0ck:modeChanged', (e) => { + if (e.detail?.mode === 3) { + htmlEl.classList.add('mode-all'); + } else { + htmlEl.classList.remove('mode-all'); + } + }); + // ---- Multi-select Rating Toggles ---- // Reads/writes a `ratings` cookie (e.g. "sfw|untagged") and syncs with server via /mode/3 (ALL). const getRatingsCookie = () => { @@ -6567,9 +6577,33 @@ class NotificationSystem { // Single Notification Click Handler (Delegated) // Handles both Dropdown and History Page const handleNotificationClick = (e) => { + // Blur-reveal intercept: if the tap/click landed on (or inside) a blurred + // notif-thumb that hasn't been revealed yet, reveal it and stop navigation. + // Skip entirely in ALL mode — nothing is blurred. + const tappedThumb = e.target.closest('.notif-thumb[data-mode]'); + if (tappedThumb && !tappedThumb.classList.contains('revealed') && window.activeMode !== 3) { + const mode = tappedThumb.dataset.mode; + const blurNsfw = localStorage.getItem('blurNsfw') === 'true'; + const blurNsfl = localStorage.getItem('blurNsfl') === 'true'; + const blurSfw = localStorage.getItem('blurSfw') === 'true'; + const blurUntagged = localStorage.getItem('blurUntagged') === 'true'; + const isBlurred = + (mode === 'nsfw' && blurNsfw) || + (mode === 'nsfl' && blurNsfl) || + (mode === 'sfw' && blurSfw) || + (mode === 'untagged' && blurUntagged); + if (isBlurred) { + e.preventDefault(); + e.stopPropagation(); + tappedThumb.classList.add('revealed'); + return; + } + } + const link = e.target.closest('.notif-item'); if (!link) return; + // Handle "Mark as Read" if (link.dataset.id && link.classList.contains('unread')) { window.f0ckDebug(`[NotificationSystem] Marking ${link.dataset.id} as read...`); @@ -6919,7 +6953,7 @@ class NotificationSystem { const itemLink = `/${n.item_id}`; return ` -
thumb
+
thumb
${userLabel}
@@ -6986,7 +7020,7 @@ class NotificationSystem { thumbSrc = `/t/${n.item_id}.webp`; thumbOnerror = `this.onerror=null;this.src='/mod/pending/t/${n.item_id}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';};}`; } - const thumb = n.item_id ? `
thumb
` : ''; + const thumb = n.item_id ? `
thumb
` : ''; return `
${thumb} @@ -7004,7 +7038,7 @@ class NotificationSystem { const link = `/${n.item_id}`; return ` -
thumb
+
thumb
${(window.f0ckI18n && window.f0ckI18n.notif_upload_approved) || 'Your Upload has been approved'} @@ -7019,7 +7053,7 @@ class NotificationSystem { const link = `/${n.item_id}`; return ` -
thumb
+
thumb
${(window.f0ckI18n && window.f0ckI18n.notif_upload_success) || 'Your background upload is finished!'} @@ -7056,7 +7090,7 @@ class NotificationSystem { const link = `/notifications#notif-${n.id}`; return ` -
thumb
+
thumb
${label} #${n.item_id} @@ -7072,7 +7106,7 @@ class NotificationSystem { const link = '/mod/approve'; return ` -
thumb
+
thumb
${(window.f0ckI18n && window.f0ckI18n.notif_upload_pending) || 'A new upload needs approval'} @@ -7087,7 +7121,7 @@ class NotificationSystem { const link = '/mod/reports'; return ` -
thumb
+
thumb
${(window.f0ckI18n && window.f0ckI18n.notif_new_report) || 'A new user report has been submitted'} @@ -7123,7 +7157,7 @@ class NotificationSystem { const cid = n.comment_id || n.reference_id; const link = `/${n.item_id}#c${cid}`; - const thumb = n.item_id ? `
thumb
` : ''; + const thumb = n.item_id ? `
thumb
` : ''; return `
${thumb} diff --git a/src/inc/routes/notifications.mjs b/src/inc/routes/notifications.mjs index db8d0d1..ab203de 100644 --- a/src/inc/routes/notifications.mjs +++ b/src/inc/routes/notifications.mjs @@ -346,6 +346,8 @@ export default (router, tpl) => { const USER_TYPES = ['comment_reply', 'subscription', 'mention', 'upload_comment']; const SYSTEM_TYPES = ['approve', 'deny', 'item_deleted', 'upload_success', 'upload_error', 'admin_pending', 'report', 'warning']; + const nsflTagId = cfg.nsfl_tag_id || 3; + async function getNotificationHistory(userId, page = 1, limit = 50, tab = null) { const offset = (page - 1) * limit; const typeFilter = tab === 'system' ? SYSTEM_TYPES : (tab === 'user' ? USER_TYPES : null); @@ -356,7 +358,13 @@ export default (router, tpl) => { COALESCE(uo.display_name, '') as from_display_name, COALESCE(u.id, 0) as from_user_id, uo.username_color, - i.dest, i.mime + i.dest, i.mime, + CASE (SELECT ta.tag_id FROM tags_assign ta WHERE ta.item_id = n.item_id AND ta.tag_id IN (1, 2, ${nsflTagId}) LIMIT 1) + WHEN 1 THEN 'sfw' + WHEN 2 THEN 'nsfw' + WHEN ${nsflTagId} THEN 'nsfl' + ELSE NULL + END as item_mode FROM notifications n LEFT JOIN comments c ON n.reference_id = c.id LEFT JOIN "user" u ON c.user_id = u.id @@ -375,7 +383,13 @@ export default (router, tpl) => { COALESCE(uo.display_name, '') as from_display_name, COALESCE(u.id, 0) as from_user_id, uo.username_color, - i.dest, i.mime + i.dest, i.mime, + CASE (SELECT ta.tag_id FROM tags_assign ta WHERE ta.item_id = n.item_id AND ta.tag_id IN (1, 2, ${nsflTagId}) LIMIT 1) + WHEN 1 THEN 'sfw' + WHEN 2 THEN 'nsfw' + WHEN ${nsflTagId} THEN 'nsfl' + ELSE NULL + END as item_mode FROM notifications n LEFT JOIN comments c ON n.reference_id = c.id LEFT JOIN "user" u ON c.user_id = u.id @@ -420,7 +434,13 @@ export default (router, tpl) => { COALESCE(uo.display_name, '') as from_display_name, COALESCE(u.id, 0) as from_user_id, uo.username_color, - i.dest, i.mime + i.dest, i.mime, + CASE (SELECT ta.tag_id FROM tags_assign ta WHERE ta.item_id = n.item_id AND ta.tag_id IN (1, 2, ${nsflTagId}) LIMIT 1) + WHEN 1 THEN 'sfw' + WHEN 2 THEN 'nsfw' + WHEN ${nsflTagId} THEN 'nsfl' + ELSE NULL + END as item_mode FROM notifications n LEFT JOIN comments c ON n.reference_id = c.id LEFT JOIN "user" u ON c.user_id = u.id diff --git a/views/snippets/notifications-list.html b/views/snippets/notifications-list.html index 3a4defe..a324d29 100644 --- a/views/snippets/notifications-list.html +++ b/views/snippets/notifications-list.html @@ -1,8 +1,8 @@ @each(notifications as n) @if(n.type === 'approve') -