198 lines
4.6 KiB
Svelte
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>
|