feat(frontend): my message menu shows when it was read or listened, group readers open in a right panel

This commit is contained in:
hh committed 2026-10-19 02:40:49 +00:00
1 parent f03d7c2890
commit bb38e23f29
14 files changed
+543 -10

No files matched your search

@@ -0,0 +1,124 @@
<script lang="ts">
import { untrack } from "svelte";
import Avatar from "#lib/components/ui/Avatar.svelte";
import EmptyState from "#lib/components/ui/EmptyState.svelte";
import Spinner from "#lib/components/ui/Spinner.svelte";
import { peerColorIndex, peerName } from "#lib/format/peer.js";
import { readWhen } from "#lib/format/read.js";
import { t } from "#lib/i18n/locale.svelte.js";
import { reads } from "#lib/stores/reads.svelte.js";
import { ui } from "#lib/stores/ui.svelte.js";
import { goto } from "$app/navigation";
import { page } from "$app/state";
const chatId = $derived(
page.params.chatId ? Number(page.params.chatId) : null
);
const messageId = $derived(ui.panelMessageId);
const entry = $derived(
chatId === null || messageId === null
? undefined
: reads.get(chatId, messageId)
);
const readers = $derived(entry?.info.readers ?? []);
$effect(() => {
const cid = chatId;
const mid = messageId;
if (cid !== null && mid !== null) {
untrack(() => reads.open(cid, mid));
}
});
function openProfile(peerId: number) {
goto(`/app/${peerId}`);
ui.openPanel("profile");
}
</script>
{#if entry === undefined}
<div class="center"><Spinner /></div>
{:else if readers.length === 0}
<EmptyState title={t("read.readers.empty")} />
{:else}
<ul class="readers">
{#each readers as reader (reader.peer_id)}
{@const name = peerName(reader.peer) || String(reader.peer_id)}
<li>
<button onclick={() => openProfile(reader.peer_id)} type="button">
<Avatar
avatar={{ id: reader.peer_id, kind: "peer" }}
colorKey={peerColorIndex(reader.peer_id)}
hasAvatar={reader.peer?.has_avatar ?? false}
{name}
size={2.625}
/>
<span class="info">
<span class="name">{name}</span>
<span class="time">{readWhen(reader.read_at)}</span>
</span>
</button>
</li>
{/each}
</ul>
{/if}
<style lang="scss">
.center {
display: flex;
justify-content: center;
padding: 2rem 0;
}
.readers {
display: flex;
flex-direction: column;
margin: 0;
padding: 0.5rem;
list-style: none;
}
button {
cursor: pointer;
display: flex;
align-items: center;
gap: 0.75rem;
width: 100%;
padding: 0.4375rem 0.75rem;
border: 0;
border-radius: var(--border-radius-default-small);
font: inherit;
color: inherit;
text-align: start;
background: transparent;
&:hover,
&:focus-visible {
background-color: var(--color-chat-hover);
}
}
.info {
display: flex;
flex-direction: column;
min-width: 0;
}
.name {
overflow: hidden;
font-weight: var(--font-weight-medium);
color: var(--color-text);
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
</style>