mirror of
https://github.com/dergigi/boris.git
synced 2025-12-21 16:44:19 +01:00
fix(lint): resolve all linting and type errors
- Remove unused imports (useRef, faExclamationCircle, getProfileUrl, Observable, UserSettings) - Remove unused error state and setError calls in Explore and Me components - Remove unused 'events' variable from exploreService and nostrverseService - Remove unused '_relays' parameter from saveSettings - Remove unused '_settings' parameter from publishEvent - Update all callers of publishEvent and saveSettings to match new signatures - Add eslint-disable comment for intentional dependency omission in Explore - Update BookmarkList to use new pull-to-refresh library and RefreshIndicator - All type checks and linting now pass
This commit is contained in:
@@ -10,8 +10,8 @@ import IconButton from './IconButton'
|
|||||||
import { ViewMode } from './Bookmarks'
|
import { ViewMode } from './Bookmarks'
|
||||||
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
|
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
|
||||||
import { UserSettings } from '../services/settingsService'
|
import { UserSettings } from '../services/settingsService'
|
||||||
import { usePullToRefresh } from '../hooks/usePullToRefresh'
|
import { usePullToRefresh } from 'use-pull-to-refresh'
|
||||||
import PullToRefreshIndicator from './PullToRefreshIndicator'
|
import RefreshIndicator from './RefreshIndicator'
|
||||||
import { BookmarkSkeleton } from './Skeletons'
|
import { BookmarkSkeleton } from './Skeletons'
|
||||||
|
|
||||||
interface BookmarkListProps {
|
interface BookmarkListProps {
|
||||||
@@ -54,14 +54,15 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
|
|||||||
const bookmarksListRef = useRef<HTMLDivElement>(null)
|
const bookmarksListRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
// Pull-to-refresh for bookmarks
|
// Pull-to-refresh for bookmarks
|
||||||
const pullToRefreshState = usePullToRefresh(bookmarksListRef, {
|
const { isRefreshing: isPulling, pullPosition } = usePullToRefresh({
|
||||||
onRefresh: () => {
|
onRefresh: () => {
|
||||||
if (onRefresh) {
|
if (onRefresh) {
|
||||||
onRefresh()
|
onRefresh()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isRefreshing: isRefreshing || false,
|
maximumPullLength: 240,
|
||||||
disabled: !onRefresh
|
refreshThreshold: 80,
|
||||||
|
isDisabled: !onRefresh
|
||||||
})
|
})
|
||||||
|
|
||||||
// Helper to check if a bookmark has either content or a URL
|
// Helper to check if a bookmark has either content or a URL
|
||||||
@@ -146,13 +147,11 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
ref={bookmarksListRef}
|
ref={bookmarksListRef}
|
||||||
className={`bookmarks-list pull-to-refresh-container ${pullToRefreshState.isPulling ? 'is-pulling' : ''}`}
|
className="bookmarks-list"
|
||||||
>
|
>
|
||||||
<PullToRefreshIndicator
|
<RefreshIndicator
|
||||||
isPulling={pullToRefreshState.isPulling}
|
isRefreshing={isPulling || isRefreshing || false}
|
||||||
pullDistance={pullToRefreshState.pullDistance}
|
pullPosition={pullPosition}
|
||||||
canRefresh={pullToRefreshState.canRefresh}
|
|
||||||
isRefreshing={isRefreshing || false}
|
|
||||||
/>
|
/>
|
||||||
<div className={`bookmarks-grid bookmarks-${viewMode}`}>
|
<div className={`bookmarks-grid bookmarks-${viewMode}`}>
|
||||||
{allIndividualBookmarks.map((individualBookmark, index) =>
|
{allIndividualBookmarks.map((individualBookmark, index) =>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef, useMemo } from 'react'
|
import React, { useState, useEffect, useMemo } from 'react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faExclamationCircle, faNewspaper, faPenToSquare, faHighlighter, faUser, faUserGroup, faNetworkWired } from '@fortawesome/free-solid-svg-icons'
|
import { faNewspaper, faPenToSquare, faHighlighter, faUser, faUserGroup, faNetworkWired } from '@fortawesome/free-solid-svg-icons'
|
||||||
import IconButton from './IconButton'
|
import IconButton from './IconButton'
|
||||||
import { BlogPostSkeleton, HighlightSkeleton } from './Skeletons'
|
import { BlogPostSkeleton, HighlightSkeleton } from './Skeletons'
|
||||||
import { Hooks } from 'applesauce-react'
|
import { Hooks } from 'applesauce-react'
|
||||||
@@ -40,7 +40,6 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
const [highlights, setHighlights] = useState<Highlight[]>([])
|
const [highlights, setHighlights] = useState<Highlight[]>([])
|
||||||
const [followedPubkeys, setFollowedPubkeys] = useState<Set<string>>(new Set())
|
const [followedPubkeys, setFollowedPubkeys] = useState<Set<string>>(new Set())
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
|
||||||
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
||||||
|
|
||||||
// Visibility filters (defaults from settings)
|
// Visibility filters (defaults from settings)
|
||||||
@@ -60,7 +59,6 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
if (!activeAccount) {
|
if (!activeAccount) {
|
||||||
setError('Please log in to explore content from your friends')
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -68,7 +66,6 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
try {
|
try {
|
||||||
// show spinner but keep existing data
|
// show spinner but keep existing data
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
|
||||||
|
|
||||||
// Seed from in-memory cache if available to avoid empty flash
|
// Seed from in-memory cache if available to avoid empty flash
|
||||||
const cachedPosts = getCachedPosts(activeAccount.pubkey)
|
const cachedPosts = getCachedPosts(activeAccount.pubkey)
|
||||||
@@ -150,15 +147,8 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (contacts.size === 0) {
|
// Always proceed to load nostrverse content even if no contacts
|
||||||
// If we already have any cached or previously shown data, do not block the UI.
|
// (removed blocking error for empty contacts)
|
||||||
const hasAnyData = (blogPosts.length > 0) || (highlights.length > 0)
|
|
||||||
if (!hasAnyData) {
|
|
||||||
// No friends and no cached content: set a soft hint, but still proceed to load nostrverse.
|
|
||||||
setError(null)
|
|
||||||
}
|
|
||||||
// Continue without returning: still fetch nostrverse content below.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store final followed pubkeys
|
// Store final followed pubkeys
|
||||||
setFollowedPubkeys(contacts)
|
setFollowedPubkeys(contacts)
|
||||||
@@ -205,12 +195,7 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contacts.size === 0 && uniquePosts.length === 0 && uniqueHighlights.length === 0) {
|
// No blocking errors - let empty states handle messaging
|
||||||
setError('You are not following anyone yet. Follow some people to see their content!')
|
|
||||||
} else if (uniquePosts.length === 0 && uniqueHighlights.length === 0) {
|
|
||||||
setError('No content found yet')
|
|
||||||
}
|
|
||||||
|
|
||||||
setBlogPosts(uniquePosts)
|
setBlogPosts(uniquePosts)
|
||||||
setCachedPosts(activeAccount.pubkey, uniquePosts)
|
setCachedPosts(activeAccount.pubkey, uniquePosts)
|
||||||
|
|
||||||
@@ -218,13 +203,15 @@ const Explore: React.FC<ExploreProps> = ({ relayPool, eventStore, settings, acti
|
|||||||
setCachedHighlights(activeAccount.pubkey, uniqueHighlights)
|
setCachedHighlights(activeAccount.pubkey, uniqueHighlights)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load data:', err)
|
console.error('Failed to load data:', err)
|
||||||
setError('Failed to load content. Please try again.')
|
// No blocking error - user can pull-to-refresh
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData()
|
loadData()
|
||||||
|
// Note: intentionally not including blogPosts/highlights length to avoid re-fetch loops
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [relayPool, activeAccount, refreshTrigger, eventStore, settings])
|
}, [relayPool, activeAccount, refreshTrigger, eventStore, settings])
|
||||||
|
|
||||||
// Pull-to-refresh
|
// Pull-to-refresh
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faHighlighter } from '@fortawesome/free-solid-svg-icons'
|
import { faHighlighter } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Highlight } from '../types/highlights'
|
import { Highlight } from '../types/highlights'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } 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, faList, faThLarge, faImage, faPenToSquare } from '@fortawesome/free-solid-svg-icons'
|
import { faSpinner, faHighlighter, faBookmark, faList, faThLarge, faImage, faPenToSquare } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Hooks } from 'applesauce-react'
|
import { Hooks } from 'applesauce-react'
|
||||||
import { BlogPostSkeleton, HighlightSkeleton, BookmarkSkeleton } from './Skeletons'
|
import { BlogPostSkeleton, HighlightSkeleton, BookmarkSkeleton } from './Skeletons'
|
||||||
import { RelayPool } from 'applesauce-relay'
|
import { RelayPool } from 'applesauce-relay'
|
||||||
@@ -24,7 +24,6 @@ import { getCachedMeData, setCachedMeData, updateCachedHighlights } from '../ser
|
|||||||
import { faBooks } from '../icons/customIcons'
|
import { faBooks } from '../icons/customIcons'
|
||||||
import { usePullToRefresh } from 'use-pull-to-refresh'
|
import { usePullToRefresh } from 'use-pull-to-refresh'
|
||||||
import RefreshIndicator from './RefreshIndicator'
|
import RefreshIndicator from './RefreshIndicator'
|
||||||
import { getProfileUrl } from '../config/nostrGateways'
|
|
||||||
|
|
||||||
interface MeProps {
|
interface MeProps {
|
||||||
relayPool: RelayPool
|
relayPool: RelayPool
|
||||||
@@ -47,7 +46,6 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
|||||||
const [readArticles, setReadArticles] = useState<BlogPostPreview[]>([])
|
const [readArticles, setReadArticles] = useState<BlogPostPreview[]>([])
|
||||||
const [writings, setWritings] = useState<BlogPostPreview[]>([])
|
const [writings, setWritings] = useState<BlogPostPreview[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
|
||||||
const [viewMode, setViewMode] = useState<ViewMode>('cards')
|
const [viewMode, setViewMode] = useState<ViewMode>('cards')
|
||||||
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
||||||
|
|
||||||
@@ -61,14 +59,12 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
if (!viewingPubkey) {
|
if (!viewingPubkey) {
|
||||||
setError(isOwnProfile ? 'Please log in to view your data' : 'Invalid profile')
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
|
||||||
|
|
||||||
// Seed from cache if available to avoid empty flash (own profile only)
|
// Seed from cache if available to avoid empty flash (own profile only)
|
||||||
if (isOwnProfile) {
|
if (isOwnProfile) {
|
||||||
@@ -114,7 +110,7 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load data:', err)
|
console.error('Failed to load data:', err)
|
||||||
setError('Failed to load data. Please try again.')
|
// No blocking error - user can pull-to-refresh
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function useSettings({ relayPool, eventStore, pubkey, accountManager }: U
|
|||||||
const fullAccount = accountManager.getActive()
|
const fullAccount = accountManager.getActive()
|
||||||
if (!fullAccount) throw new Error('No active account')
|
if (!fullAccount) throw new Error('No active account')
|
||||||
const factory = new EventFactory({ signer: fullAccount })
|
const factory = new EventFactory({ signer: fullAccount })
|
||||||
await saveSettings(relayPool, eventStore, factory, newSettings, RELAYS)
|
await saveSettings(relayPool, eventStore, factory, newSettings)
|
||||||
setSettings(newSettings)
|
setSettings(newSettings)
|
||||||
setToastType('success')
|
setToastType('success')
|
||||||
setToastMessage('Settings saved')
|
setToastMessage('Settings saved')
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { RelayPool } from 'applesauce-relay'
|
import { RelayPool } from 'applesauce-relay'
|
||||||
import { Observable } from 'rxjs'
|
|
||||||
import { prioritizeLocalRelays } from '../utils/helpers'
|
import { prioritizeLocalRelays } from '../utils/helpers'
|
||||||
import { queryEvents } from './dataFetch'
|
import { queryEvents } from './dataFetch'
|
||||||
import { CONTACTS_REMOTE_TIMEOUT_MS } from '../config/network'
|
import { CONTACTS_REMOTE_TIMEOUT_MS } from '../config/network'
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const fetchBlogPostsFromAuthors = async (
|
|||||||
// Group by author + d-tag identifier
|
// Group by author + d-tag identifier
|
||||||
const uniqueEvents = new Map<string, NostrEvent>()
|
const uniqueEvents = new Map<string, NostrEvent>()
|
||||||
|
|
||||||
const events = await queryEvents(
|
await queryEvents(
|
||||||
relayPool,
|
relayPool,
|
||||||
{ kinds: [30023], authors: pubkeys, limit: 100 },
|
{ kinds: [30023], authors: pubkeys, limit: 100 },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export async function createHighlight(
|
|||||||
const signedEvent = await factory.sign(highlightEvent)
|
const signedEvent = await factory.sign(highlightEvent)
|
||||||
|
|
||||||
// Use unified write service to store and publish
|
// Use unified write service to store and publish
|
||||||
await publishEvent(relayPool, eventStore, signedEvent, settings)
|
await publishEvent(relayPool, eventStore, signedEvent)
|
||||||
|
|
||||||
// Check current connection status for UI feedback
|
// Check current connection status for UI feedback
|
||||||
const connectedRelays = Array.from(relayPool.relays.values())
|
const connectedRelays = Array.from(relayPool.relays.values())
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const fetchNostrverseBlogPosts = async (
|
|||||||
// Deduplicate replaceable events by keeping the most recent version
|
// Deduplicate replaceable events by keeping the most recent version
|
||||||
const uniqueEvents = new Map<string, NostrEvent>()
|
const uniqueEvents = new Map<string, NostrEvent>()
|
||||||
|
|
||||||
const events = await queryEvents(
|
await queryEvents(
|
||||||
relayPool,
|
relayPool,
|
||||||
{ kinds: [30023], limit },
|
{ kinds: [30023], limit },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -148,8 +148,7 @@ export async function saveSettings(
|
|||||||
relayPool: RelayPool,
|
relayPool: RelayPool,
|
||||||
eventStore: IEventStore,
|
eventStore: IEventStore,
|
||||||
factory: EventFactory,
|
factory: EventFactory,
|
||||||
settings: UserSettings,
|
settings: UserSettings
|
||||||
_relays: string[]
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log('💾 Saving settings to nostr:', settings)
|
console.log('💾 Saving settings to nostr:', settings)
|
||||||
|
|
||||||
@@ -165,7 +164,7 @@ export async function saveSettings(
|
|||||||
const signed = await factory.sign(draft)
|
const signed = await factory.sign(draft)
|
||||||
|
|
||||||
// Use unified write service
|
// Use unified write service
|
||||||
await publishEvent(relayPool, eventStore, signed, settings)
|
await publishEvent(relayPool, eventStore, signed)
|
||||||
|
|
||||||
console.log('✅ Settings published successfully')
|
console.log('✅ Settings published successfully')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { RelayPool } from 'applesauce-relay'
|
import { RelayPool } from 'applesauce-relay'
|
||||||
import { NostrEvent } from 'nostr-tools'
|
import { NostrEvent } from 'nostr-tools'
|
||||||
import { IEventStore } from 'applesauce-core'
|
import { IEventStore } from 'applesauce-core'
|
||||||
import { UserSettings } from './settingsService'
|
|
||||||
import { RELAYS } from '../config/relays'
|
import { RELAYS } from '../config/relays'
|
||||||
import { isLocalRelay, areAllRelaysLocal } from '../utils/helpers'
|
import { isLocalRelay, areAllRelaysLocal } from '../utils/helpers'
|
||||||
import { markEventAsOfflineCreated } from './offlineSyncService'
|
import { markEventAsOfflineCreated } from './offlineSyncService'
|
||||||
@@ -13,8 +12,7 @@ import { markEventAsOfflineCreated } from './offlineSyncService'
|
|||||||
export async function publishEvent(
|
export async function publishEvent(
|
||||||
relayPool: RelayPool,
|
relayPool: RelayPool,
|
||||||
eventStore: IEventStore,
|
eventStore: IEventStore,
|
||||||
event: NostrEvent,
|
event: NostrEvent
|
||||||
settings?: UserSettings
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Store the event in the local EventStore FIRST for immediate UI display
|
// Store the event in the local EventStore FIRST for immediate UI display
|
||||||
eventStore.add(event)
|
eventStore.add(event)
|
||||||
|
|||||||
Reference in New Issue
Block a user