mirror of
https://github.com/dergigi/boris.git
synced 2025-12-18 15:14:20 +01:00
feat: add caching to /me page for faster loading
- Create meCache service to store highlights, bookmarks, and read articles - Seed Me component from cache on load to avoid empty flash - Show small spinner while refreshing if cached data is displayed - Update cache when highlights are deleted - Only show full loading screen if no cached data is available - Improves perceived performance similar to /explore page
This commit is contained in:
@@ -17,6 +17,7 @@ import { BookmarkItem } from './BookmarkItem'
|
||||
import IconButton from './IconButton'
|
||||
import { ViewMode } from './Bookmarks'
|
||||
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
|
||||
import { getCachedMeData, setCachedMeData, updateCachedHighlights } from '../services/meCache'
|
||||
import { faBooks } from '../icons/customIcons'
|
||||
|
||||
interface MeProps {
|
||||
@@ -47,6 +48,14 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
// Seed from cache if available to avoid empty flash
|
||||
const cached = getCachedMeData(activeAccount.pubkey)
|
||||
if (cached) {
|
||||
setHighlights(cached.highlights)
|
||||
setBookmarks(cached.bookmarks)
|
||||
setReadArticles(cached.readArticles)
|
||||
}
|
||||
|
||||
// Fetch highlights and read articles
|
||||
const [userHighlights, userReadArticles] = await Promise.all([
|
||||
fetchHighlights(relayPool, activeAccount.pubkey),
|
||||
@@ -57,12 +66,19 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
||||
setReadArticles(userReadArticles)
|
||||
|
||||
// Fetch bookmarks using callback pattern
|
||||
let fetchedBookmarks: Bookmark[] = []
|
||||
try {
|
||||
await fetchBookmarks(relayPool, activeAccount, setBookmarks)
|
||||
await fetchBookmarks(relayPool, activeAccount, (newBookmarks) => {
|
||||
fetchedBookmarks = newBookmarks
|
||||
setBookmarks(newBookmarks)
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn('Failed to load bookmarks:', err)
|
||||
setBookmarks([])
|
||||
}
|
||||
|
||||
// Update cache with all fetched data
|
||||
setCachedMeData(activeAccount.pubkey, userHighlights, fetchedBookmarks, userReadArticles)
|
||||
} catch (err) {
|
||||
console.error('Failed to load data:', err)
|
||||
setError('Failed to load data. Please try again.')
|
||||
@@ -75,7 +91,14 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
||||
}, [relayPool, activeAccount])
|
||||
|
||||
const handleHighlightDelete = (highlightId: string) => {
|
||||
setHighlights(prev => prev.filter(h => h.id !== highlightId))
|
||||
setHighlights(prev => {
|
||||
const updated = prev.filter(h => h.id !== highlightId)
|
||||
// Update cache when highlight is deleted
|
||||
if (activeAccount) {
|
||||
updateCachedHighlights(activeAccount.pubkey, updated)
|
||||
}
|
||||
return updated
|
||||
})
|
||||
}
|
||||
|
||||
const getPostUrl = (post: BlogPostPreview) => {
|
||||
@@ -110,7 +133,10 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
||||
.filter(hasContentOrUrl)
|
||||
.sort((a, b) => ((b.added_at || 0) - (a.added_at || 0)) || ((b.created_at || 0) - (a.created_at || 0)))
|
||||
|
||||
if (loading) {
|
||||
// Only show full loading screen if we don't have any data yet
|
||||
const hasData = highlights.length > 0 || bookmarks.length > 0 || readArticles.length > 0
|
||||
|
||||
if (loading && !hasData) {
|
||||
return (
|
||||
<div className="explore-container">
|
||||
<div className="explore-loading">
|
||||
@@ -228,6 +254,12 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
||||
<div className="explore-header">
|
||||
{activeAccount && <AuthorCard authorPubkey={activeAccount.pubkey} />}
|
||||
|
||||
{loading && hasData && (
|
||||
<div className="explore-loading" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', padding: '0.5rem 0' }}>
|
||||
<FontAwesomeIcon icon={faSpinner} spin />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="me-tabs">
|
||||
<button
|
||||
className={`me-tab ${activeTab === 'highlights' ? 'active' : ''}`}
|
||||
|
||||
54
src/services/meCache.ts
Normal file
54
src/services/meCache.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Highlight } from '../types/highlights'
|
||||
import { Bookmark } from '../types/bookmarks'
|
||||
import { BlogPostPreview } from './exploreService'
|
||||
|
||||
export interface MeCache {
|
||||
highlights: Highlight[]
|
||||
bookmarks: Bookmark[]
|
||||
readArticles: BlogPostPreview[]
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
const meCache = new Map<string, MeCache>() // key: pubkey
|
||||
|
||||
export function getCachedMeData(pubkey: string): MeCache | null {
|
||||
const entry = meCache.get(pubkey)
|
||||
if (!entry) return null
|
||||
return entry
|
||||
}
|
||||
|
||||
export function setCachedMeData(
|
||||
pubkey: string,
|
||||
highlights: Highlight[],
|
||||
bookmarks: Bookmark[],
|
||||
readArticles: BlogPostPreview[]
|
||||
): void {
|
||||
meCache.set(pubkey, {
|
||||
highlights,
|
||||
bookmarks,
|
||||
readArticles,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
export function updateCachedHighlights(pubkey: string, highlights: Highlight[]): void {
|
||||
const existing = meCache.get(pubkey)
|
||||
if (existing) {
|
||||
meCache.set(pubkey, { ...existing, highlights, timestamp: Date.now() })
|
||||
}
|
||||
}
|
||||
|
||||
export function updateCachedBookmarks(pubkey: string, bookmarks: Bookmark[]): void {
|
||||
const existing = meCache.get(pubkey)
|
||||
if (existing) {
|
||||
meCache.set(pubkey, { ...existing, bookmarks, timestamp: Date.now() })
|
||||
}
|
||||
}
|
||||
|
||||
export function updateCachedReadArticles(pubkey: string, readArticles: BlogPostPreview[]): void {
|
||||
const existing = meCache.get(pubkey)
|
||||
if (existing) {
|
||||
meCache.set(pubkey, { ...existing, readArticles, timestamp: Date.now() })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user