feat(frontend): feed of deleted and edited messages above the chat list and per chat in the changes panel
This commit is contained in:
1 parent
010fa29ce0
commit
9f16e2aa56
23 files changed
+1915
-71
No files matched your search
@@ -79,3 +79,8 @@ export function timeZoneLabel(): string {
|
||||
.find((part) => part.type === "timeZoneName")?.value ?? browserTimeZone()
|
||||
);
|
||||
}
|
||||
|
||||
export function formatDayTime(iso: string): string {
|
||||
const date = new Date(iso);
|
||||
return `${format("day", date)}, ${format("time", date)}`;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
export interface DiffPart {
|
||||
changed: boolean;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface WordDiff {
|
||||
after: DiffPart[];
|
||||
before: DiffPart[];
|
||||
}
|
||||
|
||||
const TOKEN = /(\s+)/;
|
||||
const MAX_TOKENS = 400;
|
||||
|
||||
function tokens(text: string): string[] {
|
||||
return text.split(TOKEN).filter(Boolean);
|
||||
}
|
||||
|
||||
function push(parts: DiffPart[], text: string, changed: boolean) {
|
||||
const last = parts.at(-1);
|
||||
if (last && last.changed === changed) {
|
||||
last.text += text;
|
||||
} else {
|
||||
parts.push({ changed, text });
|
||||
}
|
||||
}
|
||||
|
||||
function lcsTable(a: string[], b: string[]): Uint16Array {
|
||||
const width = b.length + 1;
|
||||
const table = new Uint16Array((a.length + 1) * width);
|
||||
for (let i = a.length - 1; i >= 0; i -= 1) {
|
||||
for (let j = b.length - 1; j >= 0; j -= 1) {
|
||||
table[i * width + j] =
|
||||
a[i] === b[j]
|
||||
? table[(i + 1) * width + j + 1] + 1
|
||||
: Math.max(table[(i + 1) * width + j], table[i * width + j + 1]);
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
export function wordDiff(before: string, after: string): WordDiff | null {
|
||||
const a = tokens(before);
|
||||
const b = tokens(after);
|
||||
if (a.length > MAX_TOKENS || b.length > MAX_TOKENS) {
|
||||
return null;
|
||||
}
|
||||
const table = lcsTable(a, b);
|
||||
const width = b.length + 1;
|
||||
const left: DiffPart[] = [];
|
||||
const right: DiffPart[] = [];
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
while (i < a.length && j < b.length) {
|
||||
if (a[i] === b[j]) {
|
||||
push(left, a[i], false);
|
||||
push(right, b[j], false);
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else if (table[(i + 1) * width + j] >= table[i * width + j + 1]) {
|
||||
push(left, a[i], true);
|
||||
i += 1;
|
||||
} else {
|
||||
push(right, b[j], true);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
for (const token of a.slice(i)) {
|
||||
push(left, token, true);
|
||||
}
|
||||
for (const token of b.slice(j)) {
|
||||
push(right, token, true);
|
||||
}
|
||||
return { after: right, before: left };
|
||||
}
|
||||
@@ -1,27 +1,30 @@
|
||||
const MEDIA_KIND_LABELS: Record<string, string> = {
|
||||
animation: "GIF",
|
||||
audio: "Аудио",
|
||||
contact: "Контакт",
|
||||
dice: "Кубик",
|
||||
document: "Файл",
|
||||
game: "Игра",
|
||||
gif: "GIF",
|
||||
location: "Геопозиция",
|
||||
photo: "Фото",
|
||||
poll: "Опрос",
|
||||
sticker: "Стикер",
|
||||
story: "Сторис",
|
||||
venue: "Геопозиция",
|
||||
video: "Видео",
|
||||
video_note: "Кружок",
|
||||
voice: "Голосовое",
|
||||
import { t } from "#lib/i18n/locale.svelte.js";
|
||||
import type { MessageKey } from "#lib/i18n/messages.js";
|
||||
|
||||
const MEDIA_KIND_KEYS: Record<string, MessageKey> = {
|
||||
animation: "media.kind.gif",
|
||||
audio: "media.kind.audio",
|
||||
contact: "media.kind.contact",
|
||||
dice: "media.kind.dice",
|
||||
document: "media.kind.document",
|
||||
game: "media.kind.game",
|
||||
gif: "media.kind.gif",
|
||||
location: "media.kind.location",
|
||||
photo: "media.kind.photo",
|
||||
poll: "media.kind.poll",
|
||||
sticker: "media.kind.sticker",
|
||||
story: "media.kind.story",
|
||||
venue: "media.kind.location",
|
||||
video: "media.kind.video",
|
||||
video_note: "media.kind.videoNote",
|
||||
voice: "media.kind.voice",
|
||||
};
|
||||
|
||||
export function mediaKindLabel(kind: string | null): string | null {
|
||||
if (!kind) {
|
||||
return null;
|
||||
}
|
||||
return MEDIA_KIND_LABELS[kind] ?? "Медиа";
|
||||
return t(MEDIA_KIND_KEYS[kind] ?? "media.kind.other");
|
||||
}
|
||||
|
||||
const BYTE_UNITS = ["B", "KB", "MB", "GB"];
|
||||
|
||||
Reference in new issue
Block a user