Files
beavergram/frontend/src/lib/components/ChatListItem.svelte
T

198 lines
4.6 KiB
Svelte

<script lang="ts">
import { goto } from "$app/navigation";
import { ripple } from "$lib/actions/ripple";
import type { Chat } from "$lib/api/types";
import Avatar from "$lib/components/ui/Avatar.svelte";
import ContextMenu from "$lib/components/ui/ContextMenu.svelte";
import ContextMenuItem from "$lib/components/ui/ContextMenuItem.svelte";
import { formatListDate } from "$lib/format/datetime";
import { accounts } from "$lib/stores/accounts.svelte";
import { peers } from "$lib/stores/peers.svelte";
import { type RightPanel, ui } from "$lib/stores/ui.svelte";
interface Props {
chat: Chat;
onclick: () => void;
selected: boolean;
}
let { chat, selected, onclick }: Props = $props();
async function openWith(panel: RightPanel) {
await goto(`/app/${chat.chat_id}`);
ui.openPanel(panel);
}
const title = $derived(
chat.title ??
(chat.chat_id > 0 ? "Удалённый аккаунт" : `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>
<ContextMenu>
{#snippet children({ props })}
<button
{...props}
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>
{/snippet}
{#snippet menu()}
<ContextMenuItem icon="open-in-new-tab" onselect={onclick}
>Открыть</ContextMenuItem
>
<ContextMenuItem icon="info" onselect={() => openWith("profile")}
>Профиль</ContextMenuItem
>
<ContextMenuItem icon="search" onselect={() => openWith("search")}
>Поиск в чате</ContextMenuItem
>
<ContextMenuItem icon="stats" onselect={() => openWith("presence")}
>Аналитика</ContextMenuItem
>
<ContextMenuItem icon="play-story" onselect={() => openWith("stories")}
>Сторис</ContextMenuItem
>
{/snippet}
</ContextMenu>
<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>