feat: remove image handling and update related UI elements for streamlined moderation experience
This commit is contained in:
@@ -397,30 +397,6 @@
|
|||||||
.badge.edit { color: var(--yellow); border-color: rgba(255,228,94,0.36); }
|
.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); }
|
.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; }
|
.filename { font-size: 13px; font-weight: 900; word-break: break-word; }
|
||||||
.link { color: var(--cyan); text-decoration: none; font-weight: 900; }
|
.link { color: var(--cyan); text-decoration: none; font-weight: 900; }
|
||||||
.link:hover { text-decoration: underline; }
|
.link:hover { text-decoration: underline; }
|
||||||
@@ -456,8 +432,8 @@
|
|||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="brand-card">
|
<div class="brand-card">
|
||||||
<div class="eyebrow"><span class="pulse"></span> Discord moderation command center</div>
|
<div class="eyebrow"><span class="pulse"></span> Discord moderation command center</div>
|
||||||
<h1>Voice. Text. Image. One Watch Floor.</h1>
|
<h1>Voice. Text. 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>
|
<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>
|
||||||
<div class="status-card">
|
<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>
|
<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">
|
<div class="tabs">
|
||||||
<button class="tab-btn active" data-tab="voice">Voice</button>
|
<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="text">Text</button>
|
||||||
<button class="tab-btn" data-tab="images">Images</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-row">
|
<div class="filter-row">
|
||||||
<span>Channel / Thread</span>
|
<span>Channel / Thread</span>
|
||||||
@@ -521,13 +496,6 @@
|
|||||||
<div id="textList" class="feed"></div>
|
<div id="textList" class="feed"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -536,7 +504,6 @@
|
|||||||
activeTab: 'voice',
|
activeTab: 'voice',
|
||||||
selectedChannel: '',
|
selectedChannel: '',
|
||||||
text: [],
|
text: [],
|
||||||
images: [],
|
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
isListening: false,
|
isListening: false,
|
||||||
audioContextTransmit: null,
|
audioContextTransmit: null,
|
||||||
@@ -568,7 +535,6 @@
|
|||||||
visualizer: document.getElementById('visualizer'),
|
visualizer: document.getElementById('visualizer'),
|
||||||
userList: document.getElementById('userList'),
|
userList: document.getElementById('userList'),
|
||||||
textList: document.getElementById('textList'),
|
textList: document.getElementById('textList'),
|
||||||
imageGrid: document.getElementById('imageGrid'),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let i = 0; i < 32; i++) {
|
for (let i = 0; i < 32; i++) {
|
||||||
@@ -714,7 +680,7 @@
|
|||||||
if (item) Object.assign(item, { deleted_at: message.data.deleted_at, type: 'deleted' });
|
if (item) Object.assign(item, { deleted_at: message.data.deleted_at, type: 'deleted' });
|
||||||
renderText();
|
renderText();
|
||||||
}
|
}
|
||||||
if (message.type === 'attachment_uploaded') fetchImages();
|
if (message.type === 'attachment_uploaded') fetchText();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderUsers(users) {
|
function renderUsers(users) {
|
||||||
@@ -746,13 +712,6 @@
|
|||||||
renderText();
|
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) {
|
function parseMetadata(value) {
|
||||||
if (!value) return {};
|
if (!value) return {};
|
||||||
try { return JSON.parse(value); } catch { return {}; }
|
try { return JSON.parse(value); } catch { return {}; }
|
||||||
@@ -876,46 +835,6 @@
|
|||||||
return wrap;
|
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) {
|
function appendBadge(parent, label, className) {
|
||||||
const badge = document.createElement('span');
|
const badge = document.createElement('span');
|
||||||
badge.className = `badge ${className}`;
|
badge.className = `badge ${className}`;
|
||||||
@@ -1015,7 +934,6 @@
|
|||||||
document.getElementById(state.activeTab).classList.add('active');
|
document.getElementById(state.activeTab).classList.add('active');
|
||||||
el.activeTabLabel.textContent = button.textContent;
|
el.activeTabLabel.textContent = button.textContent;
|
||||||
if (state.activeTab === 'text') await fetchText();
|
if (state.activeTab === 'text') await fetchText();
|
||||||
if (state.activeTab === 'images') await fetchImages();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1026,14 +944,13 @@
|
|||||||
el.listenBtn.addEventListener('click', toggleListen);
|
el.listenBtn.addEventListener('click', toggleListen);
|
||||||
el.channelFilter.addEventListener('change', async () => {
|
el.channelFilter.addEventListener('change', async () => {
|
||||||
state.selectedChannel = el.channelFilter.value;
|
state.selectedChannel = el.channelFilter.value;
|
||||||
await Promise.all([fetchText(), fetchImages()]).catch((error) => showError(error.message));
|
await fetchText().catch((error) => showError(error.message));
|
||||||
});
|
});
|
||||||
|
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
loadGuilds().then(refreshStatus).catch((error) => showError(error.message));
|
loadGuilds().then(refreshStatus).catch((error) => showError(error.message));
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (state.activeTab === 'text') fetchText().catch(() => {});
|
if (state.activeTab === 'text') fetchText().catch(() => {});
|
||||||
if (state.activeTab === 'images') fetchImages().catch(() => {});
|
|
||||||
}, 7000);
|
}, 7000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user