import type { MessageView, ReaderView, ReadInfo, ReadOutcome, } from "#lib/api/types.js"; import { formatTime } from "#lib/format/datetime.js"; import { locale, t } from "#lib/i18n/locale.svelte.js"; import type { MessageKey } from "#lib/i18n/messages.js"; export type ReadLine = | { kind: "readers"; readers: ReaderView[] } | { icon: string; kind: "text"; muted: boolean; text: string }; const REASONS: Partial> = { chat_too_big: "read.reason.chat_too_big", error: "read.reason.error", flood: "read.reason.flood", too_old: "read.reason.too_old", unavailable: "read.reason.unavailable", user_privacy: "read.reason.user_privacy", your_privacy: "read.reason.your_privacy", }; const dayFormatters = new Map(); function dayLabel(date: Date, withYear: boolean): string { const formatKey = `${locale.tag}:${withYear}`; let formatter = dayFormatters.get(formatKey); if (!formatter) { formatter = new Intl.DateTimeFormat(locale.tag, { day: "numeric", month: "short", year: withYear ? "numeric" : undefined, }); dayFormatters.set(formatKey, formatter); } const parts = formatter.formatToParts(date); const pick = (type: Intl.DateTimeFormatPartTypes) => parts.find((part) => part.type === type)?.value.replace(".", ""); return [pick("day"), pick("month"), pick("year")].filter(Boolean).join(" "); } export function readWhen(iso: string): string { const date = new Date(iso); const now = new Date(); const time = formatTime(iso); if (date.toDateString() === now.toDateString()) { return t("read.when.today", { time }); } const yesterday = new Date(now); yesterday.setDate(now.getDate() - 1); if (date.toDateString() === yesterday.toDateString()) { return t("read.when.yesterday", { time }); } const withYear = date.getFullYear() !== now.getFullYear(); return t("read.when.day", { day: dayLabel(date, withYear), time }); } function line(text: string, icon = "message-read", muted = false): ReadLine { return { icon, kind: "text", muted, text }; } export function readTitle(message: MessageView): string | undefined { if (message.read_at) { return t("read.at", { when: readWhen(message.read_at) }); } return message.read ? t("read.done") : undefined; } export function readLine( message: MessageView, info: ReadInfo | null, fetching: boolean ): ReadLine | null { if (info === null) { const title = readTitle(message); return title ? line(title) : null; } if (info.chat_kind === "group" && info.readers.length > 0) { return { kind: "readers", readers: info.readers }; } if (info.voice && info.listened_at) { return line( t("read.listened", { when: readWhen(info.listened_at) }), "play-story" ); } const at = info.exact_at ?? info.observed_at; if (at) { return line(t("read.at", { when: readWhen(at) })); } if (info.read) { return line(t("read.done")); } if (info.unread) { return line(t("read.unread"), "check"); } const reason = info.outcome ? REASONS[info.outcome] : undefined; if (reason) { return line(t(reason), "eye-crossed-outline", true); } return fetching ? line(t("read.pending"), "message-read", true) : null; }