diff --git a/src/App.svelte b/src/App.svelte index 071eabc..d3c72ce 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -23,6 +23,7 @@ let activityFilter: 'all' | 'moderation' = 'all'; 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 chatThreadRoot: { message_id: string; user_name: string; text: 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; @@ -226,6 +227,7 @@ collaborationInvites = []; showCollaboration = false; chatReplyParent = undefined; + chatThreadRoot = undefined; pinnedMessageIds = []; } @@ -243,12 +245,12 @@ const event: { event_type?: string } = JSON.parse(message.data); if (event.event_type === 'connection.ready') realtimeState = 'CONNECTED'; else if ('event_id' in event && event.event_type) { - const activity = event as { event_id: string; event_type: string; occurred_at: string; payload?: { message_id?: string; chatter_user_id?: string; chatter_user_name?: string; message?: { text?: string; fragments?: Array<{ type: string; text: string; emote?: { id?: string } }> } } }; + const activity = event as { event_id: string; event_type: string; occurred_at: string; payload?: { message_id?: string; chatter_user_id?: string; chatter_user_name?: string; message?: { text?: string; fragments?: Array<{ type: string; text: string; emote?: { id?: string } }> }; reply?: { parent_message_id?: string } } }; if (!activityEvents.some((item) => item.event_id === activity.event_id)) { activityEvents = [activity, ...activityEvents].slice(0, 100); } if (activity.event_type === 'twitch.channel.chat.message' && activity.payload?.message?.text) { - chatMessages = [{ event_id: activity.event_id, message_id: activity.payload.message_id ?? activity.event_id, user_id: activity.payload.chatter_user_id ?? '', user_name: activity.payload.chatter_user_name ?? 'unknown', text: activity.payload.message.text, fragments: (activity.payload.message.fragments ?? []).map((fragment) => ({ type: fragment.type, text: fragment.text, emote_id: fragment.emote?.id })), sent_at: activity.occurred_at }, ...chatMessages].slice(0, 100); + chatMessages = [{ event_id: activity.event_id, message_id: activity.payload.message_id ?? activity.event_id, user_id: activity.payload.chatter_user_id ?? '', user_name: activity.payload.chatter_user_name ?? 'unknown', text: activity.payload.message.text, fragments: (activity.payload.message.fragments ?? []).map((fragment) => ({ type: fragment.type, text: fragment.text, emote_id: fragment.emote?.id })), sent_at: activity.occurred_at, reply_parent_message_id: activity.payload.reply?.parent_message_id }, ...chatMessages].slice(0, 100); } } } catch { @@ -551,6 +553,10 @@ chatReplyParent = { message_id: message.message_id, user_name: message.user_name }; } + function openChatThread(message: { message_id: string; user_name: string; text: string }) { + chatThreadRoot = { message_id: message.message_id, user_name: message.user_name, text: message.text }; + } + function moderateChatUser(userId: string) { if (!userId) return; moderationUserId = userId; @@ -911,5 +917,26 @@ {/if} + {#if chatMessages.some((message) => message.reply_parent_message_id)} +
+
CHAT THREADS
+ {#each chatMessages.filter((message) => message.reply_parent_message_id) as reply} + {@const root = chatMessages.find((message) => message.message_id === reply.reply_parent_message_id)} + {#if root}{/if} + {/each} +
+ {/if} + {#if chatThreadRoot} +
+
THREAD ยท @{chatThreadRoot.user_name}
+
{chatThreadRoot.user_name}{chatThreadRoot.text}
+
+ {#each chatMessages.filter((message) => message.reply_parent_message_id === chatThreadRoot?.message_id) as reply} +
{reply.user_name}{reply.text}
+ {:else}
No replies received yet.
{/each} +
+ +
+ {/if} diff --git a/src/styles.css b/src/styles.css index e03a119..8709992 100644 --- a/src/styles.css +++ b/src/styles.css @@ -901,3 +901,33 @@ label { min-width: 28px; padding: 2px 6px; } +.chat-thread-panel, +.thread-index-panel { + margin-top: 10px; +} +.thread-index-row, +.thread-reply { + display: flex; + justify-content: space-between; + gap: 12px; + width: 100%; + padding: 8px; + border: 0; + border-bottom: 1px solid var(--ui-divider); + background: transparent; + color: var(--ed-fg); + text-align: left; +} +.thread-index-row:hover { + background: var(--ui-surface-hi); +} +.thread-root { + display: flex; + gap: 8px; + padding: 10px; + border-bottom: 1px solid var(--ui-line-focus); +} +.thread-replies { + margin: 6px 0 10px 18px; + border-left: 2px solid var(--ui-line-focus); +}