option for users to delete their own comments
This commit is contained in:
@@ -97,6 +97,7 @@
|
|||||||
],
|
],
|
||||||
"show_mime_picker": true,
|
"show_mime_picker": true,
|
||||||
"embed_youtube_in_comments": true,
|
"embed_youtube_in_comments": true,
|
||||||
|
"allow_comment_deletion": false,
|
||||||
"show_content_warning": true,
|
"show_content_warning": true,
|
||||||
"default_comment_display_mode": 1,
|
"default_comment_display_mode": 1,
|
||||||
"phrases": [
|
"phrases": [
|
||||||
|
|||||||
@@ -2261,7 +2261,8 @@ body.layout-modern .item-sidebar-left .tag-controls {
|
|||||||
|
|
||||||
.admin-pin-btn,
|
.admin-pin-btn,
|
||||||
.admin-edit-btn,
|
.admin-edit-btn,
|
||||||
.admin-delete-btn {
|
.admin-delete-btn,
|
||||||
|
.delete-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -2271,7 +2272,8 @@ body.layout-modern .item-sidebar-left .tag-controls {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-delete-btn:hover {
|
.admin-delete-btn:hover,
|
||||||
|
.delete-btn:hover {
|
||||||
color: #ff4444;
|
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;
|
color: #ff4444 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1891,6 +1891,13 @@ class CommentSystem {
|
|||||||
adminButtons = `<button class="admin-pin-btn ${isPinned ? 'active' : ''}" data-id="${comment.id}" title="${isPinned ? 'Unpin' : 'Pin'}">${pinIcon}</button><button class="admin-edit-btn" data-id="${comment.id}" data-content="${this.escapeHtml(comment.content)}">${this.icons.edit}</button><button class="admin-delete-btn" data-id="${comment.id}">${this.icons.delete}</button>`;
|
adminButtons = `<button class="admin-pin-btn ${isPinned ? 'active' : ''}" data-id="${comment.id}" title="${isPinned ? 'Unpin' : 'Pin'}">${pinIcon}</button><button class="admin-edit-btn" data-id="${comment.id}" data-content="${this.escapeHtml(comment.content)}">${this.icons.edit}</button><button class="admin-delete-btn" data-id="${comment.id}">${this.icons.delete}</button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let userDeleteButton = '';
|
||||||
|
if (!this.isAdmin && !isDeleted && window.f0ckSession?.logged_in && window.f0ckSession?.allow_comment_deletion) {
|
||||||
|
if (comment.username && comment.username === window.f0ckSession.user) {
|
||||||
|
userDeleteButton = `<button class="delete-btn" data-id="${comment.id}" title="Delete Comment">${this.icons.delete}</button>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const pinnedBadge = isPinned ? `<span class="pinned-badge" title="Pinned">${this.icons.pinned}</span>` : '';
|
const pinnedBadge = isPinned ? `<span class="pinned-badge" title="Pinned">${this.icons.pinned}</span>` : '';
|
||||||
const commentClass = isReply ? 'comment reply' : 'comment';
|
const commentClass = isReply ? 'comment reply' : 'comment';
|
||||||
|
|
||||||
@@ -1938,6 +1945,7 @@ class CommentSystem {
|
|||||||
<div class="comment-actions">
|
<div class="comment-actions">
|
||||||
${!isDeleted && currentUserId ? `<button class="reply-btn" data-id="${comment.id}" data-username="${comment.username}" data-display="${this.escapeHtml(comment.display_name || '')}" title="Reply"><i class="fa-solid fa-reply"></i></button><button class="quote-btn" data-id="${comment.id}" data-username="${comment.username}" data-display="${this.escapeHtml(comment.display_name || '')}" title="Quote with Text"><i class="fa-solid fa-quote-left"></i></button><button class="report-comment-btn" data-id="${comment.id}" title="Report Comment" style="background:none;border:none;color:inherit;cursor:pointer;opacity:0.75;padding:0;"><i class="fa-solid fa-triangle-exclamation"></i></button>` : ''}
|
${!isDeleted && currentUserId ? `<button class="reply-btn" data-id="${comment.id}" data-username="${comment.username}" data-display="${this.escapeHtml(comment.display_name || '')}" title="Reply"><i class="fa-solid fa-reply"></i></button><button class="quote-btn" data-id="${comment.id}" data-username="${comment.username}" data-display="${this.escapeHtml(comment.display_name || '')}" title="Quote with Text"><i class="fa-solid fa-quote-left"></i></button><button class="report-comment-btn" data-id="${comment.id}" title="Report Comment" style="background:none;border:none;color:inherit;cursor:pointer;opacity:0.75;padding:0;"><i class="fa-solid fa-triangle-exclamation"></i></button>` : ''}
|
||||||
${adminButtons}
|
${adminButtons}
|
||||||
|
${userDeleteButton}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2433,27 +2441,48 @@ class CommentSystem {
|
|||||||
// User Delete
|
// User Delete
|
||||||
const delBtn = target.closest('.delete-btn');
|
const delBtn = target.closest('.delete-btn');
|
||||||
if (delBtn) {
|
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 id = delBtn.dataset.id;
|
||||||
|
ModAction.confirm('Delete Comment', `Are you sure you want to delete comment <strong>${id}</strong>?`, async () => {
|
||||||
const res = await fetch(`/api/comments/${id}/delete`, {
|
const res = await fetch(`/api/comments/${id}/delete`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token }
|
headers: {
|
||||||
|
'x-csrf-token': window.f0ckSession?.csrf_token
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
if (json.success) this.loadComments();
|
if (json.success) {
|
||||||
else alert('Failed to delete: ' + (json.message || 'Error'));
|
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 = '<span class="deleted-msg">[deleted]</span>';
|
||||||
|
}
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2474,6 +2503,17 @@ class CommentSystem {
|
|||||||
});
|
});
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
if (json.success) {
|
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}`);
|
const commentEl = document.getElementById(`c${id}`);
|
||||||
if (commentEl) {
|
if (commentEl) {
|
||||||
commentEl.classList.add('deleted');
|
commentEl.classList.add('deleted');
|
||||||
|
|||||||
@@ -5764,6 +5764,17 @@ class NotificationSystem {
|
|||||||
if (contentEl) contentEl.innerHTML = '<span class="deleted-msg">[deleted]</span>';
|
if (contentEl) contentEl.innerHTML = '<span class="deleted-msg">[deleted]</span>';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 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') {
|
} else if (data.data.type === 'edit') {
|
||||||
if (window.commentSystem && typeof window.commentSystem.handleLiveEdit === 'function') {
|
if (window.commentSystem && typeof window.commentSystem.handleLiveEdit === 'function') {
|
||||||
window.commentSystem.handleLiveEdit(data.data);
|
window.commentSystem.handleLiveEdit(data.data);
|
||||||
|
|||||||
@@ -543,9 +543,15 @@ export default (router, tpl) => {
|
|||||||
const comment = await db`SELECT content, item_id, user_id FROM comments WHERE id = ${commentId}`;
|
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 (!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) {
|
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" }) });
|
return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Forbidden" }) });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Log all deletions in audit log
|
// Log all deletions in audit log
|
||||||
const reason = (req.post && req.post.reason) ? req.post.reason : (req.url.qs?.reason || 'No reason provided');
|
const reason = (req.post && req.post.reason) ? req.post.reason : (req.url.qs?.reason || 'No reason provided');
|
||||||
|
|||||||
@@ -80,3 +80,7 @@ export const setLogUserIps = (val) => {}; // No-op, strictly config-based
|
|||||||
|
|
||||||
export const getHashUserIps = () => !!cfg.websrv.hash_user_ips;
|
export const getHashUserIps = () => !!cfg.websrv.hash_user_ips;
|
||||||
export const setHashUserIps = (val) => {}; // No-op, strictly config-based
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { handleMetaExtract } from "./meta_extract_handler.mjs";
|
|||||||
import { handleMetaStrip } from "./meta_strip_handler.mjs";
|
import { handleMetaStrip } from "./meta_strip_handler.mjs";
|
||||||
import { handleCommentUpload } from "./comment_upload_handler.mjs";
|
import { handleCommentUpload } from "./comment_upload_handler.mjs";
|
||||||
import { handleDmAttachmentUpload, handleDmAttachmentDownload, handleDmAttachmentDelete } from "./dm_attachment_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 { updateHallsCache, getHalls } from "./inc/halls_cache.mjs";
|
||||||
import { createI18n } from "./inc/i18n.mjs";
|
import { createI18n } from "./inc/i18n.mjs";
|
||||||
import security from "./inc/security.mjs";
|
import security from "./inc/security.mjs";
|
||||||
@@ -1114,6 +1114,7 @@ process.on('uncaughtException', err => {
|
|||||||
get private_messages() { return getPrivateMessages(); },
|
get private_messages() { return getPrivateMessages(); },
|
||||||
get dm_attachments() { return getDmAttachments(); },
|
get dm_attachments() { return getDmAttachments(); },
|
||||||
get dm_unencrypted() { return getDmUnencrypted(); },
|
get dm_unencrypted() { return getDmUnencrypted(); },
|
||||||
|
get allow_comment_deletion() { return getAllowCommentDeletion(); },
|
||||||
get enable_pdf() { return getEnablePdf(); },
|
get enable_pdf() { return getEnablePdf(); },
|
||||||
get enable_cleanup() { return getEnableCleanup(); },
|
get enable_cleanup() { return getEnableCleanup(); },
|
||||||
get cleanup_start_date() { return getCleanupStartDate(); },
|
get cleanup_start_date() { return getCleanupStartDate(); },
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@if(session && (session.admin || session.is_moderator))
|
@if(session)
|
||||||
<div id="mod-action-modal" class="modal-overlay" style="display:none;">
|
<div id="mod-action-modal" class="modal-overlay" style="display:none;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h3 id="mod-action-title">{{ t('mod.confirm_action') }}</h3>
|
<h3 id="mod-action-title">{{ t('mod.confirm_action') }}</h3>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
@endif
|
@endif
|
||||||
@if(private_society && !session)
|
@if(private_society && !session)
|
||||||
<script>
|
<script>
|
||||||
window.f0ckSession = { logged_in: false, enable_xd_score: @if(enable_xd_score) true @else false @endif, default_theme: "{{ default_theme }}", show_content_warning: @if(show_content_warning) true @else false @endif, use_new_layout: @if(default_layout === 'legacy')false @else true @endif, comment_display_mode: {{ comment_display_mode }}, comment_max_length: {{ comment_max_length !== null && comment_max_length !== undefined ? comment_max_length : 'null' }}, development: @if(development) true @else false @endif };
|
window.f0ckSession = { logged_in: false, enable_xd_score: @if(enable_xd_score) true @else false @endif, default_theme: "{{ default_theme }}", show_content_warning: @if(show_content_warning) true @else false @endif, use_new_layout: @if(default_layout === 'legacy')false @else true @endif, comment_display_mode: {{ comment_display_mode }}, comment_max_length: {{ comment_max_length !== null && comment_max_length !== undefined ? comment_max_length : 'null' }}, development: @if(development) true @else false @endif, allow_comment_deletion: @if(allow_comment_deletion) true @else false @endif };
|
||||||
window.f0ckDebug = window.f0ckSession.development ? console.log.bind(console) : () => {};
|
window.f0ckDebug = window.f0ckSession.development ? console.log.bind(console) : () => {};
|
||||||
(() => {
|
(() => {
|
||||||
const loginModal = document.getElementById('login-modal');
|
const loginModal = document.getElementById('login-modal');
|
||||||
@@ -414,7 +414,8 @@
|
|||||||
fileupload_comments_max: {{ fileupload_comments_max }},
|
fileupload_comments_max: {{ fileupload_comments_max }},
|
||||||
fileupload_comments_mode: "{{ fileupload_comments_mode }}",
|
fileupload_comments_mode: "{{ fileupload_comments_mode }}",
|
||||||
dm_attachments: @if(dm_attachments) true @else false @endif,
|
dm_attachments: @if(dm_attachments) true @else false @endif,
|
||||||
dm_unencrypted: @if(dm_unencrypted) true @else false @endif
|
dm_unencrypted: @if(dm_unencrypted) true @else false @endif,
|
||||||
|
allow_comment_deletion: @if(allow_comment_deletion) true @else false @endif
|
||||||
};
|
};
|
||||||
window.f0ckDebug = window.f0ckSession.development ? console.log.bind(console) : () => {};
|
window.f0ckDebug = window.f0ckSession.development ? console.log.bind(console) : () => {};
|
||||||
window.f0ckI18n = {
|
window.f0ckI18n = {
|
||||||
|
|||||||
Reference in New Issue
Block a user