84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
|
|
import { useCallback, useEffect, useState } from 'react';
|
||
|
|
import { MatrixClient } from 'matrix-js-sdk';
|
||
|
|
import { useMatrixClient } from './useMatrixClient';
|
||
|
|
import { useAccountDataCallback } from './useAccountDataCallback';
|
||
|
|
|
||
|
|
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[];
|
||
|
|
};
|
||
|
|
|
||
|
|
function readBookmarks(mx: MatrixClient): Bookmark[] {
|
||
|
|
return (
|
||
|
|
(mx.getAccountData(BOOKMARKS_KEY as any)?.getContent() as BookmarksContent | undefined)
|
||
|
|
?.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, setBookmarks] = useState<Bookmark[]>(() => readBookmarks(mx));
|
||
|
|
|
||
|
|
useAccountDataCallback(
|
||
|
|
mx,
|
||
|
|
useCallback(
|
||
|
|
(evt) => {
|
||
|
|
if (evt.getType() === BOOKMARKS_KEY) {
|
||
|
|
setBookmarks(evt.getContent<BookmarksContent>()?.bookmarks ?? []);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
[setBookmarks],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
// Re-read on mx change
|
||
|
|
useEffect(() => {
|
||
|
|
setBookmarks(readBookmarks(mx));
|
||
|
|
}, [mx]);
|
||
|
|
|
||
|
|
const addBookmark = useCallback(
|
||
|
|
async (b: Bookmark) => {
|
||
|
|
const current = readBookmarks(mx);
|
||
|
|
// 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);
|
||
|
|
}
|
||
|
|
await (mx as any).setAccountData(BOOKMARKS_KEY, { bookmarks: next });
|
||
|
|
},
|
||
|
|
[mx],
|
||
|
|
);
|
||
|
|
|
||
|
|
const removeBookmark = useCallback(
|
||
|
|
async (eventId: string) => {
|
||
|
|
const current = readBookmarks(mx);
|
||
|
|
const next = current.filter((bk) => bk.eventId !== eventId);
|
||
|
|
await (mx as any).setAccountData(BOOKMARKS_KEY, { bookmarks: next });
|
||
|
|
},
|
||
|
|
[mx],
|
||
|
|
);
|
||
|
|
|
||
|
|
const isBookmarked = useCallback(
|
||
|
|
(eventId: string) => bookmarks.some((bk) => bk.eventId === eventId),
|
||
|
|
[bookmarks],
|
||
|
|
);
|
||
|
|
|
||
|
|
return { bookmarks, addBookmark, removeBookmark, isBookmarked };
|
||
|
|
}
|