Files
cinny/src/app/utils/bookmarks.ts
T
jaredandClaude Opus 4.8 85ac8de5d9 style: apply prettier across fork files
check:prettier was not part of my gate routine, so formatting drift accumulated
across the session's touched files (and a few older ones). Run prettier --write
to bring the repo back to 'All matched files use Prettier code style!'.
Formatting only — no logic changes. tsc/tests/build all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 13:52:36 -04:00

68 lines
2.6 KiB
TypeScript

import { Bookmark } from '../hooks/useBookmarks';
export type BookmarkSort = 'newest' | 'oldest' | 'room';
const BOOKMARK_SORTS: readonly BookmarkSort[] = ['newest', 'oldest', 'room'];
/** Type guard for persisted/untrusted sort values (localStorage can hold stale/corrupt data). */
export function isBookmarkSort(value: unknown): value is BookmarkSort {
return typeof value === 'string' && (BOOKMARK_SORTS as readonly string[]).includes(value);
}
export type BookmarkRoomGroup = {
roomId: string;
roomName: string;
items: Bookmark[];
};
// Deterministic newest-first comparator: most recently saved first, ties broken
// by eventId so the order is stable regardless of input order (keeps tests and
// re-renders from shuffling equal-timestamp entries).
const byNewest = (a: Bookmark, b: Bookmark): number =>
b.savedAt - a.savedAt || (a.eventId < b.eventId ? -1 : a.eventId > b.eventId ? 1 : 0);
/**
* Return a new array of bookmarks ordered per the selected sort. `room` has no
* flat ordering of its own (grouping is a render concern) and falls back to
* newest-first so callers can still render a sensible flat list if they want.
*/
export function sortBookmarks(bookmarks: Bookmark[], sort: BookmarkSort): Bookmark[] {
const copy = [...bookmarks];
if (sort === 'oldest') {
// Oldest-first is the reverse ordering; keep the same eventId tie-break shape.
return copy.sort(
(a, b) =>
a.savedAt - b.savedAt || (a.eventId < b.eventId ? -1 : a.eventId > b.eventId ? 1 : 0),
);
}
return copy.sort(byNewest);
}
/**
* Bucket bookmarks by room. Items within a group are newest-first; groups are
* ordered by each group's most-recently-saved bookmark (active rooms float up),
* with the room's stored name (the panel overrides with the live name when the
* room is joined). Deterministic tie-breaks keep the output stable.
*/
export function groupBookmarksByRoom(bookmarks: Bookmark[]): BookmarkRoomGroup[] {
const groups = new Map<string, BookmarkRoomGroup>();
bookmarks.forEach((bk) => {
const existing = groups.get(bk.roomId);
if (existing) {
existing.items.push(bk);
} else {
groups.set(bk.roomId, { roomId: bk.roomId, roomName: bk.roomName, items: [bk] });
}
});
const result = [...groups.values()];
result.forEach((group) => group.items.sort(byNewest));
// Order groups by their newest item's savedAt (desc); tie-break by roomId.
result.sort((a, b) => {
const diff = b.items[0].savedAt - a.items[0].savedAt;
if (diff !== 0) return diff;
return a.roomId < b.roomId ? -1 : a.roomId > b.roomId ? 1 : 0;
});
return result;
}