feat(frontend): feed of deleted and edited messages above the chat list and per chat in the changes panel

This commit is contained in:
hh committed 2026-10-19 03:00:12 +00:00
1 parent 010fa29ce0
commit 9f16e2aa56
23 files changed
+1915 -71

No files matched your search

+39 -14
View File
@@ -8,14 +8,17 @@
import Icon from "#lib/components/ui/Icon.svelte";
import { peerName } from "#lib/format/peer.js";
import { formatPresence, isOnline } from "#lib/format/presence.js";
import { t } from "#lib/i18n/locale.svelte.js";
import { accounts } from "#lib/stores/accounts.svelte.js";
import { chats } from "#lib/stores/chats.svelte.js";
import { discover } from "#lib/stores/discover.svelte.js";
import { withoutMessage } from "#lib/stores/feed.svelte.js";
import { policyUi } from "#lib/stores/policy.svelte.js";
import { presence } from "#lib/stores/presence.svelte.js";
import { toasts } from "#lib/stores/toasts.svelte.js";
import { ui } from "#lib/stores/ui.svelte.js";
import { goto } from "$app/navigation";
import { page } from "$app/state";
interface Props {
chatId: number;
@@ -36,14 +39,20 @@
backfilling = true;
try {
await enqueueBackfill(chatId, true);
toasts.success("Догружаем новые сообщения");
toasts.success(t("chat.backfill.started"));
} catch {
toasts.error("Не удалось запустить бэкфилл");
toasts.error(t("chat.backfill.failed"));
} finally {
backfilling = false;
}
}
function back() {
const target = new URL(page.url.href);
target.pathname = "/app";
goto(withoutMessage(target));
}
$effect(() => {
if (accounts.selectedId === null) {
return;
@@ -82,7 +91,9 @@
const fallbackTitle = $derived(
chat?.title ??
discovered?.title ??
(isDm ? "Удалённый аккаунт" : `Chat ${chatId}`)
(isDm
? t("chat.deletedAccount")
: t("chat.fallbackTitle", { id: chatId }))
);
const title = $derived(isDm && peer ? peerName(peer) : fallbackTitle);
const subtitle = $derived.by(() => {
@@ -97,9 +108,11 @@
}
const count = chat?.message_count ?? discovered?.message_count ?? 0;
if (count > 0) {
return `${count} messages`;
return t("chat.messages", { count });
}
return discovered?.kind === "channel" ? "channel" : "group";
return discovered?.kind === "channel"
? t("chat.kind.channel")
: t("chat.kind.group");
});
const avatarKind = $derived(isDm ? "peer" : "chat");
const hasAvatar = $derived(
@@ -114,8 +127,8 @@
<header class="chat-header">
<div class="back">
<Button
aria-label="Назад"
onclick={() => goto("/app")}
aria-label={t("common.back")}
onclick={back}
round
smaller
variant="translucent"
@@ -129,7 +142,7 @@
})}
<button
{...props}
aria-label="Открыть профиль"
aria-label={t("chat.openProfile")}
class="peek"
onclick={() => ui.openPanel("profile")}
type="button"
@@ -157,23 +170,26 @@
{/snippet}
{#snippet menu()}
<ContextMenuItem icon="info" onselect={() => ui.openPanel("profile")}
>Открыть профиль</ContextMenuItem
>{t("chat.openProfile")}</ContextMenuItem
>
<ContextMenuItem icon="recent" onselect={() => ui.openPanel("changes")}
>{t("feed.changes")}</ContextMenuItem
>
<ContextMenuItem
icon="play-story"
onselect={() => ui.openPanel("stories")}
>Сторис</ContextMenuItem
>{t("settings.stories")}</ContextMenuItem
>
<ContextMenuItem
icon="settings"
onselect={() => policyUi.edit({ chatId }, title)}
>Политика захвата</ContextMenuItem
>{t("settings.policy")}</ContextMenuItem
>
{/snippet}
</ContextMenu>
<div class="actions">
<Button
aria-label="Поиск в чате"
aria-label={t("chat.search")}
onclick={() => ui.openPanel("search")}
round
smaller
@@ -182,7 +198,16 @@
<Icon name="search" />
</Button>
<Button
aria-label="Аналитика"
aria-label={t("feed.changes")}
onclick={() => ui.openPanel("changes")}
round
smaller
variant="translucent"
>
<Icon name="recent" />
</Button>
<Button
aria-label={t("chat.analytics")}
onclick={() => ui.openPanel("presence")}
round
smaller
@@ -191,7 +216,7 @@
<Icon name="stats" />
</Button>
<Button
aria-label="Догрузить новые сообщения"
aria-label={t("chat.backfill")}
loading={backfilling}
onclick={backfill}
round