feat(frontend): online dots on private chats, deleted last message struck through, back from a chat goes through history
This commit is contained in:
1 parent
9c556ff8b9
commit
4a32e32114
4 files changed
+113
-3
No files matched your search
@@ -29,8 +29,11 @@
|
||||
const isDm = $derived(chatId > 0);
|
||||
const chat = $derived(chats.byId(chatId));
|
||||
const discovered = $derived(discover.get(chatId));
|
||||
const DONE_DURATION = 2500;
|
||||
|
||||
let peer = $state<PeerView | null>(null);
|
||||
let backfilling = $state(false);
|
||||
let backfilled = $state(false);
|
||||
|
||||
async function backfill() {
|
||||
if (backfilling) {
|
||||
@@ -39,7 +42,10 @@
|
||||
backfilling = true;
|
||||
try {
|
||||
await enqueueBackfill(chatId, true);
|
||||
toasts.success(t("chat.backfill.started"));
|
||||
backfilled = true;
|
||||
setTimeout(() => {
|
||||
backfilled = false;
|
||||
}, DONE_DURATION);
|
||||
} catch {
|
||||
toasts.error(t("chat.backfill.failed"));
|
||||
} finally {
|
||||
@@ -48,6 +54,10 @@
|
||||
}
|
||||
|
||||
function back() {
|
||||
if (page.state.inApp) {
|
||||
history.back();
|
||||
return;
|
||||
}
|
||||
const target = new URL(page.url.href);
|
||||
target.pathname = "/app";
|
||||
goto(withoutMessage(target));
|
||||
@@ -216,14 +226,15 @@
|
||||
<Icon name="stats" />
|
||||
</Button>
|
||||
<Button
|
||||
aria-label={t("chat.backfill")}
|
||||
aria-label={backfilled ? t("chat.backfill.started") : t("chat.backfill")}
|
||||
loading={backfilling}
|
||||
onclick={backfill}
|
||||
round
|
||||
smaller
|
||||
title={backfilled ? t("chat.backfill.started") : t("chat.backfill")}
|
||||
variant="translucent"
|
||||
>
|
||||
<Icon name="cloud-download" />
|
||||
<Icon name={backfilled ? "check" : "cloud-download"} />
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -287,8 +298,13 @@
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-secondary);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.online {
|
||||
color: var(--color-primary);
|
||||
|
||||
@@ -76,6 +76,22 @@
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const id = activeChatId;
|
||||
untrack(() => {
|
||||
const index = list.findIndex((chat) => chat.chat_id === id);
|
||||
if (index === -1 || viewport === null) {
|
||||
return;
|
||||
}
|
||||
const top = index * rowHeight;
|
||||
if (top < viewport.scrollTop) {
|
||||
viewport.scrollTop = top;
|
||||
} else if (top + rowHeight > viewport.scrollTop + viewport.clientHeight) {
|
||||
viewport.scrollTop = top + rowHeight - viewport.clientHeight;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function measure(el: HTMLElement) {
|
||||
({ scrollTop } = el);
|
||||
if (el.scrollTop + el.clientHeight >= el.scrollHeight - SCROLL_THRESHOLD) {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
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 Icon from "#lib/components/ui/Icon.svelte";
|
||||
import { formatListDate } from "#lib/format/datetime.js";
|
||||
import { t } from "#lib/i18n/locale.svelte.js";
|
||||
import { accounts } from "#lib/stores/accounts.svelte.js";
|
||||
import { online } from "#lib/stores/online.svelte.js";
|
||||
import { peers } from "#lib/stores/peers.svelte.js";
|
||||
import { policyUi } from "#lib/stores/policy.svelte.js";
|
||||
import { type RightPanel, ui } from "#lib/stores/ui.svelte.js";
|
||||
@@ -31,6 +33,14 @@
|
||||
? t("chat.deletedAccount")
|
||||
: t("chat.fallbackTitle", { id: chat.chat_id }))
|
||||
);
|
||||
const person = $derived(chat.chat_id > 0 && !chat.is_bot);
|
||||
const isOnline = $derived(person && online.isOnline(chat.chat_id));
|
||||
|
||||
$effect(() => {
|
||||
if (person) {
|
||||
online.watch(chat.chat_id);
|
||||
}
|
||||
});
|
||||
const avatarKind = $derived(chat.chat_id > 0 ? "peer" : "chat");
|
||||
const ownId = $derived(accounts.selected?.tg_user_id ?? null);
|
||||
const showSender = $derived(
|
||||
@@ -74,12 +84,14 @@
|
||||
class:selected
|
||||
use:ripple
|
||||
>
|
||||
<span class="avatar" class:online={isOnline}>
|
||||
<Avatar
|
||||
avatar={{ id: chat.chat_id, kind: avatarKind }}
|
||||
colorKey={chat.chat_id}
|
||||
hasAvatar={chat.has_avatar}
|
||||
name={title}
|
||||
/>
|
||||
</span>
|
||||
<div class="info">
|
||||
<div class="info-row">
|
||||
<h3 class="title">{title}</h3>
|
||||
@@ -90,7 +102,13 @@
|
||||
{#if senderPrefix}
|
||||
<span class="sender">{senderPrefix}</span>
|
||||
{/if}
|
||||
{#if chat.last_deleted}
|
||||
<span class="deleted-mark" title={t("chat.deletedPreview")}
|
||||
><Icon name="delete" size="1rem" /></span
|
||||
><s class="deleted-text">{preview}</s>
|
||||
{:else}
|
||||
{preview}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -147,11 +165,15 @@
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
--online-ring: var(--color-chat-hover);
|
||||
|
||||
background-color: var(--color-chat-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
--online-ring: var(--color-chat-active);
|
||||
|
||||
background-color: var(--color-chat-active);
|
||||
|
||||
.title,
|
||||
@@ -163,6 +185,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.online::after {
|
||||
content: "";
|
||||
|
||||
position: absolute;
|
||||
right: 0.0625rem;
|
||||
bottom: 0.0625rem;
|
||||
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
border: 2px solid var(--online-ring, var(--color-background));
|
||||
border-radius: 50%;
|
||||
|
||||
background-color: var(--color-green);
|
||||
}
|
||||
}
|
||||
|
||||
.deleted-mark {
|
||||
display: inline-flex;
|
||||
margin-inline-end: 0.1875rem;
|
||||
vertical-align: -0.1875rem;
|
||||
}
|
||||
|
||||
.deleted-text {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.info {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { folders } from "#lib/stores/folders.svelte.js";
|
||||
const POLL_INTERVAL = 1500;
|
||||
const POLL_MAX = 12;
|
||||
const PAGE_SIZE = 40;
|
||||
const CHANNEL_IDS = -1_000_000_000_000;
|
||||
const ALL = "all";
|
||||
const EMPTY: Chat[] = [];
|
||||
|
||||
@@ -159,7 +160,29 @@ function createChats() {
|
||||
}
|
||||
}
|
||||
|
||||
function applyDelete(chatId: number | null, ids: number[]) {
|
||||
for (const bucket of Object.values(buckets)) {
|
||||
for (const chat of bucket.list) {
|
||||
const inScope =
|
||||
chatId === null
|
||||
? chat.chat_id > CHANNEL_IDS
|
||||
: chat.chat_id === chatId;
|
||||
if (
|
||||
inScope &&
|
||||
chat.last_message_id !== null &&
|
||||
ids.includes(chat.last_message_id)
|
||||
) {
|
||||
chat.last_deleted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyEvent(event: LiveEvent) {
|
||||
if (event.type === "delete") {
|
||||
applyDelete(event.chat_id, event.message_ids);
|
||||
return;
|
||||
}
|
||||
if (event.type !== "message") {
|
||||
return;
|
||||
}
|
||||
@@ -174,6 +197,8 @@ function createChats() {
|
||||
}
|
||||
seenChat = true;
|
||||
existing.last_date = message.date;
|
||||
existing.last_deleted = message.deleted_at !== null;
|
||||
existing.last_message_id = message.message_id;
|
||||
existing.last_sender_id = message.sender_id;
|
||||
existing.last_text = message.text;
|
||||
existing.message_count += 1;
|
||||
|
||||
Reference in new issue
Block a user