mirror of
https://github.com/dergigi/boris.git
synced 2026-01-18 06:14:27 +01:00
refactor(bookmarks): never default timestamps to now; allow nulls and sort nulls last; render empty when missing
This commit is contained in:
@@ -112,10 +112,10 @@ export const CardView: React.FC<CardViewProps> = ({
|
||||
title="Open event in search"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{formatDate(bookmark.created_at || bookmark.listUpdatedAt || Math.floor(Date.now() / 1000))}
|
||||
{formatDate(bookmark.created_at ?? bookmark.listUpdatedAt)}
|
||||
</a>
|
||||
) : (
|
||||
<span className="bookmark-date">{formatDate(bookmark.created_at || bookmark.listUpdatedAt || Math.floor(Date.now() / 1000))}</span>
|
||||
<span className="bookmark-date">{formatDate(bookmark.created_at ?? bookmark.listUpdatedAt)}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export const CompactView: React.FC<CompactViewProps> = ({
|
||||
<code>{bookmark.id.slice(0, 12)}...</code>
|
||||
</div>
|
||||
)}
|
||||
<span className="bookmark-date-compact">{formatDateCompact(bookmark.created_at || bookmark.listUpdatedAt || Math.floor(Date.now() / 1000))}</span>
|
||||
<span className="bookmark-date-compact">{formatDateCompact(bookmark.created_at ?? bookmark.listUpdatedAt)}</span>
|
||||
{/* CTA removed */}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ export const LargeView: React.FC<LargeViewProps> = ({
|
||||
className="bookmark-date-link"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{formatDate(bookmark.created_at || bookmark.listUpdatedAt || Math.floor(Date.now() / 1000))}
|
||||
{formatDate(bookmark.created_at ?? bookmark.listUpdatedAt)}
|
||||
</a>
|
||||
)}
|
||||
|
||||
|
||||
@@ -419,7 +419,7 @@ const Me: React.FC<MeProps> = ({
|
||||
const mockEvent = {
|
||||
id: item.id,
|
||||
pubkey: item.author || '',
|
||||
created_at: item.readingTimestamp || Math.floor(Date.now() / 1000),
|
||||
created_at: item.readingTimestamp || 0,
|
||||
kind: 1,
|
||||
tags: [] as string[][],
|
||||
content: item.title || item.url || 'Untitled',
|
||||
|
||||
@@ -343,7 +343,7 @@ class BookmarkController {
|
||||
...b,
|
||||
tags: b.tags || [],
|
||||
content: b.content || this.externalEventStore?.getEvent(b.id)?.content || '', // Fallback to eventStore content
|
||||
created_at: b.created_at || this.externalEventStore?.getEvent(b.id)?.created_at || b.created_at
|
||||
created_at: (b.created_at ?? this.externalEventStore?.getEvent(b.id)?.created_at ?? null)
|
||||
}))
|
||||
|
||||
const sortedBookmarks = enriched
|
||||
@@ -352,9 +352,10 @@ class BookmarkController {
|
||||
urlReferences: extractUrlsFromContent(b.content)
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
// Sort by listUpdatedAt (timestamp of bookmark list event = proxy for when bookmarked)
|
||||
// Newest first (descending)
|
||||
return (b.listUpdatedAt || 0) - (a.listUpdatedAt || 0)
|
||||
// Sort by listUpdatedAt desc, nulls last
|
||||
const aTs = a.listUpdatedAt ?? -Infinity
|
||||
const bTs = b.listUpdatedAt ?? -Infinity
|
||||
return bTs - aTs
|
||||
})
|
||||
|
||||
// DEBUG: Show top 5 sorted bookmarks
|
||||
@@ -370,7 +371,7 @@ class BookmarkController {
|
||||
title: `Bookmarks (${sortedBookmarks.length})`,
|
||||
url: '',
|
||||
content: latestContent,
|
||||
created_at: newestCreatedAt || Math.floor(Date.now() / 1000),
|
||||
created_at: newestCreatedAt || 0,
|
||||
tags: allTags,
|
||||
bookmarkCount: sortedBookmarks.length,
|
||||
eventReferences: allTags.filter((tag: string[]) => tag[0] === 'e').map((tag: string[]) => tag[1]),
|
||||
|
||||
@@ -81,7 +81,7 @@ export const processApplesauceBookmarks = (
|
||||
allItems.push({
|
||||
id: note.id,
|
||||
content: '',
|
||||
created_at: note.created_at || 0,
|
||||
created_at: note.created_at ?? null,
|
||||
pubkey: note.author || activeAccount.pubkey,
|
||||
kind: 1, // Short note kind
|
||||
tags: [],
|
||||
@@ -101,14 +101,14 @@ export const processApplesauceBookmarks = (
|
||||
allItems.push({
|
||||
id: coordinate,
|
||||
content: '',
|
||||
created_at: article.created_at || 0,
|
||||
created_at: article.created_at ?? null,
|
||||
pubkey: article.pubkey,
|
||||
kind: article.kind, // Usually 30023 for long-form articles
|
||||
tags: [],
|
||||
parsedContent: undefined,
|
||||
type: 'event' as const,
|
||||
isPrivate,
|
||||
listUpdatedAt: parentCreatedAt || 0
|
||||
listUpdatedAt: parentCreatedAt ?? null
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export const processApplesauceBookmarks = (
|
||||
parsedContent: undefined,
|
||||
type: 'event' as const,
|
||||
isPrivate,
|
||||
listUpdatedAt: parentCreatedAt || 0
|
||||
listUpdatedAt: parentCreatedAt ?? null
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -159,14 +159,14 @@ export const processApplesauceBookmarks = (
|
||||
return {
|
||||
id: bookmark.id!,
|
||||
content: bookmark.content || '',
|
||||
created_at: bookmark.created_at || 0,
|
||||
created_at: bookmark.created_at ?? null,
|
||||
pubkey: activeAccount.pubkey,
|
||||
kind: bookmark.kind || 30001,
|
||||
tags: bookmark.tags || [],
|
||||
parsedContent: bookmark.content ? (getParsedContent(bookmark.content) as ParsedContent) : undefined,
|
||||
type: 'event' as const,
|
||||
isPrivate,
|
||||
listUpdatedAt: parentCreatedAt || 0
|
||||
listUpdatedAt: parentCreatedAt ?? null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ export async function collectBookmarksFromEvents(
|
||||
publicItemsAll.push({
|
||||
id: evt.id,
|
||||
content: evt.content || '',
|
||||
created_at: evt.created_at || Math.floor(Date.now() / 1000),
|
||||
created_at: evt.created_at ?? null,
|
||||
pubkey: evt.pubkey,
|
||||
kind: evt.kind,
|
||||
tags: evt.tags || [],
|
||||
@@ -148,7 +148,7 @@ export async function collectBookmarksFromEvents(
|
||||
setTitle,
|
||||
setDescription,
|
||||
setImage,
|
||||
listUpdatedAt: evt.created_at || Math.floor(Date.now() / 1000)
|
||||
listUpdatedAt: evt.created_at ?? null
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export async function fetchAllReads(
|
||||
source: 'bookmark',
|
||||
type: 'article',
|
||||
readingProgress: 0,
|
||||
readingTimestamp: bookmark.created_at
|
||||
readingTimestamp: bookmark.created_at ?? undefined
|
||||
}
|
||||
readsMap.set(coordinate, item)
|
||||
if (onItem) emitItem(item)
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface IndividualBookmark {
|
||||
id: string
|
||||
content: string
|
||||
// Timestamp when the content was created (from the content event itself)
|
||||
created_at: number
|
||||
created_at: number | null
|
||||
pubkey: string
|
||||
kind: number
|
||||
tags: string[][]
|
||||
@@ -51,7 +51,7 @@ export interface IndividualBookmark {
|
||||
setImage?: string
|
||||
// Timestamp of the bookmark list event (best proxy for "when bookmarked")
|
||||
// Note: This is imperfect - it's when the list was last updated, not necessarily when this item was added
|
||||
listUpdatedAt?: number
|
||||
listUpdatedAt?: number | null
|
||||
}
|
||||
|
||||
export interface ActiveAccount {
|
||||
|
||||
@@ -4,16 +4,16 @@ import { ParsedContent, ParsedNode, IndividualBookmark } from '../types/bookmark
|
||||
import ResolvedMention from '../components/ResolvedMention'
|
||||
// Note: RichContent is imported by components directly to keep this file component-only for fast refresh
|
||||
|
||||
export const formatDate = (timestamp: number) => {
|
||||
const safe = typeof timestamp === 'number' && isFinite(timestamp) && timestamp > 0 ? timestamp : Math.floor(Date.now() / 1000)
|
||||
const date = new Date(safe * 1000)
|
||||
export const formatDate = (timestamp: number | null | undefined) => {
|
||||
if (!timestamp || !isFinite(timestamp) || timestamp <= 0) return ''
|
||||
const date = new Date(timestamp * 1000)
|
||||
return formatDistanceToNow(date, { addSuffix: true })
|
||||
}
|
||||
|
||||
// Ultra-compact date format for tight spaces (e.g., compact view)
|
||||
export const formatDateCompact = (timestamp: number) => {
|
||||
const safe = typeof timestamp === 'number' && isFinite(timestamp) && timestamp > 0 ? timestamp : Math.floor(Date.now() / 1000)
|
||||
const date = new Date(safe * 1000)
|
||||
export const formatDateCompact = (timestamp: number | null | undefined) => {
|
||||
if (!timestamp || !isFinite(timestamp) || timestamp <= 0) return ''
|
||||
const date = new Date(timestamp * 1000)
|
||||
const now = new Date()
|
||||
|
||||
const seconds = differenceInSeconds(now, date)
|
||||
@@ -89,7 +89,11 @@ export const renderParsedContent = (parsedContent: ParsedContent) => {
|
||||
export const sortIndividualBookmarks = (items: IndividualBookmark[]) => {
|
||||
return items
|
||||
.slice()
|
||||
.sort((a, b) => (b.listUpdatedAt || 0) - (a.listUpdatedAt || 0))
|
||||
.sort((a, b) => {
|
||||
const aTs = a.listUpdatedAt ?? -Infinity
|
||||
const bTs = b.listUpdatedAt ?? -Infinity
|
||||
return bTs - aTs
|
||||
})
|
||||
}
|
||||
|
||||
export function groupIndividualBookmarks(items: IndividualBookmark[]) {
|
||||
@@ -124,10 +128,7 @@ export function hasContent(bookmark: IndividualBookmark): boolean {
|
||||
export function hasCreationDate(bookmark: IndividualBookmark): boolean {
|
||||
if (!bookmark.created_at) return false
|
||||
// If timestamp is missing or equals current time (within 1 second), consider it invalid
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const createdAt = Math.floor(bookmark.created_at)
|
||||
// If created_at is within 1 second of now, it's likely missing/placeholder
|
||||
return Math.abs(createdAt - now) > 1
|
||||
return true
|
||||
}
|
||||
|
||||
// Bookmark sets helpers (kind 30003)
|
||||
|
||||
@@ -51,7 +51,7 @@ export function deriveLinksFromBookmarks(bookmarks: Bookmark[]): ReadItem[] {
|
||||
summary,
|
||||
image,
|
||||
readingProgress: 0,
|
||||
readingTimestamp: bookmark.created_at
|
||||
readingTimestamp: bookmark.created_at ?? undefined
|
||||
}
|
||||
|
||||
linksMap.set(url, item)
|
||||
|
||||
@@ -49,7 +49,7 @@ export function deriveReadsFromBookmarks(bookmarks: Bookmark[]): ReadItem[] {
|
||||
source: 'bookmark',
|
||||
type: 'article',
|
||||
readingProgress: 0,
|
||||
readingTimestamp: bookmark.created_at,
|
||||
readingTimestamp: bookmark.created_at ?? undefined,
|
||||
title,
|
||||
summary,
|
||||
image,
|
||||
|
||||
Reference in New Issue
Block a user