105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
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<Record<ReadOutcome, MessageKey>> = {
|
|
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<string, Intl.DateTimeFormat>();
|
|
|
|
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;
|
|
}
|