mirror of
https://github.com/dergigi/boris.git
synced 2025-12-20 16:14:20 +01:00
refactor: make /me Reading List use same components as bookmark sidebar
- Reuse BookmarkItem component for rendering individual bookmarks - Apply same filtering logic (hasContentOrUrl) as BookmarkList - Add view mode controls (compact/cards/large) matching sidebar - Count shows individual bookmarks not bookmark lists - Keeps code DRY by reusing existing components and logic
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faSpinner, faExclamationCircle, faHighlighter, faBookmark } from '@fortawesome/free-solid-svg-icons'
|
import { faSpinner, faExclamationCircle, faHighlighter, faBookmark, faList, faThLarge, faImage } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Hooks } from 'applesauce-react'
|
import { Hooks } from 'applesauce-react'
|
||||||
import { RelayPool } from 'applesauce-relay'
|
import { RelayPool } from 'applesauce-relay'
|
||||||
import { nip19 } from 'nostr-tools'
|
import { nip19 } from 'nostr-tools'
|
||||||
@@ -10,9 +10,13 @@ import { fetchHighlights } from '../services/highlightService'
|
|||||||
import { fetchBookmarks } from '../services/bookmarkService'
|
import { fetchBookmarks } from '../services/bookmarkService'
|
||||||
import { fetchReadArticlesWithData } from '../services/libraryService'
|
import { fetchReadArticlesWithData } from '../services/libraryService'
|
||||||
import { BlogPostPreview } from '../services/exploreService'
|
import { BlogPostPreview } from '../services/exploreService'
|
||||||
import { Bookmark } from '../types/bookmarks'
|
import { Bookmark, IndividualBookmark } from '../types/bookmarks'
|
||||||
import AuthorCard from './AuthorCard'
|
import AuthorCard from './AuthorCard'
|
||||||
import BlogPostCard from './BlogPostCard'
|
import BlogPostCard from './BlogPostCard'
|
||||||
|
import { BookmarkItem } from './BookmarkItem'
|
||||||
|
import IconButton from './IconButton'
|
||||||
|
import { ViewMode } from './Bookmarks'
|
||||||
|
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
|
||||||
import { faBooks } from '../icons/customIcons'
|
import { faBooks } from '../icons/customIcons'
|
||||||
|
|
||||||
interface MeProps {
|
interface MeProps {
|
||||||
@@ -29,6 +33,7 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
const [readArticles, setReadArticles] = useState<BlogPostPreview[]>([])
|
const [readArticles, setReadArticles] = useState<BlogPostPreview[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [viewMode, setViewMode] = useState<ViewMode>('cards')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -83,6 +88,28 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
return `/a/${naddr}`
|
return `/a/${naddr}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to check if a bookmark has either content or a URL (same logic as BookmarkList)
|
||||||
|
const hasContentOrUrl = (ib: IndividualBookmark) => {
|
||||||
|
const hasContent = ib.content && ib.content.trim().length > 0
|
||||||
|
|
||||||
|
let hasUrl = false
|
||||||
|
if (ib.kind === 39701) {
|
||||||
|
const dTag = ib.tags?.find((t: string[]) => t[0] === 'd')?.[1]
|
||||||
|
hasUrl = !!dTag && dTag.trim().length > 0
|
||||||
|
} else {
|
||||||
|
const urls = extractUrlsFromContent(ib.content || '')
|
||||||
|
hasUrl = urls.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ib.kind === 30023) return true
|
||||||
|
return hasContent || hasUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge and flatten all individual bookmarks (same logic as BookmarkList)
|
||||||
|
const allIndividualBookmarks = bookmarks.flatMap(b => b.individualBookmarks || [])
|
||||||
|
.filter(hasContentOrUrl)
|
||||||
|
.sort((a, b) => ((b.added_at || 0) - (a.added_at || 0)) || ((b.created_at || 0) - (a.created_at || 0)))
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="explore-container">
|
<div className="explore-container">
|
||||||
@@ -125,21 +152,53 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
case 'reading-list':
|
case 'reading-list':
|
||||||
return bookmarks.length === 0 ? (
|
return allIndividualBookmarks.length === 0 ? (
|
||||||
<div className="explore-error">
|
<div className="explore-error">
|
||||||
<p>No bookmarks yet. Bookmark articles to see them here!</p>
|
<p>No bookmarks yet. Bookmark articles to see them here!</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bookmarks-list">
|
<div className="bookmarks-list">
|
||||||
{bookmarks.map((bookmark) => (
|
<div className={`bookmarks-grid bookmarks-${viewMode}`}>
|
||||||
<div key={bookmark.id} className="bookmark-item">
|
{allIndividualBookmarks.map((individualBookmark, index) => (
|
||||||
<a href={bookmark.url} target="_blank" rel="noopener noreferrer">
|
<BookmarkItem
|
||||||
<h3>{bookmark.title || 'Untitled'}</h3>
|
key={`${individualBookmark.id}-${index}`}
|
||||||
{bookmark.content && <p>{bookmark.content.slice(0, 150)}...</p>}
|
bookmark={individualBookmark}
|
||||||
</a>
|
index={index}
|
||||||
</div>
|
viewMode={viewMode}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="view-mode-controls" style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '0.5rem',
|
||||||
|
padding: '1rem',
|
||||||
|
marginTop: '1rem',
|
||||||
|
borderTop: '1px solid var(--border-color)'
|
||||||
|
}}>
|
||||||
|
<IconButton
|
||||||
|
icon={faList}
|
||||||
|
onClick={() => setViewMode('compact')}
|
||||||
|
title="Compact list view"
|
||||||
|
ariaLabel="Compact list view"
|
||||||
|
variant={viewMode === 'compact' ? 'primary' : 'ghost'}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon={faThLarge}
|
||||||
|
onClick={() => setViewMode('cards')}
|
||||||
|
title="Cards view"
|
||||||
|
ariaLabel="Cards view"
|
||||||
|
variant={viewMode === 'cards' ? 'primary' : 'ghost'}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon={faImage}
|
||||||
|
onClick={() => setViewMode('large')}
|
||||||
|
title="Large preview view"
|
||||||
|
ariaLabel="Large preview view"
|
||||||
|
variant={viewMode === 'large' ? 'primary' : 'ghost'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
case 'archive':
|
case 'archive':
|
||||||
@@ -184,7 +243,7 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
onClick={() => setActiveTab('reading-list')}
|
onClick={() => setActiveTab('reading-list')}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faBookmark} />
|
<FontAwesomeIcon icon={faBookmark} />
|
||||||
Reading List ({bookmarks.length})
|
Reading List ({allIndividualBookmarks.length})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`me-tab ${activeTab === 'archive' ? 'active' : ''}`}
|
className={`me-tab ${activeTab === 'archive' ? 'active' : ''}`}
|
||||||
|
|||||||
Reference in New Issue
Block a user