mirror of
https://github.com/dergigi/boris.git
synced 2025-12-18 23:24:22 +01:00
refactor: keep Bookmarks.tsx under 210 lines by extracting logic
Extracted large functions into separate modules to follow DRY principles
and keep files manageable:
- Created useArticleLoader.ts hook (92 lines)
- Handles article loading from naddr
- Fetches article content and highlights
- Sets up article coordinate for refresh
- Created contentLoader.ts utility (44 lines)
- Handles both Nostr articles and web URLs
- Unified content loading logic
- Reusable across components
Result: Bookmarks.tsx reduced from 282 to 208 lines ✅
All files now under 210 line limit while maintaining functionality.
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { nip19 } from 'nostr-tools'
|
|
||||||
import { Hooks } from 'applesauce-react'
|
import { Hooks } from 'applesauce-react'
|
||||||
import { useEventStore } from 'applesauce-react/hooks'
|
import { useEventStore } from 'applesauce-react/hooks'
|
||||||
import { RelayPool } from 'applesauce-relay'
|
import { RelayPool } from 'applesauce-relay'
|
||||||
@@ -11,11 +10,12 @@ import { fetchBookmarks } from '../services/bookmarkService'
|
|||||||
import { fetchHighlights, fetchHighlightsForArticle } from '../services/highlightService'
|
import { fetchHighlights, fetchHighlightsForArticle } from '../services/highlightService'
|
||||||
import ContentPanel from './ContentPanel'
|
import ContentPanel from './ContentPanel'
|
||||||
import { HighlightsPanel } from './HighlightsPanel'
|
import { HighlightsPanel } from './HighlightsPanel'
|
||||||
import { fetchReadableContent, ReadableContent } from '../services/readerService'
|
import { ReadableContent } from '../services/readerService'
|
||||||
import { fetchArticleByNaddr } from '../services/articleService'
|
|
||||||
import Settings from './Settings'
|
import Settings from './Settings'
|
||||||
import Toast from './Toast'
|
import Toast from './Toast'
|
||||||
import { useSettings } from '../hooks/useSettings'
|
import { useSettings } from '../hooks/useSettings'
|
||||||
|
import { useArticleLoader } from '../hooks/useArticleLoader'
|
||||||
|
import { loadContent, BookmarkReference } from '../utils/contentLoader'
|
||||||
export type ViewMode = 'compact' | 'cards' | 'large'
|
export type ViewMode = 'compact' | 'cards' | 'large'
|
||||||
|
|
||||||
interface BookmarksProps {
|
interface BookmarksProps {
|
||||||
@@ -51,69 +51,20 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Load article if naddr is in URL
|
// Load article if naddr is in URL
|
||||||
useEffect(() => {
|
useArticleLoader({
|
||||||
if (!relayPool || !naddr) return
|
naddr,
|
||||||
|
relayPool,
|
||||||
const loadArticle = async () => {
|
setSelectedUrl,
|
||||||
setReaderLoading(true)
|
setReaderContent,
|
||||||
setReaderContent(undefined)
|
setReaderLoading,
|
||||||
setSelectedUrl(`nostr:${naddr}`) // Use naddr as the URL identifier
|
setIsCollapsed,
|
||||||
setIsCollapsed(true)
|
setIsHighlightsCollapsed,
|
||||||
setIsHighlightsCollapsed(false) // Show highlights for the article
|
setHighlights,
|
||||||
|
setHighlightsLoading,
|
||||||
try {
|
setCurrentArticleCoordinate,
|
||||||
const article = await fetchArticleByNaddr(relayPool, naddr)
|
setCurrentArticleEventId
|
||||||
setReaderContent({
|
|
||||||
title: article.title,
|
|
||||||
markdown: article.markdown,
|
|
||||||
image: article.image,
|
|
||||||
url: `nostr:${naddr}`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fetch highlights for this article using its address coordinate
|
|
||||||
// Extract the d-tag identifier from the article event
|
|
||||||
const dTag = article.event.tags.find(t => t[0] === 'd')?.[1] || ''
|
|
||||||
const articleCoordinate = `${article.event.kind}:${article.author}:${dTag}`
|
|
||||||
|
|
||||||
// Store article info for refresh functionality
|
|
||||||
setCurrentArticleCoordinate(articleCoordinate)
|
|
||||||
setCurrentArticleEventId(article.event.id)
|
|
||||||
|
|
||||||
console.log('📰 Article details:')
|
|
||||||
console.log(' - Event ID:', article.event.id)
|
|
||||||
console.log(' - Author:', article.author)
|
|
||||||
console.log(' - Kind:', article.event.kind)
|
|
||||||
console.log(' - D-tag:', dTag)
|
|
||||||
console.log(' - Coordinate:', articleCoordinate)
|
|
||||||
console.log(' - Title:', article.title)
|
|
||||||
|
|
||||||
try {
|
|
||||||
setHighlightsLoading(true)
|
|
||||||
// Pass both the article coordinate and event ID for comprehensive highlight fetching
|
|
||||||
const fetchedHighlights = await fetchHighlightsForArticle(relayPool, articleCoordinate, article.event.id)
|
|
||||||
console.log(`📌 Found ${fetchedHighlights.length} highlights for article ${articleCoordinate}`)
|
|
||||||
setHighlights(fetchedHighlights)
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to fetch highlights:', err)
|
|
||||||
} finally {
|
|
||||||
setHighlightsLoading(false)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to load article:', err)
|
|
||||||
setReaderContent({
|
|
||||||
title: 'Error Loading Article',
|
|
||||||
html: `<p>Failed to load article: ${err instanceof Error ? err.message : 'Unknown error'}</p>`,
|
|
||||||
url: `nostr:${naddr}`
|
|
||||||
})
|
|
||||||
setReaderLoading(false)
|
|
||||||
} finally {
|
|
||||||
setReaderLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadArticle()
|
|
||||||
}, [naddr, relayPool])
|
|
||||||
|
|
||||||
// Load initial data on login
|
// Load initial data on login
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!relayPool || !activeAccount) return
|
if (!relayPool || !activeAccount) return
|
||||||
@@ -162,7 +113,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSelectUrl = async (url: string, bookmark?: { id: string; kind: number; tags: string[][]; pubkey: string }) => {
|
const handleSelectUrl = async (url: string, bookmark?: BookmarkReference) => {
|
||||||
if (!relayPool) return
|
if (!relayPool) return
|
||||||
|
|
||||||
setSelectedUrl(url)
|
setSelectedUrl(url)
|
||||||
@@ -172,33 +123,8 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
|
|||||||
if (settings.collapseOnArticleOpen !== false) setIsCollapsed(true)
|
if (settings.collapseOnArticleOpen !== false) setIsCollapsed(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if this is a kind:30023 article
|
const content = await loadContent(url, relayPool, bookmark)
|
||||||
if (bookmark && bookmark.kind === 30023) {
|
|
||||||
// For articles, construct an naddr and fetch using article service
|
|
||||||
const dTag = bookmark.tags.find(t => t[0] === 'd')?.[1] || ''
|
|
||||||
|
|
||||||
if (dTag !== undefined && bookmark.pubkey) {
|
|
||||||
const pointer = {
|
|
||||||
identifier: dTag,
|
|
||||||
kind: 30023,
|
|
||||||
pubkey: bookmark.pubkey,
|
|
||||||
}
|
|
||||||
const naddr = nip19.naddrEncode(pointer)
|
|
||||||
const article = await fetchArticleByNaddr(relayPool, naddr)
|
|
||||||
setReaderContent({
|
|
||||||
title: article.title,
|
|
||||||
markdown: article.markdown,
|
|
||||||
image: article.image,
|
|
||||||
url: `nostr:${naddr}`
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid article reference - missing d tag or pubkey')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For regular URLs, fetch readable content
|
|
||||||
const content = await fetchReadableContent(url)
|
|
||||||
setReaderContent(content)
|
setReaderContent(content)
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to fetch content:', err)
|
console.warn('Failed to fetch content:', err)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
92
src/hooks/useArticleLoader.ts
Normal file
92
src/hooks/useArticleLoader.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { RelayPool } from 'applesauce-relay'
|
||||||
|
import { fetchArticleByNaddr } from '../services/articleService'
|
||||||
|
import { fetchHighlightsForArticle } from '../services/highlightService'
|
||||||
|
import { ReadableContent } from '../services/readerService'
|
||||||
|
import { Highlight } from '../types/highlights'
|
||||||
|
|
||||||
|
interface UseArticleLoaderProps {
|
||||||
|
naddr: string | undefined
|
||||||
|
relayPool: RelayPool | null
|
||||||
|
setSelectedUrl: (url: string) => void
|
||||||
|
setReaderContent: (content: ReadableContent | undefined) => void
|
||||||
|
setReaderLoading: (loading: boolean) => void
|
||||||
|
setIsCollapsed: (collapsed: boolean) => void
|
||||||
|
setIsHighlightsCollapsed: (collapsed: boolean) => void
|
||||||
|
setHighlights: (highlights: Highlight[]) => void
|
||||||
|
setHighlightsLoading: (loading: boolean) => void
|
||||||
|
setCurrentArticleCoordinate: (coord: string | undefined) => void
|
||||||
|
setCurrentArticleEventId: (id: string | undefined) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useArticleLoader({
|
||||||
|
naddr,
|
||||||
|
relayPool,
|
||||||
|
setSelectedUrl,
|
||||||
|
setReaderContent,
|
||||||
|
setReaderLoading,
|
||||||
|
setIsCollapsed,
|
||||||
|
setIsHighlightsCollapsed,
|
||||||
|
setHighlights,
|
||||||
|
setHighlightsLoading,
|
||||||
|
setCurrentArticleCoordinate,
|
||||||
|
setCurrentArticleEventId
|
||||||
|
}: UseArticleLoaderProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!relayPool || !naddr) return
|
||||||
|
|
||||||
|
const loadArticle = async () => {
|
||||||
|
setReaderLoading(true)
|
||||||
|
setReaderContent(undefined)
|
||||||
|
setSelectedUrl(`nostr:${naddr}`)
|
||||||
|
setIsCollapsed(true)
|
||||||
|
setIsHighlightsCollapsed(false)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const article = await fetchArticleByNaddr(relayPool, naddr)
|
||||||
|
setReaderContent({
|
||||||
|
title: article.title,
|
||||||
|
markdown: article.markdown,
|
||||||
|
image: article.image,
|
||||||
|
url: `nostr:${naddr}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const dTag = article.event.tags.find(t => t[0] === 'd')?.[1] || ''
|
||||||
|
const articleCoordinate = `${article.event.kind}:${article.author}:${dTag}`
|
||||||
|
|
||||||
|
setCurrentArticleCoordinate(articleCoordinate)
|
||||||
|
setCurrentArticleEventId(article.event.id)
|
||||||
|
|
||||||
|
console.log('📰 Article loaded:', article.title)
|
||||||
|
console.log('📍 Coordinate:', articleCoordinate)
|
||||||
|
|
||||||
|
try {
|
||||||
|
setHighlightsLoading(true)
|
||||||
|
const fetchedHighlights = await fetchHighlightsForArticle(
|
||||||
|
relayPool,
|
||||||
|
articleCoordinate,
|
||||||
|
article.event.id
|
||||||
|
)
|
||||||
|
console.log(`📌 Found ${fetchedHighlights.length} highlights`)
|
||||||
|
setHighlights(fetchedHighlights)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch highlights:', err)
|
||||||
|
} finally {
|
||||||
|
setHighlightsLoading(false)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load article:', err)
|
||||||
|
setReaderContent({
|
||||||
|
title: 'Error Loading Article',
|
||||||
|
html: `<p>Failed to load article: ${err instanceof Error ? err.message : 'Unknown error'}</p>`,
|
||||||
|
url: `nostr:${naddr}`
|
||||||
|
})
|
||||||
|
setReaderLoading(false)
|
||||||
|
} finally {
|
||||||
|
setReaderLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadArticle()
|
||||||
|
}, [naddr, relayPool])
|
||||||
|
}
|
||||||
44
src/utils/contentLoader.ts
Normal file
44
src/utils/contentLoader.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { nip19 } from 'nostr-tools'
|
||||||
|
import { RelayPool } from 'applesauce-relay'
|
||||||
|
import { fetchArticleByNaddr } from '../services/articleService'
|
||||||
|
import { fetchReadableContent, ReadableContent } from '../services/readerService'
|
||||||
|
|
||||||
|
export interface BookmarkReference {
|
||||||
|
id: string
|
||||||
|
kind: number
|
||||||
|
tags: string[][]
|
||||||
|
pubkey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadContent(
|
||||||
|
url: string,
|
||||||
|
relayPool: RelayPool,
|
||||||
|
bookmark?: BookmarkReference
|
||||||
|
): Promise<ReadableContent> {
|
||||||
|
// Check if this is a kind:30023 article
|
||||||
|
if (bookmark && bookmark.kind === 30023) {
|
||||||
|
const dTag = bookmark.tags.find(t => t[0] === 'd')?.[1] || ''
|
||||||
|
|
||||||
|
if (dTag !== undefined && bookmark.pubkey) {
|
||||||
|
const pointer = {
|
||||||
|
identifier: dTag,
|
||||||
|
kind: 30023,
|
||||||
|
pubkey: bookmark.pubkey,
|
||||||
|
}
|
||||||
|
const naddr = nip19.naddrEncode(pointer)
|
||||||
|
const article = await fetchArticleByNaddr(relayPool, naddr)
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: article.title,
|
||||||
|
markdown: article.markdown,
|
||||||
|
image: article.image,
|
||||||
|
url: `nostr:${naddr}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid article reference - missing d tag or pubkey')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For regular URLs, fetch readable content
|
||||||
|
return await fetchReadableContent(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user