diff --git a/src/App.svelte b/src/App.svelte
index 0323c80..69a359a 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -19,6 +19,7 @@
let activityEvents: Array<{ event_id: string; event_type: string; occurred_at: string }> = [];
let chatMessages: Array<{ event_id: string; message_id: string; user_id: string; user_name: string; text: string; fragments: Array<{ type: string; text: string; emote_id?: string }>; sent_at: string; reply_parent_message_id?: string }> = [];
let chatReplyParent: { message_id: string; user_name: string } | undefined;
+ let pinnedMessageIds: string[] = [];
let notifications: Array<{ id: string; severity: string; title: string; message: string; seen_at: string | null; created_at: string }> = [];
let showNotifications = false;
let showStreamEditor = false;
@@ -150,6 +151,7 @@
diagnostics = undefined;
chatMessages = [];
chatReplyParent = undefined;
+ pinnedMessageIds = [];
}
function connectRealtime() {
@@ -350,6 +352,17 @@
showModeration = true;
}
+ function togglePinnedMessage(messageId: string) {
+ pinnedMessageIds = pinnedMessageIds.includes(messageId) ? pinnedMessageIds.filter((id) => id !== messageId) : [messageId, ...pinnedMessageIds];
+ }
+
+ function deleteChatMessage(message: { message_id: string; user_id: string }) {
+ moderationAction = 'delete_message';
+ moderationUserId = message.user_id;
+ moderationMessageId = message.message_id;
+ showModeration = true;
+ }
+
async function applyModeration() {
moderationBusy = true;
moderationError = '';
@@ -497,7 +510,7 @@
{#if playerSrc}{:else}STREAM OFFLINE
CONNECT TWITCH TO BEGIN
{/if}
{#if activityEvents.length}{#each activityEvents as event}
{event.event_type}{new Date(event.occurred_at).toLocaleTimeString()}
{/each}
{:else}◌
Nothing here yetFollows, subscriptions, raids, and server events will appear here. {/if}
-
{#if chatMessages.length}{#each chatMessages as message}
{#if message.fragments.length}{#each message.fragments as fragment}{#if fragment.type === 'emote' && fragment.emote_id}
{:else}{fragment.text}{/if}{/each}{:else}{message.text}{/if} {/each}
{:else}⌁
Chat is waitingConnect a streamer or bot account to begin. {/if}{#if chatReplyParent}
REPLYING TO @{chatReplyParent.user_name}
{/if}
{#if chatEmoteOptions.length}
{#each chatEmoteOptions as emote}
{/each}
{/if}
searchChatEmotes(chatMessage)} onkeydown={(event) => event.key === 'Enter' && sendChatMessage()} />
{#if chatError}
{chatError}
{/if}
+
{#if pinnedMessageIds.length}PINNED{#each chatMessages.filter((message) => pinnedMessageIds.includes(message.message_id)) as message}
{message.user_name}{message.text}
{/each}
{/if}{#if chatMessages.length}{#each chatMessages as message}
{#if message.fragments.length}{#each message.fragments as fragment}{#if fragment.type === 'emote' && fragment.emote_id}
{:else}{fragment.text}{/if}{/each}{:else}{message.text}{/if} {/each}
{:else}⌁
Chat is waitingConnect a streamer or bot account to begin. {/if}{#if chatReplyParent}
REPLYING TO @{chatReplyParent.user_name}
{/if}
{#if chatEmoteOptions.length}
{#each chatEmoteOptions as emote}
{/each}
{/if}
searchChatEmotes(chatMessage)} onkeydown={(event) => event.key === 'Enter' && sendChatMessage()} />
{#if chatError}
{chatError}
{/if}
{#if showStreamEditor}{/if}{#if showModeration}{/if}
{/if}
diff --git a/src/styles.css b/src/styles.css
index 0a75bb6..6e27bac 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -278,6 +278,37 @@ button:hover:not(:disabled) {
color: var(--ed-dim);
font: 9px var(--font-data);
}
+.chat-actions {
+ display: flex;
+ margin-left: auto;
+ gap: 3px;
+ opacity: 0;
+}
+.chat-message:hover .chat-actions,
+.chat-message:focus-within .chat-actions {
+ opacity: 1;
+}
+.chat-reply.danger {
+ color: var(--ed-danger);
+}
+.message-pinned {
+ background: var(--ui-surface);
+}
+.pinned-messages {
+ padding: 7px 10px;
+ border-bottom: 1px solid var(--ui-divider);
+ background: var(--ui-surface);
+}
+.pinned-message {
+ display: flex;
+ gap: 7px;
+ margin-top: 5px;
+ overflow: hidden;
+ color: var(--ed-fg);
+ font-size: 11px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
.chat-reply-context {
display: flex;
align-items: center;