feat: remove image handling and update related UI elements for streamlined moderation experience

This commit is contained in:
MythEclipse
2026-05-13 21:28:45 +07:00
parent 95cb8b837a
commit 251a176b2b

View File

@@ -397,30 +397,6 @@
.badge.edit { color: var(--yellow); border-color: rgba(255,228,94,0.36); }
.badge.delete { color: var(--red); border-color: rgba(255,79,109,0.42); }
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 14px;
}
.image-card {
overflow: hidden;
border: 1px solid var(--line);
border-radius: 22px;
background: rgba(255,255,255,0.035);
}
.image-preview {
height: 180px;
display: grid;
place-items: center;
background: #05070b;
color: var(--faint);
font: 700 12px/1 "JetBrains Mono", monospace;
}
.image-preview img { width: 100%; height: 100%; object-fit: cover; }
.image-meta { padding: 14px; display: grid; gap: 8px; }
.filename { font-size: 13px; font-weight: 900; word-break: break-word; }
.link { color: var(--cyan); text-decoration: none; font-weight: 900; }
.link:hover { text-decoration: underline; }
@@ -456,8 +432,8 @@
<section class="hero">
<div class="brand-card">
<div class="eyebrow"><span class="pulse"></span> Discord moderation command center</div>
<h1>Voice. Text. Image. One Watch Floor.</h1>
<p class="subtitle">Single-page watcher for live voice bridge, captured messages, and uploaded image evidence. Built for dense moderation review without jumping between pages.</p>
<h1>Voice. Text. One Watch Floor.</h1>
<p class="subtitle">Single-page watcher for live voice bridge and captured Discord messages, including stickers, embeds, replies, and uploaded image evidence inline.</p>
</div>
<div class="status-card">
<div class="status-row"><span class="status-label">WebSocket</span><span class="status-value"><span id="wsDot" class="dot"></span><span id="wsStatusText">Connecting</span></span></div>
@@ -470,7 +446,6 @@
<div class="tabs">
<button class="tab-btn active" data-tab="voice">Voice</button>
<button class="tab-btn" data-tab="text">Text</button>
<button class="tab-btn" data-tab="images">Images</button>
</div>
<div class="filter-row">
<span>Channel / Thread</span>
@@ -521,13 +496,6 @@
<div id="textList" class="feed"></div>
</div>
</section>
<section id="images" class="tab-content">
<div class="content-card">
<div class="card-title"><h2>Image Watch</h2><span class="mini">picser raw commit</span></div>
<div id="imageGrid" class="image-grid"></div>
</div>
</section>
</main>
<script>
@@ -536,7 +504,6 @@
activeTab: 'voice',
selectedChannel: '',
text: [],
images: [],
isStreaming: false,
isListening: false,
audioContextTransmit: null,
@@ -568,7 +535,6 @@
visualizer: document.getElementById('visualizer'),
userList: document.getElementById('userList'),
textList: document.getElementById('textList'),
imageGrid: document.getElementById('imageGrid'),
};
for (let i = 0; i < 32; i++) {
@@ -714,7 +680,7 @@
if (item) Object.assign(item, { deleted_at: message.data.deleted_at, type: 'deleted' });
renderText();
}
if (message.type === 'attachment_uploaded') fetchImages();
if (message.type === 'attachment_uploaded') fetchText();
}
function renderUsers(users) {
@@ -746,13 +712,6 @@
renderText();
}
async function fetchImages() {
if (!state.selectedChannel) return renderImages();
const result = await apiRequest(`/api/messages?channel=${encodeURIComponent(state.selectedChannel)}&type=image&limit=80`);
state.images = result.data || [];
renderImages();
}
function parseMetadata(value) {
if (!value) return {};
try { return JSON.parse(value); } catch { return {}; }
@@ -876,46 +835,6 @@
return wrap;
}
function renderImages() {
el.imageGrid.replaceChildren();
if (!state.selectedChannel) return appendEmpty(el.imageGrid, 'Select channel to view image captures');
if (state.images.length === 0) return appendEmpty(el.imageGrid, 'No image captures yet');
for (const image of state.images) {
const card = document.createElement('article');
card.className = 'image-card';
const preview = document.createElement('div');
preview.className = 'image-preview';
if (image.uploaded_url) {
const img = document.createElement('img');
img.src = image.uploaded_url;
img.alt = image.filename;
preview.appendChild(img);
} else {
preview.textContent = image.upload_status || 'pending';
}
const meta = document.createElement('div');
meta.className = 'image-meta';
const filename = document.createElement('div');
filename.className = 'filename';
filename.textContent = image.filename;
const info = document.createElement('div');
info.className = 'time';
info.textContent = `${(image.size / 1024).toFixed(1)}KB • ${image.upload_status}${image.thread_id ? ' • thread' : ''}`;
meta.append(filename, info);
if (image.uploaded_url) {
const link = document.createElement('a');
link.className = 'link';
link.href = image.uploaded_url;
link.target = '_blank';
link.rel = 'noreferrer';
link.textContent = 'open raw commit';
meta.appendChild(link);
}
card.append(preview, meta);
el.imageGrid.appendChild(card);
}
}
function appendBadge(parent, label, className) {
const badge = document.createElement('span');
badge.className = `badge ${className}`;
@@ -1015,7 +934,6 @@
document.getElementById(state.activeTab).classList.add('active');
el.activeTabLabel.textContent = button.textContent;
if (state.activeTab === 'text') await fetchText();
if (state.activeTab === 'images') await fetchImages();
});
});
@@ -1026,14 +944,13 @@
el.listenBtn.addEventListener('click', toggleListen);
el.channelFilter.addEventListener('change', async () => {
state.selectedChannel = el.channelFilter.value;
await Promise.all([fetchText(), fetchImages()]).catch((error) => showError(error.message));
await fetchText().catch((error) => showError(error.message));
});
connectWebSocket();
loadGuilds().then(refreshStatus).catch((error) => showError(error.message));
setInterval(() => {
if (state.activeTab === 'text') fetchText().catch(() => {});
if (state.activeTab === 'images') fetchImages().catch(() => {});
}, 7000);
</script>
</body>