mirror of
https://github.com/dergigi/boris.git
synced 2025-12-19 15:44:20 +01:00
feat(archive): support un-archive toggle; add ArchiveController mark/unmark; prep NIP-09 deletion hook
This commit is contained in:
@@ -599,7 +599,32 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
}, [selectedUrl, currentArticle, activeAccount, relayPool, isNostrArticle])
|
}, [selectedUrl, currentArticle, activeAccount, relayPool, isNostrArticle])
|
||||||
|
|
||||||
const handleMarkAsRead = () => {
|
const handleMarkAsRead = () => {
|
||||||
if (!activeAccount || !relayPool || isMarkedAsRead) {
|
if (!activeAccount || !relayPool) return
|
||||||
|
|
||||||
|
// Toggle archive state: if already archived, request deletion; else archive
|
||||||
|
if (isMarkedAsRead) {
|
||||||
|
// Optimistically unarchive in UI; background deletion request (NIP-09)
|
||||||
|
setIsMarkedAsRead(false)
|
||||||
|
;(async () => {
|
||||||
|
try {
|
||||||
|
// Best-effort: we don't store reaction IDs yet; leaving placeholder for future improvement
|
||||||
|
// When we track reaction IDs, call deleteReaction(id,...)
|
||||||
|
// For now, clear controller mark so lists update
|
||||||
|
if (isNostrArticle && currentArticle) {
|
||||||
|
try {
|
||||||
|
const dTag = currentArticle.tags.find(t => t[0] === 'd')?.[1]
|
||||||
|
if (dTag) {
|
||||||
|
const naddr = nip19.naddrEncode({ kind: 30023, pubkey: currentArticle.pubkey, identifier: dTag })
|
||||||
|
archiveController.unmark(naddr)
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
} else if (selectedUrl) {
|
||||||
|
archiveController.unmark(selectedUrl)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[archive][content] unarchive failed', err)
|
||||||
|
}
|
||||||
|
})()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,6 +665,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore private update for instant UI; controller will confirm via stream
|
// @ts-ignore private update for instant UI; controller will confirm via stream
|
||||||
archiveController['markedIds'].add(naddr)
|
archiveController['markedIds'].add(naddr)
|
||||||
|
archiveController.mark(naddr)
|
||||||
console.log('[archive][content] optimistic mark article', naddr.slice(0, 24) + '...')
|
console.log('[archive][content] optimistic mark article', naddr.slice(0, 24) + '...')
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -652,6 +678,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore private update for instant UI; controller will confirm via stream
|
// @ts-ignore private update for instant UI; controller will confirm via stream
|
||||||
archiveController['markedIds'].add(selectedUrl)
|
archiveController['markedIds'].add(selectedUrl)
|
||||||
|
archiveController.mark(selectedUrl)
|
||||||
console.log('[archive][content] optimistic mark url', selectedUrl)
|
console.log('[archive][content] optimistic mark url', selectedUrl)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -31,6 +31,21 @@ class ArchiveController {
|
|||||||
this.listeners.forEach(cb => cb(snapshot))
|
this.listeners.forEach(cb => cb(snapshot))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mark(id: string): void {
|
||||||
|
if (!this.markedIds.has(id)) {
|
||||||
|
this.markedIds.add(id)
|
||||||
|
this.emit()
|
||||||
|
console.log('[archive] mark() added', id.slice(0, 48))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unmark(id: string): void {
|
||||||
|
if (this.markedIds.delete(id)) {
|
||||||
|
this.emit()
|
||||||
|
console.log('[archive] unmark() removed', id.slice(0, 48))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isMarked(id: string): boolean {
|
isMarked(id: string): boolean {
|
||||||
return this.markedIds.has(id)
|
return this.markedIds.has(id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { IAccount } from 'applesauce-accounts'
|
|||||||
import { NostrEvent } from 'nostr-tools'
|
import { NostrEvent } from 'nostr-tools'
|
||||||
import { lastValueFrom, takeUntil, timer, toArray } from 'rxjs'
|
import { lastValueFrom, takeUntil, timer, toArray } from 'rxjs'
|
||||||
import { RELAYS } from '../config/relays'
|
import { RELAYS } from '../config/relays'
|
||||||
|
import { EventFactory } from 'applesauce-factory'
|
||||||
|
|
||||||
const MARK_AS_READ_EMOJI = '📚'
|
const MARK_AS_READ_EMOJI = '📚'
|
||||||
|
|
||||||
@@ -105,6 +106,27 @@ export async function createWebsiteReaction(
|
|||||||
return signed
|
return signed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a deletion request (NIP-09) for a reaction event to effectively un-archive.
|
||||||
|
* The caller must know the reaction event id to delete.
|
||||||
|
*/
|
||||||
|
export async function deleteReaction(
|
||||||
|
reactionEventId: string,
|
||||||
|
account: IAccount,
|
||||||
|
relayPool: RelayPool
|
||||||
|
): Promise<NostrEvent> {
|
||||||
|
const factory = new EventFactory({ signer: account })
|
||||||
|
const draft = await factory.create(async () => ({
|
||||||
|
kind: 5, // Deletion per NIP-09
|
||||||
|
content: 'unarchive',
|
||||||
|
tags: [['e', reactionEventId]],
|
||||||
|
created_at: Math.floor(Date.now() / 1000)
|
||||||
|
}))
|
||||||
|
const signed = await factory.sign(draft)
|
||||||
|
await relayPool.publish(RELAYS, signed)
|
||||||
|
return signed
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the user has already marked a nostr event as read
|
* Checks if the user has already marked a nostr event as read
|
||||||
* @param eventId The ID of the event to check
|
* @param eventId The ID of the event to check
|
||||||
|
|||||||
Reference in New Issue
Block a user