From bb4125601f51521df218389e0af20fef45fc7435 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Sun, 24 May 2026 10:05:40 +0200 Subject: [PATCH] option for users to delete their own comments --- config_example.json | 1 + public/s/css/f0ckm.css | 9 +++-- public/s/js/comments.js | 80 +++++++++++++++++++++++++++---------- public/s/js/f0ckm.js | 11 +++++ src/inc/routes/comments.mjs | 10 ++++- src/inc/settings.mjs | 4 ++ src/index.mjs | 3 +- views/snippets/footer.html | 7 ++-- 8 files changed, 96 insertions(+), 29 deletions(-) diff --git a/config_example.json b/config_example.json index 3fc91b6..15746db 100644 --- a/config_example.json +++ b/config_example.json @@ -97,6 +97,7 @@ ], "show_mime_picker": true, "embed_youtube_in_comments": true, + "allow_comment_deletion": false, "show_content_warning": true, "default_comment_display_mode": 1, "phrases": [ diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 57029e2..1308a2a 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -2261,7 +2261,8 @@ body.layout-modern .item-sidebar-left .tag-controls { .admin-pin-btn, .admin-edit-btn, -.admin-delete-btn { +.admin-delete-btn, +.delete-btn { background: none; border: none; padding: 0; @@ -2271,7 +2272,8 @@ body.layout-modern .item-sidebar-left .tag-controls { text-decoration: underline; } -.admin-delete-btn:hover { +.admin-delete-btn:hover, +.delete-btn:hover { color: #ff4444; } @@ -2799,7 +2801,8 @@ body.layout-legacy .scroll-to-bottom svg { } -.admin-delete-btn:hover { +.admin-delete-btn:hover, +.delete-btn:hover { color: #ff4444 !important; } diff --git a/public/s/js/comments.js b/public/s/js/comments.js index 90c65e3..1ea4f2e 100644 --- a/public/s/js/comments.js +++ b/public/s/js/comments.js @@ -1891,6 +1891,13 @@ class CommentSystem { adminButtons = ``; } + let userDeleteButton = ''; + if (!this.isAdmin && !isDeleted && window.f0ckSession?.logged_in && window.f0ckSession?.allow_comment_deletion) { + if (comment.username && comment.username === window.f0ckSession.user) { + userDeleteButton = ``; + } + } + const pinnedBadge = isPinned ? `${this.icons.pinned}` : ''; const commentClass = isReply ? 'comment reply' : 'comment'; @@ -1938,6 +1945,7 @@ class CommentSystem {
${!isDeleted && currentUserId ? `` : ''} ${adminButtons} + ${userDeleteButton}
@@ -2433,27 +2441,48 @@ class CommentSystem { // User Delete const delBtn = target.closest('.delete-btn'); if (delBtn) { - if (delBtn.dataset.confirming !== 'true') { - delBtn.dataset.confirming = 'true'; - const originalText = delBtn.innerHTML; - delBtn.innerHTML = 'Sure?'; - delBtn.classList.add('btn-danger'); // Optional styling - setTimeout(() => { - delBtn.dataset.confirming = 'false'; - delBtn.innerHTML = originalText; - delBtn.classList.remove('btn-danger'); - }, 3000); - return; - } - const id = delBtn.dataset.id; - const res = await fetch(`/api/comments/${id}/delete`, { - method: 'POST', - headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token } - }); - const json = await res.json(); - if (json.success) this.loadComments(); - else alert('Failed to delete: ' + (json.message || 'Error')); + ModAction.confirm('Delete Comment', `Are you sure you want to delete comment ${id}?`, async () => { + const res = await fetch(`/api/comments/${id}/delete`, { + method: 'POST', + headers: { + 'x-csrf-token': window.f0ckSession?.csrf_token + } + }); + const json = await res.json(); + if (json.success) { + const sidebarEl = document.getElementById('sc' + id); + if (sidebarEl) { + sidebarEl.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; + sidebarEl.style.opacity = '0'; + sidebarEl.style.transform = 'scale(0.95)'; + setTimeout(() => sidebarEl.remove(), 300); + } + if (window._sidebarActivityCache) { + window._sidebarActivityCache = window._sidebarActivityCache.filter(c => String(c.id) !== String(id)); + } + + const commentEl = document.getElementById(`c${id}`); + if (commentEl) { + commentEl.classList.add('deleted'); + const contentEl = commentEl.querySelector('.comment-content'); + if (contentEl) { + contentEl.innerHTML = '[deleted]'; + } + const actionsEl = commentEl.querySelector('.comment-actions'); + if (actionsEl) { + actionsEl.innerHTML = ''; // Remove reply/quote/admin buttons + } + // Remove attachments if present + const attachmentsEl = commentEl.querySelector('.comment-attachments, .media-pills'); + if (attachmentsEl) attachmentsEl.remove(); + } else { + this.loadComments(); + } + } else { + throw new Error(json.message || 'Failed to delete'); + } + }, { hideReason: true }); return; } @@ -2474,6 +2503,17 @@ class CommentSystem { }); const json = await res.json(); if (json.success) { + const sidebarEl = document.getElementById('sc' + id); + if (sidebarEl) { + sidebarEl.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; + sidebarEl.style.opacity = '0'; + sidebarEl.style.transform = 'scale(0.95)'; + setTimeout(() => sidebarEl.remove(), 300); + } + if (window._sidebarActivityCache) { + window._sidebarActivityCache = window._sidebarActivityCache.filter(c => String(c.id) !== String(id)); + } + const commentEl = document.getElementById(`c${id}`); if (commentEl) { commentEl.classList.add('deleted'); diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index 7ea5518..84b9f06 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -5764,6 +5764,17 @@ class NotificationSystem { if (contentEl) contentEl.innerHTML = '[deleted]'; } }); + // Also vanish from sidebar + const sidebarEl = document.getElementById('sc' + data.data.comment_id); + if (sidebarEl) { + sidebarEl.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; + sidebarEl.style.opacity = '0'; + sidebarEl.style.transform = 'scale(0.95)'; + setTimeout(() => sidebarEl.remove(), 300); + } + if (window._sidebarActivityCache) { + window._sidebarActivityCache = window._sidebarActivityCache.filter(c => String(c.id) !== String(data.data.comment_id)); + } } else if (data.data.type === 'edit') { if (window.commentSystem && typeof window.commentSystem.handleLiveEdit === 'function') { window.commentSystem.handleLiveEdit(data.data); diff --git a/src/inc/routes/comments.mjs b/src/inc/routes/comments.mjs index 0e36930..389ebb6 100644 --- a/src/inc/routes/comments.mjs +++ b/src/inc/routes/comments.mjs @@ -543,8 +543,14 @@ export default (router, tpl) => { const comment = await db`SELECT content, item_id, user_id FROM comments WHERE id = ${commentId}`; if (!comment.length) return res.reply({ code: 404, body: JSON.stringify({ success: false, message: "Not found" }) }); - if (!req.session.admin && !req.session.is_moderator && comment[0].user_id !== req.session.id) { - return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Forbidden" }) }); + const { getAllowCommentDeletion } = await import("../settings.mjs"); + const canDeleteOwn = getAllowCommentDeletion(); + const isOwner = comment[0].user_id === req.session.id; + + if (!req.session.admin && !req.session.is_moderator) { + if (!canDeleteOwn || !isOwner) { + return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Forbidden" }) }); + } } // Log all deletions in audit log diff --git a/src/inc/settings.mjs b/src/inc/settings.mjs index 85fa7d6..387007a 100644 --- a/src/inc/settings.mjs +++ b/src/inc/settings.mjs @@ -80,3 +80,7 @@ export const setLogUserIps = (val) => {}; // No-op, strictly config-based export const getHashUserIps = () => !!cfg.websrv.hash_user_ips; export const setHashUserIps = (val) => {}; // No-op, strictly config-based + +export const getAllowCommentDeletion = () => !!cfg.websrv.allow_comment_deletion; +export const setAllowCommentDeletion = (val) => {}; // No-op, strictly config-based + diff --git a/src/index.mjs b/src/index.mjs index b2737b4..f73965a 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -18,7 +18,7 @@ import { handleMetaExtract } from "./meta_extract_handler.mjs"; import { handleMetaStrip } from "./meta_strip_handler.mjs"; import { handleCommentUpload } from "./comment_upload_handler.mjs"; import { handleDmAttachmentUpload, handleDmAttachmentDownload, handleDmAttachmentDelete } from "./dm_attachment_handler.mjs"; -import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getBypassDuplicateCheck, setBypassDuplicateCheck, getProtectFiles, setProtectFiles, getPrivateMessages, setPrivateMessages, getDmAttachments, setDmAttachments, getDmUnencrypted, setDmUnencrypted, getDefaultLayout, setDefaultLayout, getEnablePdf, setEnablePdf, getEnableCleanup, setEnableCleanup, getCleanupStartDate, setCleanupStartDate, getCleanupEndDate, setCleanupEndDate, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps, getShitpostMode, setShitpostMode } from "./inc/settings.mjs"; +import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getBypassDuplicateCheck, setBypassDuplicateCheck, getProtectFiles, setProtectFiles, getPrivateMessages, setPrivateMessages, getDmAttachments, setDmAttachments, getDmUnencrypted, setDmUnencrypted, getDefaultLayout, setDefaultLayout, getEnablePdf, setEnablePdf, getEnableCleanup, setEnableCleanup, getCleanupStartDate, setCleanupStartDate, getCleanupEndDate, setCleanupEndDate, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps, getShitpostMode, setShitpostMode, getAllowCommentDeletion, setAllowCommentDeletion } from "./inc/settings.mjs"; import { updateHallsCache, getHalls } from "./inc/halls_cache.mjs"; import { createI18n } from "./inc/i18n.mjs"; import security from "./inc/security.mjs"; @@ -1114,6 +1114,7 @@ process.on('uncaughtException', err => { get private_messages() { return getPrivateMessages(); }, get dm_attachments() { return getDmAttachments(); }, get dm_unencrypted() { return getDmUnencrypted(); }, + get allow_comment_deletion() { return getAllowCommentDeletion(); }, get enable_pdf() { return getEnablePdf(); }, get enable_cleanup() { return getEnableCleanup(); }, get cleanup_start_date() { return getCleanupStartDate(); }, diff --git a/views/snippets/footer.html b/views/snippets/footer.html index 916306c..83d0950 100644 --- a/views/snippets/footer.html +++ b/views/snippets/footer.html @@ -1,4 +1,4 @@ -@if(session && (session.admin || session.is_moderator)) +@if(session)