diff --git a/migrations/add_hide_fav_badge.sql b/migrations/add_hide_fav_badge.sql new file mode 100644 index 0000000..09473b9 --- /dev/null +++ b/migrations/add_hide_fav_badge.sql @@ -0,0 +1,5 @@ +-- Add hide_fav_badge column to user_options table +ALTER TABLE user_options + ADD COLUMN IF NOT EXISTS hide_fav_badge boolean DEFAULT false; + +COMMENT ON COLUMN public.user_options.favorites_private IS 'When true, the user''s favorite badge on item pages displays as a ghost icon without linking to their profile'; diff --git a/migrations/f0ckm_schema.sql b/migrations/f0ckm_schema.sql index 3d95514..f33256b 100644 --- a/migrations/f0ckm_schema.sql +++ b/migrations/f0ckm_schema.sql @@ -1499,7 +1499,8 @@ CREATE TABLE public.user_options ( do_not_disturb boolean DEFAULT false, comment_display_mode integer DEFAULT 1, force_comment_display_mode integer DEFAULT 0, - favorites_private boolean DEFAULT false + favorites_private boolean DEFAULT false, + hide_fav_badge boolean DEFAULT false ); diff --git a/public/s/img/ghost_fav.svg b/public/s/img/ghost_fav.svg new file mode 100644 index 0000000..6d70075 --- /dev/null +++ b/public/s/img/ghost_fav.svg @@ -0,0 +1,5 @@ + + + + ? + diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index 68abd1c..eadca1f 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -8768,23 +8768,39 @@ class NotificationSystem { // Build DOM nodes imperatively (avoids Sanitizer stripping inline border-color) const fragment = document.createDocumentFragment(); data.favs.forEach(fav => { - const avatarUrl = fav.avatar_file - ? `/a/${fav.avatar_file}` - : (fav.avatar ? `/t/${fav.avatar}.webp` : '/a/default.png'); + if (fav.hide_fav_badge) { + const a = document.createElement('a'); + a.className = 'ghost-fav'; + a.setAttribute('tooltip', 'Ghost Fav'); + a.setAttribute('flow', 'up'); + a.style.cursor = 'default'; - const a = document.createElement('a'); - a.href = `/user/${fav.user.toLowerCase()}`; - a.setAttribute('tooltip', fav.display_name || fav.user); - a.setAttribute('flow', 'up'); + const img = document.createElement('img'); + img.src = '/s/img/ghost_fav.svg'; + img.style.height = '32px'; + img.style.width = '32px'; - const img = document.createElement('img'); - img.src = avatarUrl; - img.style.height = '32px'; - img.style.width = '32px'; - if (fav.username_color) img.style.borderColor = fav.username_color; + a.appendChild(img); + fragment.appendChild(a); + } else { + const avatarUrl = fav.avatar_file + ? `/a/${fav.avatar_file}` + : (fav.avatar ? `/t/${fav.avatar}.webp` : '/a/default.png'); - a.appendChild(img); - fragment.appendChild(a); + const a = document.createElement('a'); + a.href = `/user/${fav.user.toLowerCase()}`; + a.setAttribute('tooltip', fav.display_name || fav.user); + a.setAttribute('flow', 'up'); + + const img = document.createElement('img'); + img.src = avatarUrl; + img.style.height = '32px'; + img.style.width = '32px'; + if (fav.username_color) img.style.borderColor = fav.username_color; + + a.appendChild(img); + fragment.appendChild(a); + } }); favsContainer.innerHTML = ''; diff --git a/public/s/js/settings.js b/public/s/js/settings.js index 4ada4b7..d765818 100644 --- a/public/s/js/settings.js +++ b/public/s/js/settings.js @@ -1150,6 +1150,37 @@ }); } + const hideFavBadgeToggle = document.getElementById('hide_fav_badge_toggle'); + if (hideFavBadgeToggle) { + hideFavBadgeToggle.addEventListener('change', async function() { + const hide_fav_badge = hideFavBadgeToggle.checked; + try { + const res = await fetch('/api/v2/settings/hide_fav_badge', { + method: 'PUT', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'X-CSRF-Token': window.f0ckSession ? window.f0ckSession.csrf_token : '' + }, + body: new URLSearchParams({ hide_fav_badge }) + }); + const data = await res.json(); + if (data.success) { + showStatus('Hide favorite badge preference updated!', 'success'); + if (window.f0ckSession) { + window.f0ckSession.hide_fav_badge = hide_fav_badge; + } + } else { + alert(data.msg || 'Error saving preference'); + hideFavBadgeToggle.checked = !hide_fav_badge; + } + } catch (err) { + console.error('Update Hide Fav Badge error:', err); + alert('Connection error'); + hideFavBadgeToggle.checked = !hide_fav_badge; + } + }); + } + // New Dual Column Layout Toggle const layoutToggle = document.getElementById('use_new_layout_toggle'); if (layoutToggle) { diff --git a/src/inc/locales/de.json b/src/inc/locales/de.json index 2355b0d..f40cf9e 100644 --- a/src/inc/locales/de.json +++ b/src/inc/locales/de.json @@ -167,6 +167,8 @@ "disable_swiping_hint": "Navigation per Wischen auf Mobilgeräten deaktivieren", "favorites_private": "Private Favoriten", "favorites_private_hint": "Nur du und Administratoren können deine Favoritenliste sehen.", + "hide_fav_badge": "Favoriten-Badge-Avatar verbergen", + "hide_fav_badge_hint": "Zeigt auf Beitragsseiten stattdessen ein Geist-Icon ohne Profilverlinkung an", "image_expand_on_click": "Bilder beim Klicken inline erweitern", "image_expand_on_click_hint": "Anstatt das Scroll-Zoom-Modal zu öffnen, wird ein Bild beim Klicken innerhalb der Seite auf volle Größe erweitert.", "enable_bg_blur": "Hintergrundunschärfe aktivieren", diff --git a/src/inc/locales/en.json b/src/inc/locales/en.json index 3bc8ef9..d113355 100644 --- a/src/inc/locales/en.json +++ b/src/inc/locales/en.json @@ -167,6 +167,8 @@ "disable_swiping_hint": "Disable swipe-to-navigate on mobile devices", "favorites_private": "Private Favorites", "favorites_private_hint": "Only you and administrators can view your favorites list.", + "hide_fav_badge": "Hide Favorite Badge Avatar", + "hide_fav_badge_hint": "Display as a ghost icon on post detail pages without linking to your profile", "image_expand_on_click": "Expand images inline on click", "image_expand_on_click_hint": "Instead of opening the scroll zoom modal, clicking an image will expand it to full size within the page.", "enable_bg_blur": "Enable Background blur", diff --git a/src/inc/locales/nl.json b/src/inc/locales/nl.json index 539ed77..c33b5e7 100644 --- a/src/inc/locales/nl.json +++ b/src/inc/locales/nl.json @@ -167,6 +167,8 @@ "disable_swiping_hint": "Swipe-to-navigate uitschakelen op mobiele apparaten", "favorites_private": "Privé favorieten", "favorites_private_hint": "Alleen jij en beheerders kunnen je favorietenlijst bekijken.", + "hide_fav_badge": "Favoriet-badge avatar verbergen", + "hide_fav_badge_hint": "Toon als een spook-icoon op berichtpagina's zonder koppeling naar je profiel", "image_expand_on_click": "Afbeeldingen inline vergroten bij klikken", "image_expand_on_click_hint": "In plaats van de scroll-zoom-modal te openen, wordt een afbeelding bij klikken vergroot tot volledige grootte binnen de pagina.", "enable_bg_blur": "Achtergrondvervaging inschakelen", diff --git a/src/inc/locales/zange.json b/src/inc/locales/zange.json index 1cacf08..7c3ad42 100644 --- a/src/inc/locales/zange.json +++ b/src/inc/locales/zange.json @@ -167,6 +167,8 @@ "disable_swiping_hint": "Deaktivieren der Wisch-Navigation auf Mobilgeräten", "favorites_private": "Private Favoriten", "favorites_private_hint": "Nur du und Administratoren können deine Favoritenliste sehen.", + "hide_fav_badge": "Herzi-Badge-Avatar versteckeln", + "hide_fav_badge_hint": "Zeigt auf Beitragsseiten 1 geiles Geist-Icon an vong Anonymität her", "image_expand_on_click": "Bildli beim Klickle inline uffblähe", "image_expand_on_click_hint": "Anstatt dat Scroll-Zoom-Moped aufzumache, wird n Bild beim Klickle uff volle Größ im Bereich uffgepumpt.", "enable_bg_blur": "Hintergrundunschärfe aktivieren", diff --git a/src/inc/routeinc/f0cklib.mjs b/src/inc/routeinc/f0cklib.mjs index 7996679..b2ad199 100644 --- a/src/inc/routeinc/f0cklib.mjs +++ b/src/inc/routeinc/f0cklib.mjs @@ -768,7 +768,7 @@ export default { link.suffix = ''; } const favorites = await db` - select "user".user, "user_options".avatar, "user_options".avatar_file, "user_options".username_color, "user_options".display_name + select "user".user, "user_options".avatar, "user_options".avatar_file, "user_options".username_color, "user_options".display_name, "user_options".hide_fav_badge from "favorites" left join "user" on "user".id = "favorites".user_id left join "user_options" on "user_options".user_id = "favorites".user_id diff --git a/src/inc/routes/apiv2/index.mjs b/src/inc/routes/apiv2/index.mjs index d7e127d..9d4b8a0 100644 --- a/src/inc/routes/apiv2/index.mjs +++ b/src/inc/routes/apiv2/index.mjs @@ -1066,7 +1066,7 @@ export default router => { } const favs = await db` - select "user".user, "user_options".avatar, "user_options".avatar_file, "user_options".display_name, "user_options".username_color + select "user".user, "user_options".avatar, "user_options".avatar_file, "user_options".display_name, "user_options".username_color, "user_options".hide_fav_badge from "favorites" left join "user" on "user".id = "favorites".user_id left join "user_options" on "user_options".user_id = "favorites".user_id diff --git a/src/inc/routes/apiv2/settings.mjs b/src/inc/routes/apiv2/settings.mjs index febe441..8339c61 100644 --- a/src/inc/routes/apiv2/settings.mjs +++ b/src/inc/routes/apiv2/settings.mjs @@ -334,6 +334,25 @@ export default router => { } }); + // Update Hide Fav Badge preference + group.put(/\/hide_fav_badge/, lib.loggedin, async (req, res) => { + const hide_fav_badge = req.post.hide_fav_badge === true || req.post.hide_fav_badge === 'true'; + + try { + await db` + update user_options + set hide_fav_badge = ${hide_fav_badge} + where user_id = ${+req.session.id} + `; + // Sync session immediately + if (req.session) req.session.hide_fav_badge = hide_fav_badge; + return res.json({ success: true, hide_fav_badge }, 200); + } catch (e) { + console.error('Update Hide Fav Badge pref error:', e); + return res.json({ success: false, msg: 'Error updating preference' }, 500); + } + }); + // Update Username Color preference group.put(/\/username_color/, lib.loggedin, async (req, res) => { const { color } = req.post; diff --git a/src/index.mjs b/src/index.mjs index 862710e..039cac6 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -672,7 +672,7 @@ process.on('uncaughtException', err => { user = [_cachedRow]; } else { user = await db` - select "user".id, "user".login, "user".user, "user".admin, "user".is_moderator, "user".banned, "user".ban_reason, "user".ban_expires, "user".force_password_change, "user_sessions".id as sess_id, "user_sessions".csrf_token, "user_options".mode, "user_options".theme, "user_options".fullscreen, "user_options".excluded_tags, "user_options".avatar, "user_options".avatar_file, "user_options".show_motd, "user_options".strict_mode, "user_options".show_background, "user_options".use_new_layout, "user_options".username_color, "user_options".font, "user_options".disable_autoplay, "user_options".disable_swiping, "user_options".favorites_private, "user_options".description, "user_options".display_name, COALESCE("user_options".min_xd_score, 0) as min_xd_score, "user_options".ruffle_volume, "user_options".ruffle_background, "user_options".quote_emojis, "user_options".embed_youtube_in_comments, "user_options".hide_koepfe, "user_options".language, "user_options".use_alternative_infobox, "user_options".use_alternative_steuerung, "user_options".receive_system_notifications, "user_options".receive_user_notifications, "user_options".do_not_disturb, "user_options".comment_display_mode, "user_options".force_comment_display_mode + select "user".id, "user".login, "user".user, "user".admin, "user".is_moderator, "user".banned, "user".ban_reason, "user".ban_expires, "user".force_password_change, "user_sessions".id as sess_id, "user_sessions".csrf_token, "user_options".mode, "user_options".theme, "user_options".fullscreen, "user_options".excluded_tags, "user_options".avatar, "user_options".avatar_file, "user_options".show_motd, "user_options".strict_mode, "user_options".show_background, "user_options".use_new_layout, "user_options".username_color, "user_options".font, "user_options".disable_autoplay, "user_options".disable_swiping, "user_options".favorites_private, "user_options".hide_fav_badge, "user_options".description, "user_options".display_name, COALESCE("user_options".min_xd_score, 0) as min_xd_score, "user_options".ruffle_volume, "user_options".ruffle_background, "user_options".quote_emojis, "user_options".embed_youtube_in_comments, "user_options".hide_koepfe, "user_options".language, "user_options".use_alternative_infobox, "user_options".use_alternative_steuerung, "user_options".receive_system_notifications, "user_options".receive_user_notifications, "user_options".do_not_disturb, "user_options".comment_display_mode, "user_options".force_comment_display_mode from "user_sessions" left join "user" on "user".id = "user_sessions".user_id left join "user_options" on "user_options".user_id = "user_sessions".user_id diff --git a/views/item-partial-legacy.html b/views/item-partial-legacy.html index 8e94837..3beeacd 100644 --- a/views/item-partial-legacy.html +++ b/views/item-partial-legacy.html @@ -197,7 +197,11 @@ @each(item.favorites as fav) + @if(fav.hide_fav_badge) + + @else + @endif @endeach @if(enable_xd_score && item.xd_score > 0) diff --git a/views/item-partial-modern.html b/views/item-partial-modern.html index ddd032a..f6546f5 100644 --- a/views/item-partial-modern.html +++ b/views/item-partial-modern.html @@ -157,7 +157,11 @@ @each(item.favorites as fav) + @if(fav.hide_fav_badge) + + @else + @endif @endeach diff --git a/views/settings.html b/views/settings.html index 565f5db..153bad5 100644 --- a/views/settings.html +++ b/views/settings.html @@ -326,6 +326,13 @@ {{ t('settings.favorites_private') }} {{ t('settings.favorites_private_hint') }} +
+ + {{ t('settings.hide_fav_badge_hint') }} +