From d30642ca4a5c62fbbabfc9f1fd3a2c7822cbf599 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Wed, 3 Jun 2026 12:25:57 +0200 Subject: [PATCH] QoL fixes --- public/s/js/f0ckm.js | 40 ++++++++++++++++++++++++-- public/s/js/meme-creator.js | 21 ++++++++++---- public/s/js/scroller.js | 14 +++++++-- public/s/js/upload.js | 35 +++++++++++----------- src/inc/routes/notifications.mjs | 16 ++++++----- src/inc/routes/warnings.mjs | 5 ++++ views/notifications.html | 6 ++-- views/snippets/notifications-list.html | 12 ++++++++ 8 files changed, 110 insertions(+), 39 deletions(-) diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index f831b5a..83269dd 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -283,6 +283,8 @@ window.cancelAnimFrame = (function () { baseStyle += 'background:rgba(200,30,30,0.95);'; } else if (type === 'success') { baseStyle += 'background:rgba(30,130,60,0.95);'; + } else if (type === 'warning') { + baseStyle += 'background:rgba(220,180,0,0.95);color:#000;'; } else { baseStyle += 'background:rgba(30,30,30,0.95);'; } @@ -5985,7 +5987,7 @@ if (sbtForm) { class NotificationSystem { // Notification type categorization static USER_TYPES = ['comment_reply', 'subscription', 'mention', 'upload_comment']; - static SYSTEM_TYPES = ['approve', 'deny', 'item_deleted', 'upload_success', 'upload_error', 'admin_pending', 'report']; + static SYSTEM_TYPES = ['approve', 'deny', 'item_deleted', 'upload_success', 'upload_error', 'admin_pending', 'report', 'warning']; constructor() { this.bell = document.getElementById('nav-notif-btn'); @@ -6185,7 +6187,7 @@ class NotificationSystem { // System notifications (deletion, deny, reports) require explicit acknowledgment — // never auto-mark them as read just because the user is viewing that item. - const isSystemNotif = ['item_deleted', 'deny', 'admin_pending', 'report'].includes(notifType); + const isSystemNotif = ['item_deleted', 'deny', 'admin_pending', 'report', 'warning'].includes(notifType); // If the user is currently viewing this item, mark comment-type notifications as read immediately // (they are live on the thread, so no need to show a badge/highlight) @@ -6950,6 +6952,11 @@ class NotificationSystem { link = '/mod/reports'; user = (window.f0ckI18n && window.f0ckI18n.notif_moderation) || 'Moderator'; msg = (window.f0ckI18n && window.f0ckI18n.notif_new_report) || 'A new user report has been submitted'; + } else if (n.type === 'warning') { + link = `/notifications?tab=system#notif-${n.id}`; + user = (window.f0ckI18n && window.f0ckI18n.notif_system) || 'System'; + msg = (window.f0ckI18n && window.f0ckI18n.account_warning && window.f0ckI18n.account_warning.title) || 'Account Warning'; + if (n.reason) msg += `
${n.reason}
`; } else { // Comment notification link = `/${n.item_id}#c${n.comment_id || n.reference_id}`; @@ -6961,7 +6968,18 @@ class NotificationSystem { // For admin_pending the thumbnail lives in /mod/pending/t/ until approved let thumbSrc, thumbOnerror; - if (n.type === 'admin_pending') { + if (n.type === 'warning') { + return ` +
+
+
+
${user}
+
${msg}
+
${new Date(n.created_at).toLocaleString()}
+
+
+ `; + } else if (n.type === 'admin_pending') { thumbSrc = `/mod/pending/t/${n.item_id}.webp`; thumbOnerror = `this.onerror=null;this.src='/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';}`; } else { @@ -7080,6 +7098,22 @@ class NotificationSystem { `; } + if (n.type === 'warning') { + const link = `/notifications?tab=system#notif-${n.id}`; + return ` + +
+
+
+ ${(window.f0ckI18n && window.f0ckI18n.account_warning && window.f0ckI18n.account_warning.title) || 'Account Warning'} +
${n.reason || 'No reason provided'}
+
+ ${new Date(n.created_at).toLocaleString()} +
+
+ `; + } + let typeText = 'Start'; if (n.type === 'comment_reply') typeText = (window.f0ckI18n && window.f0ckI18n.notif_replied) || 'replied to you'; else if (n.type === 'subscription') typeText = (window.f0ckI18n && window.f0ckI18n.notif_subscribed) || 'commented in a thread you follow'; diff --git a/public/s/js/meme-creator.js b/public/s/js/meme-creator.js index 12948bb..036fa4e 100644 --- a/public/s/js/meme-creator.js +++ b/public/s/js/meme-creator.js @@ -543,13 +543,22 @@ const result = await res.json(); if (result.success) { - const dest = result.redirect || '/meme'; - if (window.loadItemAjax) { - window.loadItemAjax(dest); - } else if (window.loadPageAjax) { - window.loadPageAjax(dest); + if (result.manual_approval) { + window.flashMessage(window.f0ckI18n?.upload_pending_approval_patient || 'Upload awaits approval', 3000, 'warning'); + if (window.loadPageAjax) { + window.loadPageAjax('/'); + } else { + window.location.href = '/'; + } } else { - window.location.href = dest; + const dest = result.redirect || '/meme'; + if (window.loadItemAjax) { + window.loadItemAjax(dest); + } else if (window.loadPageAjax) { + window.loadPageAjax(dest); + } else { + window.location.href = dest; + } } } else { diff --git a/public/s/js/scroller.js b/public/s/js/scroller.js index 42abb97..109b49d 100644 --- a/public/s/js/scroller.js +++ b/public/s/js/scroller.js @@ -3496,7 +3496,7 @@ // Tab type arrays const SCROLLER_USER_TYPES = ['comment_reply', 'subscription', 'mention', 'upload_comment']; - const SCROLLER_SYSTEM_TYPES = ['approve', 'deny', 'item_deleted', 'upload_success', 'upload_error', 'admin_pending', 'report']; + const SCROLLER_SYSTEM_TYPES = ['approve', 'deny', 'item_deleted', 'upload_success', 'upload_error', 'admin_pending', 'report', 'warning']; let sActiveTab = 'user'; let sCachedNotifs = []; @@ -3600,6 +3600,9 @@ } else if (n.type === 'report') { link = '/mod/reports'; user = i18n.notif_moderation || 'Moderator'; msg = i18n.notif_new_report || 'New user report'; + } else if (n.type === 'warning') { + link = `/notifications?tab=system#notif-${n.id}`; user = i18n.notif_system || 'System'; + msg = (i18n.account_warning && i18n.account_warning.title) || 'Account Warning'; } else { link = `/${n.item_id}#c${n.reference_id}`; if (n.type === 'comment_reply') msg = i18n.notif_replied || 'replied to you'; @@ -3607,8 +3610,13 @@ else if (n.type === 'mention') msg = i18n.notif_mentioned || 'highlighted you'; else msg = i18n.notif_commented || 'commented'; } - const thumbSrc = n.type === 'admin_pending' ? `/mod/pending/t/${n.item_id}.webp` : `/t/${n.item_id}.webp`; - const thumb = n.item_id ? `
` : ''; + let thumb; + if (n.type === 'warning') { + thumb = `
`; + } else { + const thumbSrc = n.type === 'admin_pending' ? `/mod/pending/t/${n.item_id}.webp` : `/t/${n.item_id}.webp`; + thumb = n.item_id ? `
` : ''; + } return ` ${thumb}
diff --git a/public/s/js/upload.js b/public/s/js/upload.js index b5c2ffe..74dbdc2 100644 --- a/public/s/js/upload.js +++ b/public/s/js/upload.js @@ -1971,18 +1971,18 @@ window.initUploadForm = (selector) => { form._f0ckUploader.reset(); if (isShitpost) { - // Flash message removed as requested - if (lastData?.manual_approval && typeof window.showFlash === 'function') { - window.showFlash('Upload awaits approval, please be patient', 'info'); + if (lastData?.manual_approval && typeof window.flashMessage === 'function') { + window.flashMessage(window.f0ckI18n?.upload_pending_approval_patient || 'Upload awaits approval', 3000, 'warning'); } } else { - if (!dragModal && statusDiv) { + if (lastData?.manual_approval) { + if (typeof window.flashMessage === 'function') { + window.flashMessage(window.f0ckI18n?.upload_pending_approval_patient || 'Upload awaits approval', 3000, 'warning'); + } + } else if (!dragModal && statusDiv) { statusDiv.innerHTML = '✓ ' + (lastData?.msg || 'Upload successful'); statusDiv.className = 'upload-status success'; } - if (lastData?.manual_approval && typeof window.showFlash === 'function') { - window.showFlash('Upload awaits approval, please be patient', 'info'); - } } setTimeout(() => { @@ -2127,17 +2127,18 @@ window.initUploadForm = (selector) => { if (dragModal) dragModal.classList.remove('show'); form._f0ckUploader.reset(); if (isShitpost) { - // Flash message removed as requested - if (lastData?.manual_approval && typeof window.showFlash === 'function') { - window.showFlash('Upload awaits approval, please be patient', 'info'); + if (lastData?.manual_approval && typeof window.flashMessage === 'function') { + window.flashMessage(window.f0ckI18n?.upload_pending_approval_patient || 'Upload awaits approval', 3000, 'warning'); + } + } else { + if (lastData?.manual_approval) { + if (typeof window.flashMessage === 'function') { + window.flashMessage(window.f0ckI18n?.upload_pending_approval_patient || 'Upload awaits approval', 3000, 'warning'); + } + } else if (!dragModal && statusDiv) { + statusDiv.innerHTML = '✓ ' + (lastData?.msg || 'Upload successful'); + statusDiv.className = 'upload-status success'; } - } else if (!dragModal && statusDiv) { - statusDiv.innerHTML = '✓ ' + (lastData?.msg || 'Upload successful'); - statusDiv.className = 'upload-status success'; - } - - if (!isShitpost && lastData?.manual_approval && typeof window.showFlash === 'function') { - window.showFlash('Upload awaits approval, please be patient', 'info'); } setTimeout(() => { diff --git a/src/inc/routes/notifications.mjs b/src/inc/routes/notifications.mjs index 3c0d274..db8d0d1 100644 --- a/src/inc/routes/notifications.mjs +++ b/src/inc/routes/notifications.mjs @@ -57,7 +57,8 @@ db.listen('notifications', (payload) => { if (client.do_not_disturb === true) continue; if (SYSTEM_TYPES.includes(data.type) && client.receive_system_notifications === false) continue; - if (USER_TYPES.includes(data.type) && client.receive_user_notifications === false) continue; + // warnings bypass user settings + if (data.type !== 'warning' && USER_TYPES.includes(data.type) && client.receive_user_notifications === false) continue; client.send({ type: 'notify', data }); } } @@ -343,7 +344,7 @@ db.listen('global_chat_topic', (payload) => { 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']; + const SYSTEM_TYPES = ['approve', 'deny', 'item_deleted', 'upload_success', 'upload_error', 'admin_pending', 'report', 'warning']; async function getNotificationHistory(userId, page = 1, limit = 50, tab = null) { const offset = (page - 1) * limit; @@ -363,7 +364,7 @@ export default (router, tpl) => { LEFT JOIN items i ON n.item_id = i.id WHERE n.user_id = ${userId} AND n.type = ANY(${typeFilter}) - AND (n.type IN ('admin_pending', 'deny', 'item_deleted', 'report') OR i.id IS NULL OR (i.active = true AND i.is_deleted = false)) + AND (n.type IN ('admin_pending', 'deny', 'item_deleted', 'report', 'warning') OR i.id IS NULL OR (i.active = true AND i.is_deleted = false)) ORDER BY n.created_at DESC LIMIT ${limit + 1} OFFSET ${offset} @@ -381,7 +382,7 @@ export default (router, tpl) => { LEFT JOIN user_options uo ON u.id = uo.user_id LEFT JOIN items i ON n.item_id = i.id WHERE n.user_id = ${userId} - AND (n.type IN ('admin_pending', 'deny', 'item_deleted', 'report') OR i.id IS NULL OR (i.active = true AND i.is_deleted = false)) + AND (n.type IN ('admin_pending', 'deny', 'item_deleted', 'report', 'warning') OR i.id IS NULL OR (i.active = true AND i.is_deleted = false)) ORDER BY n.created_at DESC LIMIT ${limit + 1} OFFSET ${offset} @@ -426,7 +427,7 @@ export default (router, tpl) => { LEFT JOIN user_options uo ON u.id = uo.user_id LEFT JOIN items i ON n.item_id = i.id WHERE n.user_id = ${req.session.id} AND n.is_read = false - AND (n.type IN ('admin_pending', 'deny', 'item_deleted', 'report', 'approve') + AND (n.type IN ('admin_pending', 'deny', 'item_deleted', 'report', 'approve', 'warning') OR ( ${req.session.do_not_disturb !== true} AND ( (n.type IN ('upload_success', 'upload_error') AND ${req.session.receive_system_notifications !== false}) @@ -434,7 +435,7 @@ export default (router, tpl) => { ) ) ) - AND (n.item_id IS NULL OR (i.active = true AND i.is_deleted = false) OR n.type IN ('admin_pending', 'deny', 'item_deleted', 'report')) + AND (n.item_id IS NULL OR (i.active = true AND i.is_deleted = false) OR n.type IN ('admin_pending', 'deny', 'item_deleted', 'report', 'warning')) ORDER BY n.created_at DESC LIMIT 1000 `; @@ -496,7 +497,7 @@ export default (router, tpl) => { router.post(/\/api\/notifications\/item\/(?\d+)\/read/, async (req, res) => { if (!req.session) return res.reply({ code: 401, body: JSON.stringify({ success: false }) }); const itemId = req.params.itemId; - const SYSTEM_TYPES = ['item_deleted', 'deny', 'admin_pending', 'report']; + const SYSTEM_TYPES = ['item_deleted', 'deny', 'admin_pending', 'report', 'warning']; console.log(`[NotificationRoute] Marking comment notifications for item ${itemId} as read for user ${req.session.id}`); try { await db` @@ -646,6 +647,7 @@ export default (router, tpl) => { next: data.hasMore ? 2 : null }; data.link = { main: '/notifications', path: '/' }; + data.activeTab = tab; data.domain = cfg.main.url.domain; // For header return res.html(tpl.render('notifications', data, req)); }); diff --git a/src/inc/routes/warnings.mjs b/src/inc/routes/warnings.mjs index 78a5a27..7303a70 100644 --- a/src/inc/routes/warnings.mjs +++ b/src/inc/routes/warnings.mjs @@ -21,6 +21,11 @@ export default (router, tpl) => { // Broadcast to SSE clients instantly if (result.length > 0) { + await db` + INSERT INTO notifications (user_id, type, reference_id, data, is_read) + VALUES (${+user_id}, 'warning', 0, ${JSON.stringify({ reason: reason.trim(), warning_id: result[0].id })}, false) + `; + await db`SELECT pg_notify('warnings', ${JSON.stringify({ user_id: +user_id, warning_id: result[0].id, diff --git a/views/notifications.html b/views/notifications.html index b7060c3..3bc3332 100644 --- a/views/notifications.html +++ b/views/notifications.html @@ -7,10 +7,10 @@
- - + +
-
+
@include(snippets/notifications-list)
@if(pagination.next) diff --git a/views/snippets/notifications-list.html b/views/snippets/notifications-list.html index 7e4bd97..3a4defe 100644 --- a/views/snippets/notifications-list.html +++ b/views/snippets/notifications-list.html @@ -103,6 +103,18 @@
{{ new Date(n.created_at).toLocaleString() }}
+@elseif(n.type === 'warning') +
+
+
+
{{ t('notifications.system') }}
+
+ Account Warning +
Reason: {{ n.reason }}
+
+
{{ new Date(n.created_at).toLocaleString() }}
+
+
@else @if(n.item_id)