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 a6247ec..c26d13b 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') }}
+