feat(me): populate reads/links from bookmarks instantly

- Add deriveReadsFromBookmarks helper to convert 30023 bookmarks to ReadItems
- Add deriveLinksFromBookmarks helper for web bookmarks (39701) and URLs
- Update loadReadsTab to show bookmarked articles immediately, enrich in background
- Update loadLinksTab to show bookmarked links immediately, enrich in background
- Background enrichment merges reading progress only for displayed items
- Preserve existing pull-to-refresh and empty state logic
This commit is contained in:
Gigi
2025-10-16 08:45:31 +02:00
parent fddf79e0c6
commit 7e2b4b46c9
3 changed files with 177 additions and 28 deletions

View File

@@ -0,0 +1,69 @@
import { Bookmark } from '../types/bookmarks'
import { ReadItem } from '../services/readsService'
import { KINDS } from '../config/kinds'
import { fallbackTitleFromUrl } from './readItemMerge'
/**
* Derives ReadItems from bookmarks for external URLs:
* - Web bookmarks (kind:39701)
* - Any bookmark with http(s) URLs in content or urlReferences
*/
export function deriveLinksFromBookmarks(bookmarks: Bookmark[]): ReadItem[] {
const linksMap = new Map<string, ReadItem>()
const allBookmarks = bookmarks.flatMap(b => b.individualBookmarks || [])
for (const bookmark of allBookmarks) {
const urls: string[] = []
// Web bookmarks (kind:39701) - extract from 'd' tag
if (bookmark.kind === KINDS.WebBookmark) {
const dTag = bookmark.tags.find(t => t[0] === 'd')?.[1]
if (dTag) {
const url = dTag.startsWith('http') ? dTag : `https://${dTag}`
urls.push(url)
}
}
// Extract URLs from urlReferences (pre-extracted by bookmarkService)
if (bookmark.urlReferences && bookmark.urlReferences.length > 0) {
urls.push(...bookmark.urlReferences)
}
// Extract URLs from content if not already captured
if (bookmark.content) {
const urlRegex = /(https?:\/\/[^\s]+)/g
const matches = bookmark.content.match(urlRegex)
if (matches) {
urls.push(...matches)
}
}
// Create ReadItem for each unique URL
for (const url of [...new Set(urls)]) {
if (!linksMap.has(url)) {
const item: ReadItem = {
id: url,
source: 'bookmark',
type: 'external',
url,
title: bookmark.title || fallbackTitleFromUrl(url),
summary: bookmark.summary,
image: bookmark.image,
readingProgress: 0,
readingTimestamp: bookmark.added_at || bookmark.created_at
}
linksMap.set(url, item)
}
}
}
// Sort by most recent bookmark activity
return Array.from(linksMap.values()).sort((a, b) => {
const timeA = a.readingTimestamp || 0
const timeB = b.readingTimestamp || 0
return timeB - timeA
})
}

View File

@@ -0,0 +1,47 @@
import { Bookmark, IndividualBookmark } from '../types/bookmarks'
import { ReadItem } from '../services/readsService'
import { classifyBookmarkType } from './bookmarkTypeClassifier'
import { KINDS } from '../config/kinds'
/**
* Derives ReadItems from bookmarks for Nostr articles (kind:30023).
* Returns items with type='article', using hydrated event data when available.
*/
export function deriveReadsFromBookmarks(bookmarks: Bookmark[]): ReadItem[] {
const readsMap = new Map<string, ReadItem>()
const allBookmarks = bookmarks.flatMap(b => b.individualBookmarks || [])
for (const bookmark of allBookmarks) {
const bookmarkType = classifyBookmarkType(bookmark)
// Only include articles (kind:30023)
if (bookmarkType === 'article' && bookmark.kind === KINDS.BlogPost) {
const coordinate = bookmark.id // Already in coordinate format
const item: ReadItem = {
id: coordinate,
source: 'bookmark',
type: 'article',
readingProgress: 0,
readingTimestamp: bookmark.added_at || bookmark.created_at,
event: bookmark.event,
title: bookmark.title,
summary: bookmark.summary,
image: bookmark.image,
author: bookmark.pubkey,
url: bookmark.url
}
readsMap.set(coordinate, item)
}
}
// Sort by most recent bookmark activity
return Array.from(readsMap.values()).sort((a, b) => {
const timeA = a.readingTimestamp || 0
const timeB = b.readingTimestamp || 0
return timeB - timeA
})
}