Files
cinny/src/app/hooks/useBookmarks.ts
T
jaredandClaude Opus 4.8 e01b87b214 feat(bookmarks): show who wrote each saved message
Saved-message cards showed the room and time but not the author, so in a busy
room you couldn't tell who said it without jumping. Now each card shows
"{sender} - {time ago}":

- Bookmark gains optional senderId/senderName (snapshotted at save time in
  Message.tsx from the already-computed sender display name); optional so
  existing stored bookmarks stay valid.
- The panel re-resolves the author's current display name live from the event
  when the room is joined, falling back to the stored snapshot for left rooms.
- Search now also matches the author name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:37:12 -04:00

70 lines
2.2 KiB
TypeScript

import { useCallback } from 'react';
import { useMatrixClient } from './useMatrixClient';
import { createAccountDataListStore } from './createAccountDataListStore';
export type Bookmark = {
roomId: string;
eventId: string;
savedAt: number;
previewText: string;
roomName: string;
// Author of the saved message. Optional for backward compatibility with
// bookmarks stored before attribution was added; the panel resolves a live
// name when the room is joined and falls back to these snapshots otherwise.
senderId?: string;
senderName?: string;
};
const BOOKMARKS_KEY = 'io.lotus.bookmarks';
const MAX_BOOKMARKS = 500;
type BookmarksContent = {
bookmarks: Bookmark[];
};
// Shared, concurrency-safe store. See createAccountDataListStore for why the
// snapshot + write queue must be module-scoped (writes are serialized to avoid
// lost updates, since setAccountData replaces the whole content with no merge).
const bookmarksStore = createAccountDataListStore<Bookmark[], BookmarksContent>({
eventType: BOOKMARKS_KEY,
read: (content) => content?.bookmarks ?? [],
write: (bookmarks) => ({ bookmarks }),
});
export function useBookmarks(): {
bookmarks: Bookmark[];
addBookmark: (b: Bookmark) => Promise<void>;
removeBookmark: (eventId: string) => Promise<void>;
isBookmarked: (eventId: string) => boolean;
} {
const mx = useMatrixClient();
const bookmarks = bookmarksStore.useValue(mx);
const addBookmark = useCallback(
(b: Bookmark) =>
bookmarksStore.enqueueWrite(mx, (current) => {
// Avoid duplicates
const filtered = current.filter((bk) => bk.eventId !== b.eventId);
let next = [b, ...filtered];
if (next.length > MAX_BOOKMARKS) {
next = next.slice(0, MAX_BOOKMARKS);
}
return next;
}),
[mx],
);
const removeBookmark = useCallback(
(eventId: string) =>
bookmarksStore.enqueueWrite(mx, (current) => current.filter((bk) => bk.eventId !== eventId)),
[mx],
);
const isBookmarked = useCallback(
(eventId: string) => bookmarks.some((bk) => bk.eventId === eventId),
[bookmarks],
);
return { bookmarks, addBookmark, removeBookmark, isBookmarked };
}