diff --git a/config_example.json b/config_example.json index 0b05a0e..54f0506 100644 --- a/config_example.json +++ b/config_example.json @@ -111,6 +111,7 @@ "enable_swiping": true, "enable_profile_description": true, "user_alternative_infobox": false, + "user_alternative_steuerung": false, "enable_swf": false, "swf_thumb": "/s/img/swf.png", "enable_item_title": true, diff --git a/migrations/f0ckm_schema.sql b/migrations/f0ckm_schema.sql index 42a3058..1a2be8b 100644 --- a/migrations/f0ckm_schema.sql +++ b/migrations/f0ckm_schema.sql @@ -1493,6 +1493,7 @@ CREATE TABLE public.user_options ( hide_koepfe boolean DEFAULT false NOT NULL, language text, use_alternative_infobox boolean DEFAULT false, + use_alternative_steuerung boolean DEFAULT false, receive_system_notifications boolean DEFAULT true, receive_user_notifications boolean DEFAULT true, do_not_disturb boolean DEFAULT false, diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 2d8c130..c8e61c6 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -9023,6 +9023,28 @@ html[theme="f0ck95d"] .badge-dark { color: white; } +.steuerung.steuerung-icon { + font-size: x-large; +} + +.steuerung.steuerung-icon a { + color: white; + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.6em; + transition: color 0.15s ease, transform 0.15s ease; +} + +.steuerung.steuerung-icon a:hover { + color: var(--accent); +} + +.steuerung.steuerung-icon .fa-solid { + font-size: 0.85em; +} + + .blahlol { grid-column: 1 / 4; width: 100%; diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index b9be14f..d07aca5 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -9099,6 +9099,16 @@ if (navigator.vibrate) { }, { passive: true }); } +// ── Steuerung icon style: #scrolltobottom smooth scroll ─────────────────────── +// The alternative icon nav replaces the Zufall link with a down-chevron that +// scrolls the page to the bottom (comments / tag section). +document.addEventListener('click', (e) => { + const link = e.target.closest('a.steuerung-scroll-down'); + if (!link) return; + e.preventDefault(); + window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); +}); + // ── Spoiler Tags Event Delegation ───────────────────────────────────────────── (function() { const isHidden = (el) => el && el.classList && (el.classList.contains('spoiler') || el.classList.contains('blur-text')); diff --git a/public/s/js/settings.js b/public/s/js/settings.js index 6e61912..a26c5c7 100644 --- a/public/s/js/settings.js +++ b/public/s/js/settings.js @@ -670,6 +670,37 @@ }); } + // Alternative Steuerung Toggle (icon-only nav style) + const alternativeSteuerungToggle = document.getElementById('alternative_steuerung_toggle'); + if (alternativeSteuerungToggle) { + alternativeSteuerungToggle.addEventListener('change', async () => { + const use_alternative_steuerung = alternativeSteuerungToggle.checked; + try { + const res = await fetch('/api/v2/settings/alternative_steuerung', { + method: 'PUT', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'X-CSRF-Token': window.f0ckSession?.csrf_token + }, + body: new URLSearchParams({ use_alternative_steuerung }) + }); + const data = await res.json(); + if (data.success) { + showStatus('Navigation style updated!', 'success'); + if (window.f0ckSession) window.f0ckSession.use_alternative_steuerung = use_alternative_steuerung; + } else { + alert(data.msg || 'Error saving preference'); + alternativeSteuerungToggle.checked = !use_alternative_steuerung; + } + } catch (err) { + console.error(err); + alert('Failed to save navigation style preference'); + alternativeSteuerungToggle.checked = !use_alternative_steuerung; + } + }); + } + + // Notification Preferences Toggles const setupPreferenceToggle = (id, sessionKey) => { const el = document.getElementById(id); diff --git a/src/inc/locales/de.json b/src/inc/locales/de.json index a83f382..d45cc42 100644 --- a/src/inc/locales/de.json +++ b/src/inc/locales/de.json @@ -142,6 +142,8 @@ "modern_layout_hint": "3-Spalten-Layout", "alternative_infobox": "Alternativer Autor-Infoblock", "alternative_infobox_hint": "Zeigt einen erweiterten Autor-Block mit Avatar und Bio auf Beitragsseiten", + "alternative_steuerung": "Icon-Navigationsstil", + "alternative_steuerung_hint": "Ersetzt die Text-Navigation (← zurück | Zufall | weiter →) durch kompakte Chevron-Icons", "disable_autoplay": "Automatische Wiedergabe deaktivieren", "disable_autoplay_hint": "Verhindert die automatische Wiedergabe von Videos und Audio", "disable_swiping": "Wischen deaktivieren", diff --git a/src/inc/locales/en.json b/src/inc/locales/en.json index fa5a11b..4ac0d02 100644 --- a/src/inc/locales/en.json +++ b/src/inc/locales/en.json @@ -142,6 +142,8 @@ "modern_layout_hint": "3 Column Layout", "alternative_infobox": "Alternative Author Infobox", "alternative_infobox_hint": "Show a rich author card with avatar and bio on item pages", + "alternative_steuerung": "Icon nav style", + "alternative_steuerung_hint": "Replace text navigation (← prev | random | next →) with compact chevron icons", "disable_autoplay": "Disable Autoplay", "disable_autoplay_hint": "Prevent videos and audio from playing automatically", "disable_swiping": "Disable Swiping", diff --git a/src/inc/locales/nl.json b/src/inc/locales/nl.json index a3a2363..4d9a0f7 100644 --- a/src/inc/locales/nl.json +++ b/src/inc/locales/nl.json @@ -142,6 +142,8 @@ "modern_layout_hint": "Indeling met 3 kolommen", "alternative_infobox": "Alternatief auteur-informatievak", "alternative_infobox_hint": "Toont een uitgebreide auteurkaart met avatar en bio op itempagina's", + "alternative_steuerung": "Icoon-navigatiestijl", + "alternative_steuerung_hint": "Vervangt tekstnavigatie (← terug | willekeurig | verder →) door compacte chevron-iconen", "disable_autoplay": "Automatisch afspelen uitschakelen", "disable_autoplay_hint": "Voorkomen dat video's en audio automatisch worden afgespeeld", "disable_swiping": "Swipen uitschakelen", diff --git a/src/inc/locales/zange.json b/src/inc/locales/zange.json index c55db23..d54755c 100644 --- a/src/inc/locales/zange.json +++ b/src/inc/locales/zange.json @@ -142,6 +142,8 @@ "modern_layout_hint": "3-Spalten-Layout", "alternative_infobox": "Alternativer Autor-Infoblock", "alternative_infobox_hint": "Zeigt einen erweiterten Autor-Block mit Avatar und Bio auf Beitragsseiten", + "alternative_steuerung": "Icon-Navigationsstil", + "alternative_steuerung_hint": "Ersetzt die Text-Navigation (← zurück | Zufall | weiter →) durch kompakte Chevron-Icons", "disable_autoplay": "Automatische Wiedergabe deaktivieren", "disable_autoplay_hint": "Vermeiden Sie das automatische Abspielen von Videos und Tondateien", "disable_swiping": "Wischen deaktivieren", diff --git a/src/inc/routes/apiv2/settings.mjs b/src/inc/routes/apiv2/settings.mjs index 5d52649..2d9eb68 100644 --- a/src/inc/routes/apiv2/settings.mjs +++ b/src/inc/routes/apiv2/settings.mjs @@ -645,6 +645,24 @@ export default router => { } }); + // Update alternative steuerung preference (per-user toggle for icon-only nav) + group.put(/\/alternative_steuerung/, lib.loggedin, async (req, res) => { + const use_alternative_steuerung = req.post.use_alternative_steuerung === true || req.post.use_alternative_steuerung === 'true'; + try { + await db` + update user_options + set use_alternative_steuerung = ${use_alternative_steuerung} + where user_id = ${+req.session.id} + `; + if (req.session) req.session.use_alternative_steuerung = use_alternative_steuerung; + return res.json({ success: true, use_alternative_steuerung }, 200); + } catch (e) { + console.error('Update alternative_steuerung error:', e); + return res.json({ success: false, msg: 'Error updating preference' }, 500); + } + }); + + // Update per-user language preference group.put(/\/language/, lib.loggedin, async (req, res) => { if (cfg.websrv.allow_language_change === false) { diff --git a/src/index.mjs b/src/index.mjs index d92ab18..dd99084 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -506,7 +506,7 @@ process.on('uncaughtException', err => { if (req.cookies.session) { const 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".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".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".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 @@ -633,9 +633,10 @@ process.on('uncaughtException', err => { hide_koepfe: user[0].hide_koepfe ?? false, language: (user[0].language && user[0].language.trim()) ? user[0].language.trim() : null, use_alternative_infobox: user[0].use_alternative_infobox ?? (cfg.websrv.user_alternative_infobox !== false), + use_alternative_steuerung: user[0].use_alternative_steuerung ?? (cfg.websrv.user_alternative_steuerung !== false), comment_display_mode: user[0].comment_display_mode ?? (cfg.websrv.default_comment_display_mode || 0), force_comment_display_mode: user[0].force_comment_display_mode ?? 0 - }, 'user_id', 'mode', 'theme', 'fullscreen', 'excluded_tags', 'font', 'disable_autoplay', 'disable_swiping', 'show_background', 'ruffle_volume', 'ruffle_background', 'quote_emojis', 'embed_youtube_in_comments', 'hide_koepfe', 'language', 'use_alternative_infobox', 'comment_display_mode', 'force_comment_display_mode') + }, 'user_id', 'mode', 'theme', 'fullscreen', 'excluded_tags', 'font', 'disable_autoplay', 'disable_swiping', 'show_background', 'ruffle_volume', 'ruffle_background', 'quote_emojis', 'embed_youtube_in_comments', 'hide_koepfe', 'language', 'use_alternative_infobox', 'use_alternative_steuerung', 'comment_display_mode', 'force_comment_display_mode') } on conflict ("user_id") do update set theme = excluded.theme, @@ -652,6 +653,7 @@ process.on('uncaughtException', err => { hide_koepfe = excluded.hide_koepfe, language = excluded.language, use_alternative_infobox = excluded.use_alternative_infobox, + use_alternative_steuerung = excluded.use_alternative_steuerung, comment_display_mode = excluded.comment_display_mode, force_comment_display_mode = excluded.force_comment_display_mode, user_id = excluded.user_id @@ -1222,10 +1224,19 @@ process.on('uncaughtException', err => { ? data.user_alternative_infobox : (cfg.websrv.user_alternative_infobox !== false))); + const useAltSteuerung = (req && req.session && typeof req.session.use_alternative_steuerung === 'boolean') + ? req.session.use_alternative_steuerung + : (req && !req.session + ? false + : (data && typeof data.user_alternative_steuerung === 'boolean' + ? data.user_alternative_steuerung + : (cfg.websrv.user_alternative_steuerung !== false))); + data = Object.assign({}, globals, data || {}, { t: perRequestT, lang: perRequestLang, user_alternative_infobox: useAltInfobox, + user_alternative_steuerung: useAltSteuerung, comment_display_mode: (req && req.session && typeof req.session.comment_display_mode === 'number') ? req.session.comment_display_mode : (data && typeof data.comment_display_mode === 'number' diff --git a/views/item-partial-legacy.html b/views/item-partial-legacy.html index df33077..0545f7f 100644 --- a/views/item-partial-legacy.html +++ b/views/item-partial-legacy.html @@ -45,6 +45,7 @@