mirror of
https://github.com/block/buzz.git
synced 2026-11-08 10:21:46 +00:00
170 lines
4.6 KiB
TypeScript
170 lines
4.6 KiB
TypeScript
import type { Channel, RelayEvent } from "@/shared/api/types";
|
|
|
|
import type { TimelineMessage } from "@/features/messages/types";
|
|
import { getThreadReference } from "@/features/messages/lib/threading";
|
|
import {
|
|
resolveUserLabel,
|
|
type UserProfileLookup,
|
|
} from "@/features/profile/lib/identity";
|
|
|
|
function getEffectiveAuthorPubkey(event: RelayEvent) {
|
|
const [firstTag] = event.tags;
|
|
if (
|
|
firstTag?.[0] === "p" &&
|
|
firstTag[1] &&
|
|
event.tags.some((tag) => tag[0] === "h")
|
|
) {
|
|
return firstTag[1];
|
|
}
|
|
|
|
return event.pubkey;
|
|
}
|
|
|
|
function formatMessageAuthor(
|
|
event: RelayEvent,
|
|
channel: Channel | null,
|
|
currentPubkey: string | undefined,
|
|
profiles: UserProfileLookup | undefined,
|
|
) {
|
|
const authorPubkey = getEffectiveAuthorPubkey(event);
|
|
const fallbackName =
|
|
channel?.channelType === "dm"
|
|
? (() => {
|
|
const participantIndex =
|
|
channel.participantPubkeys.indexOf(authorPubkey);
|
|
if (participantIndex < 0) {
|
|
return null;
|
|
}
|
|
|
|
return channel.participants[participantIndex] ?? null;
|
|
})()
|
|
: null;
|
|
|
|
return resolveUserLabel({
|
|
pubkey: authorPubkey,
|
|
currentPubkey,
|
|
fallbackName,
|
|
profiles,
|
|
preferResolvedSelfLabel: true,
|
|
});
|
|
}
|
|
|
|
function getAuthorAvatarUrl(input: {
|
|
authorPubkey: string;
|
|
currentPubkey: string | undefined;
|
|
currentUserAvatarUrl: string | null;
|
|
profiles: UserProfileLookup | undefined;
|
|
}) {
|
|
const { authorPubkey, currentPubkey, currentUserAvatarUrl, profiles } = input;
|
|
|
|
if (currentPubkey === authorPubkey) {
|
|
return currentUserAvatarUrl ?? null;
|
|
}
|
|
|
|
return profiles?.[authorPubkey.toLowerCase()]?.avatarUrl ?? null;
|
|
}
|
|
|
|
export function formatTimelineMessages(
|
|
events: RelayEvent[],
|
|
channel: Channel | null,
|
|
currentPubkey: string | undefined,
|
|
currentUserAvatarUrl: string | null,
|
|
profiles?: UserProfileLookup,
|
|
): TimelineMessage[] {
|
|
const eventsById = new Map(events.map((event) => [event.id, event]));
|
|
const authorPubkeyByEventId = new Map<string, string>();
|
|
const authorLabelByEventId = new Map<string, string>();
|
|
const depthByEventId = new Map<string, number>();
|
|
const resolvingEventIds = new Set<string>();
|
|
|
|
function getAuthorLabel(event: RelayEvent) {
|
|
const cached = authorLabelByEventId.get(event.id);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
|
|
const authorPubkey = getEffectiveAuthorPubkey(event);
|
|
const author = formatMessageAuthor(event, channel, currentPubkey, profiles);
|
|
|
|
authorPubkeyByEventId.set(event.id, authorPubkey);
|
|
authorLabelByEventId.set(event.id, author);
|
|
return author;
|
|
}
|
|
|
|
function getDepth(event: RelayEvent): number {
|
|
const cached = depthByEventId.get(event.id);
|
|
if (cached !== undefined) {
|
|
return cached;
|
|
}
|
|
|
|
if (resolvingEventIds.has(event.id)) {
|
|
return 0;
|
|
}
|
|
|
|
const thread = getThreadReference(event.tags);
|
|
if (!thread.parentId) {
|
|
depthByEventId.set(event.id, 0);
|
|
return 0;
|
|
}
|
|
|
|
const parent = eventsById.get(thread.parentId);
|
|
if (!parent) {
|
|
const fallbackDepth =
|
|
thread.rootId && thread.rootId !== thread.parentId ? 2 : 1;
|
|
depthByEventId.set(event.id, fallbackDepth);
|
|
return fallbackDepth;
|
|
}
|
|
|
|
resolvingEventIds.add(event.id);
|
|
const depth = getDepth(parent) + 1;
|
|
resolvingEventIds.delete(event.id);
|
|
depthByEventId.set(event.id, depth);
|
|
return depth;
|
|
}
|
|
|
|
return events.map((event) => {
|
|
const author = getAuthorLabel(event);
|
|
const authorPubkey =
|
|
authorPubkeyByEventId.get(event.id) ?? getEffectiveAuthorPubkey(event);
|
|
const thread = getThreadReference(event.tags);
|
|
const parentEvent = thread.parentId
|
|
? eventsById.get(thread.parentId)
|
|
: undefined;
|
|
|
|
return {
|
|
id: event.id,
|
|
createdAt: event.created_at,
|
|
pubkey: authorPubkey,
|
|
author,
|
|
avatarUrl: getAuthorAvatarUrl({
|
|
authorPubkey,
|
|
currentPubkey,
|
|
currentUserAvatarUrl,
|
|
profiles,
|
|
}),
|
|
time: new Intl.DateTimeFormat("en-US", {
|
|
hour: "numeric",
|
|
minute: "2-digit",
|
|
}).format(new Date(event.created_at * 1_000)),
|
|
body: event.content,
|
|
parentId: thread.parentId,
|
|
rootId: thread.rootId,
|
|
depth: getDepth(event),
|
|
replyToAuthor: parentEvent ? getAuthorLabel(parentEvent) : null,
|
|
replyToSnippet: parentEvent?.content ?? null,
|
|
accent: currentPubkey === authorPubkey,
|
|
pending: event.pending,
|
|
kind: event.kind,
|
|
tags: event.tags,
|
|
};
|
|
});
|
|
}
|
|
|
|
export function collectMessageAuthorPubkeys(events: RelayEvent[]) {
|
|
return [
|
|
...new Set(
|
|
events.map((event) => getEffectiveAuthorPubkey(event).toLowerCase()),
|
|
),
|
|
];
|
|
}
|