mirror of
https://github.com/dergigi/boris.git
synced 2026-01-06 08:24:27 +01:00
feat: show reading progress in compact and card view bookmarks
- Add readingProgress prop to BookmarkItem component - Display reading progress in CompactView with 2px indicator - Display reading progress in CardView with 3px indicator - Progress color matches main app: blue (reading), green (completed), neutral (started) - Add getBookmarkReadingProgress helper in Me.tsx - Show progress only for kind:30023 articles with progress > 0 - Reading progress now visible across all bookmark view modes
This commit is contained in:
@@ -19,9 +19,10 @@ interface BookmarkItemProps {
|
||||
index: number
|
||||
onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][]; pubkey: string }) => void
|
||||
viewMode?: ViewMode
|
||||
readingProgress?: number
|
||||
}
|
||||
|
||||
export const BookmarkItem: React.FC<BookmarkItemProps> = ({ bookmark, index, onSelectUrl, viewMode = 'cards' }) => {
|
||||
export const BookmarkItem: React.FC<BookmarkItemProps> = ({ bookmark, index, onSelectUrl, viewMode = 'cards', readingProgress }) => {
|
||||
const [ogImage, setOgImage] = useState<string | null>(null)
|
||||
|
||||
const short = (v: string) => `${v.slice(0, 8)}...${v.slice(-8)}`
|
||||
@@ -139,7 +140,8 @@ export const BookmarkItem: React.FC<BookmarkItemProps> = ({ bookmark, index, onS
|
||||
handleReadNow,
|
||||
articleImage,
|
||||
articleSummary,
|
||||
contentTypeIcon: getContentTypeIcon()
|
||||
contentTypeIcon: getContentTypeIcon(),
|
||||
readingProgress
|
||||
}
|
||||
|
||||
if (viewMode === 'compact') {
|
||||
|
||||
@@ -24,6 +24,7 @@ interface CardViewProps {
|
||||
articleImage?: string
|
||||
articleSummary?: string
|
||||
contentTypeIcon: IconDefinition
|
||||
readingProgress?: number
|
||||
}
|
||||
|
||||
export const CardView: React.FC<CardViewProps> = ({
|
||||
@@ -38,7 +39,8 @@ export const CardView: React.FC<CardViewProps> = ({
|
||||
handleReadNow,
|
||||
articleImage,
|
||||
articleSummary,
|
||||
contentTypeIcon
|
||||
contentTypeIcon,
|
||||
readingProgress
|
||||
}) => {
|
||||
const firstUrl = hasUrls ? extractedUrls[0] : null
|
||||
const firstUrlClassificationType = firstUrl ? classifyUrl(firstUrl)?.type : null
|
||||
@@ -52,6 +54,14 @@ export const CardView: React.FC<CardViewProps> = ({
|
||||
const shouldTruncate = !expanded && contentLength > 210
|
||||
const isArticle = bookmark.kind === 30023
|
||||
|
||||
// Calculate progress color (matching BlogPostCard logic)
|
||||
let progressColor = '#6366f1' // Default blue (reading)
|
||||
if (readingProgress && readingProgress >= 0.95) {
|
||||
progressColor = '#10b981' // Green (completed)
|
||||
} else if (readingProgress && readingProgress > 0 && readingProgress <= 0.10) {
|
||||
progressColor = 'var(--color-text)' // Neutral text color (started)
|
||||
}
|
||||
|
||||
// Determine which image to use (article image, instant preview, or OG image)
|
||||
const previewImage = articleImage || instantPreview || ogImage
|
||||
const cachedImage = useImageCache(previewImage || undefined)
|
||||
@@ -163,6 +173,28 @@ export const CardView: React.FC<CardViewProps> = ({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Reading progress indicator for articles */}
|
||||
{isArticle && readingProgress !== undefined && readingProgress > 0 && (
|
||||
<div
|
||||
style={{
|
||||
height: '3px',
|
||||
width: '100%',
|
||||
background: 'var(--color-border)',
|
||||
overflow: 'hidden',
|
||||
marginTop: '0.75rem'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: `${Math.round(readingProgress * 100)}%`,
|
||||
background: progressColor,
|
||||
transition: 'width 0.3s ease, background 0.3s ease'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bookmark-footer">
|
||||
<div className="bookmark-meta-minimal">
|
||||
<Link
|
||||
|
||||
@@ -13,6 +13,7 @@ interface CompactViewProps {
|
||||
onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][]; pubkey: string }) => void
|
||||
articleSummary?: string
|
||||
contentTypeIcon: IconDefinition
|
||||
readingProgress?: number
|
||||
}
|
||||
|
||||
export const CompactView: React.FC<CompactViewProps> = ({
|
||||
@@ -22,12 +23,21 @@ export const CompactView: React.FC<CompactViewProps> = ({
|
||||
extractedUrls,
|
||||
onSelectUrl,
|
||||
articleSummary,
|
||||
contentTypeIcon
|
||||
contentTypeIcon,
|
||||
readingProgress
|
||||
}) => {
|
||||
const isArticle = bookmark.kind === 30023
|
||||
const isWebBookmark = bookmark.kind === 39701
|
||||
const isClickable = hasUrls || isArticle || isWebBookmark
|
||||
|
||||
// Calculate progress color (matching BlogPostCard logic)
|
||||
let progressColor = '#6366f1' // Default blue (reading)
|
||||
if (readingProgress && readingProgress >= 0.95) {
|
||||
progressColor = '#10b981' // Green (completed)
|
||||
} else if (readingProgress && readingProgress > 0 && readingProgress <= 0.10) {
|
||||
progressColor = 'var(--color-text)' // Neutral text color (started)
|
||||
}
|
||||
|
||||
const handleCompactClick = () => {
|
||||
if (!onSelectUrl) return
|
||||
|
||||
@@ -62,6 +72,28 @@ export const CompactView: React.FC<CompactViewProps> = ({
|
||||
<span className="bookmark-date-compact">{formatDateCompact(bookmark.created_at)}</span>
|
||||
{/* CTA removed */}
|
||||
</div>
|
||||
|
||||
{/* Reading progress indicator for articles */}
|
||||
{isArticle && readingProgress !== undefined && readingProgress > 0 && (
|
||||
<div
|
||||
style={{
|
||||
height: '2px',
|
||||
width: '100%',
|
||||
background: 'var(--color-border)',
|
||||
overflow: 'hidden',
|
||||
margin: '0'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: `${Math.round(readingProgress * 100)}%`,
|
||||
background: progressColor,
|
||||
transition: 'width 0.3s ease, background 0.3s ease'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -471,6 +471,25 @@ const Me: React.FC<MeProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get reading progress for a bookmark
|
||||
const getBookmarkReadingProgress = (bookmark: IndividualBookmark): number | undefined => {
|
||||
if (bookmark.kind === 30023) {
|
||||
const dTag = bookmark.tags.find(t => t[0] === 'd')?.[1]
|
||||
if (!dTag) return undefined
|
||||
try {
|
||||
const naddr = nip19.naddrEncode({
|
||||
kind: 30023,
|
||||
pubkey: bookmark.pubkey,
|
||||
identifier: dTag
|
||||
})
|
||||
return readingProgressMap.get(naddr)
|
||||
} catch (err) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Merge and flatten all individual bookmarks
|
||||
const allIndividualBookmarks = bookmarks.flatMap(b => b.individualBookmarks || [])
|
||||
.filter(hasContent)
|
||||
@@ -567,6 +586,7 @@ const Me: React.FC<MeProps> = ({
|
||||
index={index}
|
||||
viewMode="cards"
|
||||
onSelectUrl={handleSelectUrl}
|
||||
readingProgress={getBookmarkReadingProgress(individualBookmark)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user