Files
cinny/src/app/hooks/useBookmarks.ts
T

65 lines
1.9 KiB
TypeScript
Raw Normal View History

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;
};
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 };
}