feat: 1-to-1 message render + web data-lake backend

This commit is contained in:
h
2026-05-31 01:27:40 +02:00
parent f0afb7ec5b
commit 75425d1bee
110 changed files with 10199 additions and 54 deletions
@@ -0,0 +1,163 @@
<script lang="ts">
import { ripple } from "$lib/actions/ripple";
import type { Chat } from "$lib/api/types";
import Avatar from "$lib/components/ui/Avatar.svelte";
import { formatListDate } from "$lib/format/datetime";
import { accounts } from "$lib/stores/accounts.svelte";
import { peers } from "$lib/stores/peers.svelte";
interface Props {
chat: Chat;
onclick: () => void;
selected: boolean;
}
let { chat, selected, onclick }: Props = $props();
const title = $derived(chat.title ?? `Chat ${chat.chat_id}`);
const avatarKind = $derived(chat.chat_id > 0 ? "peer" : "chat");
const ownId = $derived(accounts.selected?.tg_user_id ?? null);
const showSender = $derived(
chat.kind === "group" && chat.last_sender_id !== null
);
$effect(() => {
if (showSender && chat.last_sender_id !== ownId) {
peers.ensure([chat.last_sender_id as number]);
}
});
const senderPrefix = $derived.by(() => {
if (!showSender) {
return "";
}
if (chat.last_sender_id === ownId) {
return "You: ";
}
const peer = peers.get(chat.last_sender_id as number);
if (!peer) {
return "";
}
return `${peer.first_name ?? peer.username ?? peer.peer_id}: `;
});
const preview = $derived(
chat.last_text ?? (chat.message_count > 0 ? "Media" : "")
);
</script>
<button
type="button"
class="Chat ListItem-button"
class:selected
use:ripple
{onclick}
>
<Avatar
name={title}
colorKey={chat.chat_id}
avatar={{ kind: avatarKind, id: chat.chat_id }}
hasAvatar={chat.has_avatar}
/>
<div class="info">
<div class="info-row">
<h3 class="title">{title}</h3>
<span class="date">{formatListDate(chat.last_date)}</span>
</div>
<div class="subtitle">
<span class="last-message">
{#if senderPrefix}
<span class="sender">{senderPrefix}</span>
{/if}
{preview}
</span>
</div>
</div>
</button>
<style lang="scss">
.Chat {
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
gap: 0.625rem;
width: 100%;
padding: 0.5625rem 0.5rem;
border: 0;
border-radius: 0.625rem;
text-align: start;
color: var(--color-text);
background-color: transparent;
transition: background-color 0.15s ease;
--ripple-color: var(--color-interactive-element-hover);
@media (hover: hover) {
&:hover {
background-color: var(--color-chat-hover);
}
}
&.selected {
background-color: var(--color-chat-active);
.title,
.date,
.last-message,
.sender {
color: var(--color-white);
}
}
}
.info {
overflow: hidden;
flex: 1;
min-width: 0;
}
.info-row {
display: flex;
align-items: baseline;
gap: 0.5rem;
}
.title {
overflow: hidden;
flex: 1;
margin: 0;
font-size: 1rem;
font-weight: var(--font-weight-medium);
text-overflow: ellipsis;
white-space: nowrap;
}
.date {
flex-shrink: 0;
font-size: 0.75rem;
color: var(--color-text-secondary);
}
.subtitle {
margin-top: 0.125rem;
}
.last-message {
overflow: hidden;
display: block;
font-size: 0.875rem;
color: var(--color-text-secondary);
text-overflow: ellipsis;
white-space: nowrap;
}
.sender {
color: var(--color-text);
}
</style>