feat: implement bookmarks page

This commit is contained in:
Bhavishy
2026-04-14 01:21:27 +05:30
parent bdc8e75640
commit ad3f882081
15 changed files with 1623 additions and 74 deletions
+151
View File
@@ -33,6 +33,7 @@ import {
ROOMS,
COMMENT,
APP_DATA,
asDecryptedEvent,
isSignedEvent,
makeEvent,
normalizeRelayUrl,
@@ -41,11 +42,14 @@ import {
removeFromListByPredicate,
updateList,
getTag,
getTagValue,
getListTags,
getRelayTagValues,
getEventTagValues,
toNostrURI,
RelayMode,
getTagValues,
readList,
uploadBlob,
canUploadBlob,
encryptFile,
@@ -167,6 +171,153 @@ export const broadcastUserData = async (relays: string[]) => {
// List updates
const BOOKMARKS = 10003
const BOOKMARK_LISTS = 30003
const parseListKey = (key: string) => {
const [kind, ...rest] = key.split(":")
return {
kind: parseInt(kind),
d: rest.join(":"),
}
}
const getUserBookmarkList = (key = `${BOOKMARKS}:`) => {
const author = pubkey.get()
const {kind, d} = parseListKey(key)
if (!author) {
return makeList({kind})
}
const latest = first(
repository
.query([{kinds: [kind], authors: [author]}])
.filter(event => getTagValue("d", event.tags) === d),
)
return latest ? readList(asDecryptedEvent(latest)) : makeList({kind})
}
export const createBookmarkList = async (title: string) => {
const d = title.trim()
if (!d) {
return
}
const existing = getUserBookmarkList(`${BOOKMARK_LISTS}:${d}`)
if (existing.event?.id) {
return
}
const list = makeList({kind: BOOKMARK_LISTS})
const event = await updateList(list, {publicTags: [["d", d]]}).reconcile(nip44EncryptToSelf)
const relays = uniq([...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
return publishThunk({event, relays})
}
export const addEventBookmark = async (target: TrustedEvent, key = `${BOOKMARKS}:`) => {
const list = getUserBookmarkList(key)
const {d} = parseListKey(key)
if (d && !list.event?.id) {
return
}
const existing = new Set(getEventTagValues(getListTags(list)))
if (existing.has(target.id)) {
return
}
const event = await addToListPublicly(list, ["e", target.id]).reconcile(nip44EncryptToSelf)
const relays = uniq([
...Router.get().FromUser().getUrls(),
...Router.get().Event(target).limit(3).getUrls(),
...getRelayTagValues(event.tags),
])
return publishThunk({event, relays})
}
export const removeEventBookmark = async (target: TrustedEvent, key = `${BOOKMARKS}:`) => {
const list = getUserBookmarkList(key)
const {d} = parseListKey(key)
if (d && !list.event?.id) {
return
}
const targetD = getTagValue("d", target.tags)
const targetAddress = targetD ? `${target.kind}:${target.pubkey}:${targetD}` : undefined
const hasMatch = getListTags(list).some(
tag =>
(tag[0] === "e" && tag[1] === target.id) ||
(targetAddress !== undefined && tag[0] === "a" && tag[1] === targetAddress),
)
if (!hasMatch) {
return
}
const event = await removeFromListByPredicate(
list,
tag =>
(tag[0] === "e" && tag[1] === target.id) ||
(targetAddress !== undefined && tag[0] === "a" && tag[1] === targetAddress),
).reconcile(nip44EncryptToSelf)
const relays = uniq([
...INDEXER_RELAYS,
...getRelayTagValues(event.tags),
...Router.get().Event(target).limit(3).getUrls(),
])
return publishThunk({event, relays})
}
export const deleteBookmarkList = async (key: string) => {
const list = getUserBookmarkList(key)
const {kind, d} = parseListKey(key)
if (kind !== BOOKMARK_LISTS || !d || !list.event) {
return
}
const relays = uniq([...INDEXER_RELAYS, ...getRelayTagValues(list.event.tags)])
const address = `${kind}:${list.event.pubkey}:${d}`
return publishDelete({protect: false, event: list.event, tags: [["a", address]], relays})
}
export const renameBookmarkList = async (key: string, title: string) => {
const list = getUserBookmarkList(key)
const {kind, d} = parseListKey(key)
const nextD = title.trim()
if (kind !== BOOKMARK_LISTS || !d || !nextD || !list.event) {
return
}
if (nextD === d) {
return
}
const existing = getUserBookmarkList(`${BOOKMARK_LISTS}:${nextD}`)
if (existing.event?.id && existing.event.id !== list.event.id) {
return
}
const publicTags = [["d", nextD], ...list.publicTags.filter(tag => tag[0] !== "d")]
const event = await updateList(list, {publicTags}).reconcile(nip44EncryptToSelf)
const relays = uniq([...INDEXER_RELAYS, ...getRelayTagValues(list.event.tags)])
return publishThunk({event, relays})
}
export const addSpaceMembership = async (url: string) => {
const list = get(userGroupList) || makeList({kind: ROOMS})
const event = await addToListPublicly(list, ["r", url]).reconcile(nip44EncryptToSelf)