feat(frontend): my message menu shows when it was read or listened, group readers open in a right panel
This commit is contained in:
1 parent
f03d7c2890
commit
bb38e23f29
14 files changed
+543
-10
No files matched your search
@@ -0,0 +1,104 @@
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user