feat(frontend): profiles chip in the feed shows name, username, photo and title changes
This commit is contained in:
1 parent
8e0f828dd4
commit
98dda99c5c
9 files changed
+181
-14
No files matched your search
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { FeedKind } from "#lib/api/types.js";
|
||||
import type { FeedItem, FeedKind } from "#lib/api/types.js";
|
||||
import FeedFilters from "#lib/components/feed/FeedFilters.svelte";
|
||||
import FeedList from "#lib/components/feed/FeedList.svelte";
|
||||
import { ui } from "#lib/stores/ui.svelte.js";
|
||||
@@ -16,10 +16,14 @@
|
||||
kind = next;
|
||||
}
|
||||
|
||||
function open(messageId: number) {
|
||||
function open(item: FeedItem, messageId: number) {
|
||||
if (chatId === null) {
|
||||
return;
|
||||
}
|
||||
if (item.kind === "profile" && !messageId) {
|
||||
ui.openPanel("profile");
|
||||
return;
|
||||
}
|
||||
ui.requestJump(chatId, messageId);
|
||||
if (isMobile()) {
|
||||
ui.closePanel();
|
||||
@@ -30,6 +34,6 @@
|
||||
{#if chatId !== null}
|
||||
<FeedFilters {kind} onkind={pick} />
|
||||
{#key chatId}
|
||||
<FeedList {chatId} {kind} onopen={(_, messageId) => open(messageId)} />
|
||||
<FeedList {chatId} {kind} onopen={open} />
|
||||
{/key}
|
||||
{/if}
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ripple } from "#lib/actions/ripple.js";
|
||||
import type { FeedItem, MessageView } from "#lib/api/types.js";
|
||||
import FeedBulk from "#lib/components/feed/FeedBulk.svelte";
|
||||
import FeedPhoto from "#lib/components/feed/FeedPhoto.svelte";
|
||||
import FeedText from "#lib/components/feed/FeedText.svelte";
|
||||
import Avatar from "#lib/components/ui/Avatar.svelte";
|
||||
import Icon from "#lib/components/ui/Icon.svelte";
|
||||
@@ -20,6 +21,14 @@
|
||||
|
||||
let { item, onopen, inChat = false }: Props = $props();
|
||||
|
||||
const FIELD_KEYS = {
|
||||
name: "feed.field.name",
|
||||
photo: "feed.field.photo",
|
||||
title: "feed.field.title",
|
||||
username: "feed.field.username",
|
||||
} as const;
|
||||
const FIELD_ORDER = ["name", "title", "username", "photo"] as const;
|
||||
|
||||
let expanded = $state(false);
|
||||
|
||||
const ownId = $derived(accounts.selected?.tg_user_id ?? null);
|
||||
@@ -67,8 +76,14 @@
|
||||
? t("chat.deletedAccount")
|
||||
: t("chat.fallbackTitle", { id: item.chat.chat_id }))
|
||||
);
|
||||
const profile = $derived(item.kind === "profile");
|
||||
const fields = $derived(
|
||||
FIELD_ORDER.filter((field) => item.detail?.[field]).map(
|
||||
(field) => [field, item.detail?.[field] ?? [null, null]] as const
|
||||
)
|
||||
);
|
||||
const title = $derived.by(() => {
|
||||
if (!inChat) {
|
||||
if (!inChat || profile) {
|
||||
return chatTitle;
|
||||
}
|
||||
if (bulk) {
|
||||
@@ -77,7 +92,7 @@
|
||||
return senderName(item.sender_id, item.sender_name);
|
||||
});
|
||||
const avatar = $derived.by(() => {
|
||||
if (!inChat) {
|
||||
if (!inChat || profile) {
|
||||
return {
|
||||
has: item.chat.has_avatar,
|
||||
id: item.chat.chat_id,
|
||||
@@ -92,7 +107,7 @@
|
||||
};
|
||||
});
|
||||
const prefix = $derived.by(() => {
|
||||
if (inChat) {
|
||||
if (inChat || profile) {
|
||||
return "";
|
||||
}
|
||||
if (item.outgoing) {
|
||||
@@ -107,6 +122,13 @@
|
||||
: null
|
||||
);
|
||||
|
||||
function shown(field: string, value: string | null): string {
|
||||
if (!value) {
|
||||
return t("feed.noText");
|
||||
}
|
||||
return field === "username" ? `@${value}` : value;
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (bulk) {
|
||||
expanded = !expanded;
|
||||
@@ -142,7 +164,27 @@
|
||||
{formatListDate(item.at)}
|
||||
</time>
|
||||
</span>
|
||||
{#if bulk}
|
||||
{#if profile}
|
||||
{#each fields as [field, pair] (field)}
|
||||
<span class="change now">
|
||||
<span class="tag">{t(FIELD_KEYS[field])}</span>
|
||||
{#if field === "photo"}
|
||||
<FeedPhoto ownerId={item.chat.chat_id} uniqueId={pair[0]} />
|
||||
<span class="arrow">→</span>
|
||||
<FeedPhoto ownerId={item.chat.chat_id} uniqueId={pair[1]} />
|
||||
{:else}
|
||||
<span class="was">{shown(field, pair[0])}</span>
|
||||
<span class="arrow">→</span>
|
||||
{shown(field, pair[1])}
|
||||
{/if}
|
||||
</span>
|
||||
{/each}
|
||||
{#if item.message_count > fields.length}
|
||||
<span class="count">
|
||||
{t("feed.profileCount", { count: item.message_count })}
|
||||
</span>
|
||||
{/if}
|
||||
{:else if bulk}
|
||||
<span class="mark">
|
||||
<Icon name="delete" size="0.875rem" />
|
||||
{inChat
|
||||
@@ -360,4 +402,18 @@
|
||||
.missing {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.was {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
margin-inline: 0.25rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
</style>
|
||||
@@ -16,6 +16,7 @@
|
||||
all: "feed.filter.all",
|
||||
deleted: "feed.filter.deleted",
|
||||
edited: "feed.filter.edited",
|
||||
profile: "feed.filter.profile",
|
||||
} as const;
|
||||
|
||||
const CHAT_KEYS = {
|
||||
@@ -69,7 +70,7 @@
|
||||
|
||||
.group {
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
all: "feed.empty.all",
|
||||
deleted: "feed.empty.deleted",
|
||||
edited: "feed.empty.edited",
|
||||
profile: "feed.empty.profile",
|
||||
} as const;
|
||||
|
||||
let items = $state<FeedItem[]>([]);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { loadAvatarVariant } from "#lib/api/avatars.js";
|
||||
import Icon from "#lib/components/ui/Icon.svelte";
|
||||
|
||||
interface Props {
|
||||
ownerId: number;
|
||||
uniqueId: string | null;
|
||||
}
|
||||
|
||||
let { ownerId, uniqueId }: Props = $props();
|
||||
|
||||
let url = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
url = null;
|
||||
if (uniqueId === null) {
|
||||
return;
|
||||
}
|
||||
let active = true;
|
||||
loadAvatarVariant(ownerId > 0 ? "peer" : "chat", ownerId, uniqueId).then(
|
||||
(resolved) => {
|
||||
if (active) {
|
||||
url = resolved;
|
||||
}
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<span class="feed-photo">
|
||||
{#if url}
|
||||
<img alt="" src={url}>
|
||||
{:else}
|
||||
<Icon name="photo" size="0.875rem" />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<style lang="scss">
|
||||
.feed-photo {
|
||||
overflow: hidden;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 50%;
|
||||
|
||||
vertical-align: middle;
|
||||
|
||||
color: var(--color-white);
|
||||
|
||||
background-color: var(--color-gray);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
@@ -11,6 +11,7 @@
|
||||
feed,
|
||||
messageHref,
|
||||
} from "#lib/stores/feed.svelte.js";
|
||||
import { ui } from "#lib/stores/ui.svelte.js";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
|
||||
@@ -20,8 +21,11 @@
|
||||
|
||||
let { filter }: Props = $props();
|
||||
|
||||
function open(item: FeedItem, messageId: number) {
|
||||
goto(messageHref(page.url, item.chat.chat_id, messageId));
|
||||
async function open(item: FeedItem, messageId: number) {
|
||||
await goto(messageHref(page.url, item.chat.chat_id, messageId));
|
||||
if (item.kind === "profile" && !messageId) {
|
||||
ui.openPanel("profile");
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
Reference in new issue
Block a user