alotta good shit
This commit is contained in:
+236
-420
@@ -3,448 +3,264 @@
|
||||
<div class="pagewrapper">
|
||||
<div id="main">
|
||||
<style>
|
||||
.mod-reports-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 8px;
|
||||
color: var(--white);
|
||||
.rp-page { max-width: 900px; margin: 0 auto; padding: 30px 15px 60px; }
|
||||
.rp-header { margin-bottom: 28px; }
|
||||
.rp-header h2 { margin: 0; font-weight: 800; letter-spacing: -0.5px; }
|
||||
.rp-header p { color: #888; margin: 5px 0 0 0; font-size: 0.9em; }
|
||||
|
||||
.rp-filter-bar { display: flex; gap: 10px; align-items: center; margin-bottom: 24px; flex-wrap: wrap; }
|
||||
.rp-filter-bar select {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.12);
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
padding: 7px 12px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
.mod-reports-table th {
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 1px;
|
||||
color: #888;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
.mod-reports-table tr {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.mod-reports-table tbody tr {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
.mod-reports-table tbody tr:hover:not(.expanded-report) {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.mod-reports-table td {
|
||||
padding: 15px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.mod-reports-table .btn, .mod-reports-table button, .btn-modern {
|
||||
border-radius: 4px !important;
|
||||
font-size: 0.75rem;
|
||||
.rp-filter-bar button {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.12);
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
padding: 7px 14px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.rp-filter-bar button:hover { background: rgba(255,255,255,0.1); }
|
||||
|
||||
.rp-feed { display: flex; flex-direction: column; gap: 14px; }
|
||||
.rp-state-msg { color: #666; font-style: italic; text-align: center; padding: 40px 0; }
|
||||
|
||||
/* Card */
|
||||
.rp-card {
|
||||
background: rgba(255,255,255,0.025);
|
||||
border: 1px solid rgba(255,255,255,0.07);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.rp-card:hover { border-color: rgba(255,255,255,0.14); }
|
||||
.rp-card.illegal-flag { border-left: 3px solid #dc3545; }
|
||||
|
||||
/* Card top bar */
|
||||
.rp-card-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 16px;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.rp-card-bar-left { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.rp-card-id {
|
||||
font-family: monospace;
|
||||
font-size: 0.78em;
|
||||
color: #666;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 4px;
|
||||
padding: 1px 7px;
|
||||
}
|
||||
.rp-card-time { font-size: 0.78em; color: #666; }
|
||||
.rp-status-badge {
|
||||
font-size: 0.7em;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 8px 16px;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.btn-modern {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
.rp-status-pending { background: rgba(255,193,7,0.15); color: #ffc107; border: 1px solid rgba(255,193,7,0.3); }
|
||||
.rp-status-resolved { background: rgba(40,167,69,0.15); color: #28a745; border: 1px solid rgba(40,167,69,0.3); }
|
||||
.rp-status-rejected { background: rgba(108,117,125,0.2); color: #aaa; border: 1px solid rgba(108,117,125,0.3); }
|
||||
|
||||
/* Card body */
|
||||
.rp-card-body {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* Media preview column */
|
||||
.rp-preview {
|
||||
width: 180px;
|
||||
min-width: 180px;
|
||||
background: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rp-preview img,
|
||||
.rp-preview video {
|
||||
width: 180px;
|
||||
height: 140px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.rp-preview-link {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0,0,0,0.55);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
color: #fff;
|
||||
font-size: 1.4em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.rp-preview:hover .rp-preview-link { opacity: 1; }
|
||||
.rp-no-preview {
|
||||
width: 180px;
|
||||
height: 140px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #444;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
/* Info column */
|
||||
.rp-info {
|
||||
flex: 1;
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.rp-meta-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.83em;
|
||||
align-items: center;
|
||||
}
|
||||
.rp-meta-label { color: #555; text-transform: uppercase; font-size: 0.75em; font-weight: 700; letter-spacing: 0.5px; }
|
||||
.rp-reporter-link { color: var(--accent); font-weight: 600; text-decoration: none; }
|
||||
.rp-reporter-link:hover { text-decoration: underline; }
|
||||
.rp-reporter-ip { color: #555; font-size: 0.88em; }
|
||||
.rp-target-link { color: #ddd; font-weight: 500; text-decoration: none; }
|
||||
.rp-target-link:hover { color: #fff; }
|
||||
.rp-open-link { color: var(--accent); margin-left: 4px; font-size: 0.85em; }
|
||||
|
||||
/* Category badges */
|
||||
.rp-cats { display: flex; flex-wrap: wrap; gap: 5px; }
|
||||
.rp-cat {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 9px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
/* Reason */
|
||||
.rp-reason {
|
||||
font-size: 0.85em;
|
||||
color: #bbb;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-left: 2px solid rgba(255,255,255,0.08);
|
||||
padding: 6px 10px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
max-height: 80px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Actions footer */
|
||||
.rp-card-actions {
|
||||
display: flex;
|
||||
gap: 7px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
border-top: 1px solid rgba(255,255,255,0.05);
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
.rp-btn {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 13px;
|
||||
font-size: 0.78em;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s, transform 0.1s;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.rp-btn:hover { opacity: 0.85; transform: translateY(-1px); }
|
||||
.rp-btn-resolve { background: #28a745; color: #fff; }
|
||||
.rp-btn-reject { background: #495057; color: #fff; }
|
||||
.rp-btn-delete { background: #dc3545; color: #fff; }
|
||||
.rp-btn-warn { background: #ffc107; color: #000; }
|
||||
.rp-btn-ban { background: #b71c1c; color: #fff; }
|
||||
.rp-btn-unavail { background: #6f42c1; color: #fff; }
|
||||
.rp-btn-avail { background: #28a745; color: #fff; }
|
||||
.rp-btn-view { background: rgba(255,255,255,0.08); color: #fff; border: 1px solid rgba(255,255,255,0.12); }
|
||||
.rp-btn-secondary { background: #6c757d; color: #fff; }
|
||||
.rp-sep { width: 1px; height: 20px; background: rgba(255,255,255,0.08); margin: 0 2px; }
|
||||
.rp-anon-note { color: #555; font-size: 0.78em; font-style: italic; }
|
||||
|
||||
/* Pagination */
|
||||
.rp-pagination { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 24px; }
|
||||
.rp-pagination button {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
padding: 6px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.btn-modern:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255,255,255,0.2);
|
||||
.rp-pagination button:hover { background: rgba(255,255,255,0.1); }
|
||||
.rp-pagination .rp-page-info { color: #666; font-size: 0.85em; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.rp-preview { width: 120px; min-width: 120px; }
|
||||
.rp-preview img, .rp-preview video, .rp-no-preview { width: 120px; }
|
||||
}
|
||||
.btn-success { background: #28a745 !important; color: #fff !important; }
|
||||
.btn-danger { background: #dc3545 !important; color: #fff !important; }
|
||||
.btn-warning { background: #ffc107 !important; color: #000 !important; }
|
||||
.btn-secondary { background: #6c757d !important; color: #fff !important; }
|
||||
</style>
|
||||
|
||||
<div class="container mod-reports-page">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 30px; gap: 20px; flex-wrap: wrap;">
|
||||
<div>
|
||||
<h2 style="margin: 0; font-weight: 800; letter-spacing: -0.5px;">User Reports</h2>
|
||||
<p style="color: #888; margin: 5px 0 0 0;">Review and resolve content flags from the community.</p>
|
||||
</div>
|
||||
<div class="container rp-page">
|
||||
<div class="rp-header">
|
||||
<h2>User Reports</h2>
|
||||
<p>Review and resolve content flags from the community.</p>
|
||||
</div>
|
||||
<div style="margin-bottom: 25px; display: flex; gap: 10px; align-items: center;">
|
||||
<select id="report-status-filter" style="width: 180px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; color: #fff; padding: 8px 12px; outline: none; cursor: pointer;">
|
||||
<div class="rp-filter-bar">
|
||||
<select id="report-status-filter">
|
||||
<option value="pending">Pending</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
<option value="rejected">Rejected</option>
|
||||
</select>
|
||||
<button class="btn-modern" onclick="loadReports(1)">Refresh</button>
|
||||
<button onclick="loadReports(1)"><i class="fa-solid fa-rotate-right"></i> Refresh</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive" style="border: none;">
|
||||
<table class="mod-reports-table responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Reporter</th>
|
||||
<th>Target</th>
|
||||
<th>Reason</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="reports-table-body">
|
||||
<tr><td colspan="6" class="text-center">Loading reports...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="reports-feed" class="rp-feed">
|
||||
<div class="rp-state-msg">Loading reports…</div>
|
||||
</div>
|
||||
<div id="reports-pagination" style="text-align: center; margin-top: 15px;"></div>
|
||||
<div id="reports-pagination" class="rp-pagination"></div>
|
||||
</div>
|
||||
<script>
|
||||
window.currentPage = window.currentPage || 1;
|
||||
|
||||
window.loadReports = async function(page = 1) {
|
||||
window.currentPage = page;
|
||||
const status = document.getElementById('report-status-filter').value;
|
||||
const tbody = document.getElementById('reports-table-body');
|
||||
const pag = document.getElementById('reports-pagination');
|
||||
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center">Loading reports...</td></tr>';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/v2/mod/reports?status=' + status + '&page=' + page);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
window.currentReports = data.reports;
|
||||
window.emojiMap = new Map();
|
||||
if (data.emojis) {
|
||||
data.emojis.forEach(emojiObj => window.emojiMap.set(emojiObj.name.toLowerCase(), emojiObj.url));
|
||||
}
|
||||
tbody.innerHTML = '';
|
||||
if (data.reports.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center">No reports found.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
data.reports.forEach(r => {
|
||||
let targetHtml = '';
|
||||
if (r.comment_id) {
|
||||
targetHtml += 'Comment: <a href="#" onclick="window.expandItem(event, ' + r.id + ')">#' + r.comment_id + '</a>';
|
||||
} else if (r.resolved_item_id) {
|
||||
targetHtml += 'Item: <a href="#" onclick="window.expandItem(event, ' + r.id + ')">#' + r.resolved_item_id + '</a>';
|
||||
} else if (r.reported_user_name) {
|
||||
targetHtml += 'User: <a href="/user/' + r.reported_user_name + '">' + r.reported_user_name + '</a>';
|
||||
}
|
||||
|
||||
let actionHtml = '';
|
||||
if (status === 'pending') {
|
||||
actionHtml =
|
||||
'<button class="btn btn-sm btn-success" onclick="window.resolveReport(' + r.id + ', "resolved")">Resolve</button> ' +
|
||||
'<button class="btn btn-sm btn-danger" onclick="window.resolveReport(' + r.id + ', "rejected")">Reject</button>';
|
||||
}
|
||||
|
||||
let reporterHtml = '';
|
||||
if (r.reporter_name) {
|
||||
reporterHtml = '<a href="/user/' + r.reporter_name + '" style="color: var(--accent); font-weight: bold;">' + r.reporter_name + '</a>' + (r.reporter_ip ? ' <span style="font-size: 0.75rem; color: #888;">(' + r.reporter_ip + ')</span>' : '');
|
||||
} else {
|
||||
reporterHtml = '<span style="color: #aaa; font-style: italic;">Guest' + (r.reporter_ip ? ' (' + r.reporter_ip + ')' : '') + '</span>';
|
||||
}
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML =
|
||||
'<td data-label="ID">' + r.id + '</td>' +
|
||||
'<td data-label="Reporter">' + reporterHtml + '</td>' +
|
||||
'<td data-label="Target">' + targetHtml + '</td>' +
|
||||
'<td data-label="Reason"><span style="opacity: 0.8;">' + r.reason + '</span></td>' +
|
||||
'<td data-label="Date"><span style="font-size: 0.85rem; color: #888;">' + new Date(r.created_at).toLocaleString() + '</span></td>' +
|
||||
'<td data-label="Actions">' + actionHtml + '</td>';
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
// Pagination
|
||||
pag.innerHTML = '';
|
||||
if (data.pages > 1) {
|
||||
if (data.page > 1) {
|
||||
pag.innerHTML += '<button class="btn-modern" style="margin-right: 5px;" onclick="window.loadReports(' + (data.page - 1) + ')">Prev</button> ';
|
||||
}
|
||||
pag.innerHTML += '<span style="font-size: 0.85rem; color: #888; margin: 0 10px;">Page ' + data.page + ' of ' + data.pages + '</span>';
|
||||
if (data.page < data.pages) {
|
||||
pag.innerHTML += '<button class="btn-modern" style="margin-left: 5px;" onclick="window.loadReports(' + (data.page + 1) + ')">Next</button>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Error: ' + data.msg + '</td></tr>';
|
||||
}
|
||||
} catch (e) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Network Error</td></tr>';
|
||||
}
|
||||
};
|
||||
|
||||
window.resolveReport = async function(id, action) {
|
||||
if (!confirm('Mark report #' + id + ' as ' + action + '?')) return;
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
params.append('action', action);
|
||||
|
||||
const res = await fetch('/api/v2/mod/reports/' + id + '/resolve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
window.loadReports(window.currentPage);
|
||||
if (window.NotificationSystemInstance && typeof window.NotificationSystemInstance.pollDebounced === 'function') {
|
||||
window.NotificationSystemInstance.pollDebounced();
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + data.msg);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Network error');
|
||||
}
|
||||
};
|
||||
|
||||
window.expandItem = function(e, id) {
|
||||
e.preventDefault();
|
||||
const tr = e.target.closest('tr');
|
||||
|
||||
// Toggle logic: If the next row is an expanded row, remove it and return.
|
||||
if (tr.nextElementSibling && tr.nextElementSibling.classList.contains('expanded-report')) {
|
||||
tr.nextElementSibling.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Lookup the report locally
|
||||
const r = window.currentReports.find(x => x.id === id);
|
||||
if (!r) return;
|
||||
|
||||
// Checking if the moderator is also a superadmin for ban abilities
|
||||
const isAdmin = window.f0ckSession && window.f0ckSession.admin;
|
||||
|
||||
// Build the Expansion Row
|
||||
const expTr = document.createElement('tr');
|
||||
expTr.className = 'expanded-report';
|
||||
const isComment = !!r.comment_id;
|
||||
const isItem = !!r.resolved_item_id && r.resolved_item_dest;
|
||||
|
||||
let previewHtml = '';
|
||||
// Only show media preview for direct Item reports
|
||||
if (isItem && !isComment) {
|
||||
const mime = r.resolved_item_mime || '';
|
||||
const src = '/b/' + r.resolved_item_dest;
|
||||
const baseStyle = 'max-height: 250px; border: 1px solid #333; border-radius: 4px;';
|
||||
if (mime === 'video/youtube') {
|
||||
const ytId = r.resolved_item_dest.replace('yt:', '');
|
||||
previewHtml = '<div><iframe width="444" height="250" src="https://www.youtube.com/embed/' + ytId + '" frameborder="0" allowfullscreen style="' + baseStyle + '"></iframe></div>';
|
||||
} else if (mime === 'application/pdf') {
|
||||
previewHtml = '<div><iframe src="' + src + '#toolbar=0" style="' + baseStyle + ' width: 444px; height: 250px;" frameborder="0" allowfullscreen></iframe></div>';
|
||||
} else if (mime.startsWith('image/')) {
|
||||
previewHtml = '<div><img src="' + src + '" style="' + baseStyle + ' background: #000;"></div>';
|
||||
} else if (mime.startsWith('audio/')) {
|
||||
previewHtml = '<div><audio src="' + src + '" controls style="' + baseStyle + '"></audio></div>';
|
||||
} else {
|
||||
previewHtml = '<div><video src="' + src + '" controls loop style="' + baseStyle + ' background: #000;"></video></div>';
|
||||
}
|
||||
}
|
||||
if (isComment) {
|
||||
let escapedContent = (r.comment_body || '[Deleted or Empty]')
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
|
||||
// Handle Emojis
|
||||
if (window.emojiMap) {
|
||||
escapedContent = escapedContent.replace(/:([a-z0-9_]+):/g, function(match, code) {
|
||||
var url = window.emojiMap.get(code.toLowerCase());
|
||||
if (url) {
|
||||
return '<img src="' + url + '" style="height:24px;vertical-align:middle;" alt="' + code + '" title=":' + code + ':">';
|
||||
}
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
previewHtml += '<div style="background: rgba(0,0,0,0.5); padding: 15px; border: 1px solid #444; color: #eee; font-family: monospace; max-height: 250px; overflow-y: auto; white-space: pre-wrap; font-size: 0.9rem;">' +
|
||||
'<strong>Reported Comment:</strong><br><br>' + escapedContent +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
let buttonsHtml = '';
|
||||
// Delete Item and Make Unavailable buttons for direct Item reports
|
||||
if (isItem && !isComment) {
|
||||
buttonsHtml += '<button class="btn btn-danger" onclick="window.adminDeleteItem(' + r.resolved_item_id + ')">Delete Item</button>';
|
||||
const isUnav = r.resolved_item_visibility === 3;
|
||||
buttonsHtml += ' <button class="btn ' + (isUnav ? 'btn-success' : 'btn-warning') + '" onclick="window.modToggleUnavailable(' + r.resolved_item_id + ', ' + (r.resolved_item_visibility || 0) + ')">' + (isUnav ? 'Make Available' : 'Make Unavailable (451)') + '</button>';
|
||||
}
|
||||
if (isComment) {
|
||||
buttonsHtml += '<button class="btn btn-danger" onclick="window.adminDeleteComment(' + r.comment_id + ')">Delete Comment</button>';
|
||||
if (r.resolved_item_id) {
|
||||
buttonsHtml += '<a href="/' + r.resolved_item_id + '" class="btn btn-info" style="text-decoration: none; color: white;" target="_blank">View Video</a>';
|
||||
}
|
||||
}
|
||||
|
||||
// Punitive actions target the reported party
|
||||
if (r.reported_user_id) {
|
||||
// Only show punitive actions if viewer is admin OR reported user is NOT an admin
|
||||
if (isAdmin || !r.reported_user_is_admin) {
|
||||
const warnLabel = isItem ? 'Warn Uploader' : (isComment ? 'Warn Commenter' : 'Warn User');
|
||||
buttonsHtml += '<button class="btn btn-warning" onclick="window.modWarnUser(' + r.reported_user_id + ')">' + warnLabel + ' (' + r.reported_user_name + ')</button>';
|
||||
const banLabel = isItem ? 'Ban Uploader' : (isComment ? 'Ban Commenter' : 'Ban User');
|
||||
buttonsHtml += '<button class="btn btn-danger" onclick="window.adminBanUser(' + r.reported_user_id + ')">' + banLabel + ' (' + r.reported_user_name + ')</button>';
|
||||
} else {
|
||||
buttonsHtml += '<span style="color:var(--gray); font-style: italic; opacity:0.8; margin-left: 10px;">(Admin Protection Active)</span>';
|
||||
}
|
||||
} else {
|
||||
buttonsHtml += '<span style="color:var(--gray); opacity:0.6;">(Anonymous/Unknown Source)</span>';
|
||||
}
|
||||
|
||||
expTr.innerHTML =
|
||||
'<td colspan="6" style="background: rgba(255,255,255,0.01); border-left: 4px solid var(--accent); padding: 30px; box-shadow: inset 0 0 20px rgba(0,0,0,0.4);">' +
|
||||
'<div style="display: flex; gap: 40px; align-items: center;">' +
|
||||
previewHtml +
|
||||
'<div style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 15px;">' +
|
||||
'<div style="font-weight: bold; opacity: 1; text-transform: uppercase; font-size: 0.8rem; letter-spacing: 1.5px; margin-bottom: 5px;">Moderation Action:</div>' +
|
||||
'<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center;">' +
|
||||
buttonsHtml +
|
||||
(r.reporter_id && r.reporter_name ? '<button class="btn btn-secondary" onclick="window.modWarnUser(' + r.reporter_id + ')">Warn Reporter (' + r.reporter_name + ')</button>' : (r.reporter_ip ? '<span style="color: #888; font-size: 0.85rem;">Reporter: Guest (' + r.reporter_ip + ')</span>' : '')) +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</td>';
|
||||
|
||||
tr.insertAdjacentElement('afterend', expTr);
|
||||
};
|
||||
|
||||
window.adminDeleteComment = function(id) {
|
||||
window.ModAction.confirm('Delete Comment #' + id, 'Are you sure you want to delete this comment? This action is permanent.', async (reason) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('reason', reason);
|
||||
const res = await fetch('/api/comments/' + id + '/delete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (window.showFlash) window.showFlash('comment deleted', 'success');
|
||||
} else {
|
||||
throw new Error(data.msg || 'Unknown error');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.adminDeleteItem = function(id) {
|
||||
window.ModAction.confirm('Delete Item #' + id, 'Are you sure you want to delete this item? This action is permanent.', async (reason) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('postid', id);
|
||||
params.append('reason', reason);
|
||||
const res = await fetch('/api/v2/admin/deletepost', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (window.showFlash) window.showFlash('item deleted', 'success');
|
||||
} else {
|
||||
throw new Error(data.msg || 'Unknown error');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.modToggleUnavailable = function(id, currentVis) {
|
||||
const willBeUnavailable = currentVis !== 3;
|
||||
const targetVis = willBeUnavailable ? 3 : 0;
|
||||
const actionText = willBeUnavailable ? 'Make Unavailable (serves HTTP 451 to non-logged in visitors)' : 'Make Available (Public)';
|
||||
|
||||
window.ModAction.confirm('Item Visibility', actionText + ' for item #' + id + '?', async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('postid', id);
|
||||
params.append('id', id);
|
||||
params.append('visibility', targetVis);
|
||||
const res = await fetch('/api/v2/item/visibility', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-Token': window.f0ckSession?.csrf_token
|
||||
},
|
||||
body: params
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (window.showFlash) window.showFlash(willBeUnavailable ? 'Item marked unavailable (451)' : 'Item restored to public', 'success');
|
||||
const item = window.currentReports.find(x => x.resolved_item_id === id);
|
||||
if (item) item.resolved_item_visibility = targetVis;
|
||||
window.loadReports(window.currentPage);
|
||||
} else {
|
||||
throw new Error(data.msg || 'Failed to update visibility');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.modWarnUser = function(userId) {
|
||||
window.ModAction.confirm('Warn User ID ' + userId, '', async (reason) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('user_id', userId);
|
||||
params.append('reason', reason);
|
||||
const res = await fetch('/api/v2/mod/warnings/issue', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (window.showFlash) window.showFlash('user has been warned', 'success');
|
||||
} else {
|
||||
throw new Error(data.msg || 'Unknown error');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.adminBanUser = function(userId) {
|
||||
const isAdmin = window.f0ckSession && window.f0ckSession.admin;
|
||||
const promptHtml =
|
||||
'<p>This will restrict the user from accessing their account and performing most actions.</p>' +
|
||||
'<div style="margin-top:10px;">' +
|
||||
'<label>Ban Duration:</label>' +
|
||||
'<select id="ban-duration-select" class="form-control" style="margin-top:5px;">' +
|
||||
(isAdmin ? '<option value="permanent">Permanent</option>' : '') +
|
||||
'<option value="1">1 Hour</option>' +
|
||||
'<option value="6">6 Hours</option>' +
|
||||
'<option value="24">24 Hours (1 Day)</option>' +
|
||||
(!isAdmin ? '<option value="48">48 Hours (2 Days)</option>' : '') +
|
||||
(isAdmin ? '<option value="168">168 Hours (1 Week)</option>' : '') +
|
||||
(isAdmin ? '<option value="720">720 Hours (1 Month)</option>' : '') +
|
||||
'</select>' +
|
||||
'</div>';
|
||||
|
||||
window.ModAction.confirm('Ban User ID ' + userId, promptHtml, async (reason) => {
|
||||
const duration = document.getElementById('ban-duration-select').value;
|
||||
const params = new URLSearchParams();
|
||||
params.append('user_id', userId);
|
||||
params.append('reason', reason);
|
||||
params.append('duration', duration);
|
||||
|
||||
const res = await fetch('/api/v2/admin/ban', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (window.showFlash) window.showFlash('User banned cleanly.', 'success');
|
||||
} else {
|
||||
throw new Error(data.msg || 'Unknown error');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
(function() {
|
||||
const filter = document.getElementById('report-status-filter');
|
||||
if (filter) {
|
||||
// Prevent stacking, although safe here
|
||||
filter.onchange = () => window.loadReports(1);
|
||||
}
|
||||
window.loadReports(1);
|
||||
})();
|
||||
</script>
|
||||
<script src="/s/js/mod-reports.js?v=1789785562"></script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user