Compare commits

...

12 Commits

Author SHA1 Message Date
Gigi
28ba620967 chore: bump version to 0.10.18 2025-10-23 16:37:43 +02:00
Gigi
56f2d33e93 fix: fetch all highlights without incremental loading
- Remove incremental loading (since filter) from highlightsController
- Fetch ALL highlights without limits for complete results
- Remove unused timestamp tracking methods and constant
- Ensures /my/highlights shows all highlights consistently
- Matches the fix applied to writingsController
2025-10-23 16:36:02 +02:00
Gigi
312c742969 refactor: centralize writings fetching in controller
- Remove incremental loading (since filter) from writingsController
- Fetch ALL writings without limits for complete results
- Remove duplicate background fetch from Me.tsx and Profile.tsx
- Use writingsController.start() in Profile to populate event store
- Keep code DRY by having single source of truth in controller
- Follows controller pattern: stream, dedupe, store, emit
2025-10-23 16:31:56 +02:00
Gigi
0781c4ebfc fix: fetch all writings in background on /my/writings page
Add background fetch effect to Me component to populate event store with
all writings without limits, matching the behavior of Profile component.
This ensures all writings are displayed on /my/writings page.
2025-10-23 16:28:50 +02:00
Gigi
85f4cd3590 docs: update FEATURES and CHANGELOG from /me to /my 2025-10-23 16:18:32 +02:00
Gigi
89bc6258b1 docs: update CSS comments from Me to My 2025-10-23 16:14:32 +02:00
Gigi
534b628aea refactor: update ShareTargetHandler to navigate to /my/links 2025-10-23 16:13:35 +02:00
Gigi
317d2e0b53 refactor: update Bookmarks component to detect /my routes 2025-10-23 16:13:20 +02:00
Gigi
9ea69589fa refactor: update sidebar avatar navigation to /my 2025-10-23 16:12:52 +02:00
Gigi
89eaa97d30 refactor: update Me component navigation to /my routes 2025-10-23 16:12:33 +02:00
Gigi
0283405fb5 refactor: rename /me routes to /my in App.tsx 2025-10-23 16:12:01 +02:00
Gigi
5eade913d1 docs: update CHANGELOG for v0.10.17 2025-10-23 16:01:31 +02:00
12 changed files with 111 additions and 152 deletions

View File

@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.10.17] - 2025-10-23
### Added
- Setting to control auto-scroll to reading position
- New toggle in Settings > Reading Experience
- Allows users to disable automatic scroll restoration
- Defaults to enabled (preserves existing behavior)
### Fixed
- Blockquote styling on mobile devices
- Added equal right padding to match left padding (2rem on both sides)
- Prevents awkward text cutoff on narrow screens
- Timestamp clicks in highlight cards now navigate within app
- Articles (kind:30023) open via `/a/{naddr}` route
- External URLs open via `/r/{encodedUrl}` route
- Previously opened external search portal (ants.sh)
- Highlight automatically scrolls into view with sidebar open
- Hero images now properly extend edge-to-edge on mobile
- Adjusted negative margins to match new reader padding
- Image bleeds to screen edges while text maintains comfortable margins
- Article relay links now open via `/a/` path instead of `/r/`
- Ensures nostr-native articles route correctly
- External links continue to use `/r/` path
### Changed
- Mobile reader padding increased for better readability
- Horizontal padding increased from 0.5rem to 1rem
- Title, summary, and body text now properly aligned
- More comfortable reading experience on small screens
- Reading position save interval reduced from 3s to 1s
- More frequent auto-saves during active reading
- Better preservation of reading progress
## [0.10.16] - 2025-10-22
### Fixed
@@ -34,7 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Highlights now scroll into view when clicked from `/me/highlights` page
- Highlights now scroll into view when clicked from `/my/highlights` page
- Navigation state properly passes highlight ID and openHighlights flag
- Works for both article links and external URL links
@@ -159,8 +195,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Default bookmark view changed to flat chronological list (newest first)
- Bookmark URL changed from `/me/reading-list` to `/me/bookmarks`
- Router updated to handle `/me/reading-list``/me/bookmarks` redirect
- Bookmark URL changed from `/my/reading-list` to `/my/bookmarks`
- Router updated to handle `/my/reading-list``/my/bookmarks` redirect
- Me.tsx bookmarks tab now uses dynamic filter titles and chronological sorting
- Me.tsx updated to use faClock icon instead of faBars
- Removed bookmark count from section headings for cleaner display
@@ -266,7 +302,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Tab switching regression on `/me` page
- Tab switching regression on `/my` page
- Resolved infinite update loop caused by circular dependency in `useCallback` hooks
- Tab navigation now properly updates UI when URL changes
- Removed `loadedTabs` from dependency arrays to prevent re-render cycles
@@ -624,7 +660,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- `/me/bookmarks` tab now displays in cards view only
- `/my/bookmarks` tab now displays in cards view only
- Removed view mode toggle buttons (compact, large) from bookmarks tab
- Cards view provides optimal bookmark browsing experience
- Grouping toggle (grouped/flat) still available
@@ -737,7 +773,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Prevent "No highlights yet" flash on `/me/highlights` page
- Prevent "No highlights yet" flash on `/my/highlights` page
- Force React to remount tab content when switching tabs for proper state management
- Deduplicate blog posts by author:d-tag instead of event ID for better accuracy
- Show skeleton placeholders while highlights are loading for better UX
@@ -945,7 +981,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Filter icons colored when active (blue for most, green for completed)
- URL routing support for reading progress filters
- Reading progress filters available in Archive tab and bookmarks sidebar
- Reads and Links tabs on `/me` page
- Reads and Links tabs on `/my` page
- Reads tab shows nostr-native articles with reading progress
- Links tab shows external URLs with reading progress
- Both tabs populate instantly from bookmarks for fast loading
@@ -991,7 +1027,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bookmark filter buttons by content type (articles, videos, images, web links)
- Filter bookmarks by their content type on bookmarks sidebar
- Filters also available on `/me` page bookmarks tab
- Filters also available on `/my` page bookmarks tab
- Separate filter for external articles with link icon
- Multiple filters can be active simultaneously
- Private Bookmarks section for encrypted legacy bookmarks
@@ -1005,7 +1041,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Better categorization of bookmark types
- Bookmark filter button styling refined
- Reduced whitespace around bookmark filters for cleaner layout
- Dramatically reduced whitespace on both sidebar and `/me` page
- Dramatically reduced whitespace on both sidebar and `/my` page
- Lock icon removed from individual bookmarks
- Encryption status now indicated by section grouping
- Cleaner bookmark item appearance
@@ -1172,7 +1208,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Camera icon for image bookmarks
- Sticky note icon for text-only bookmarks without URLs
- Bookmark grouping and sections
- Grouped sections in sidebar and `/me` reading-list
- Grouped sections in sidebar and `/my` reading-list
- Web bookmarks, default bookmarks, and legacy bookmarks in separate sections
- Grouping and sorting helpers for organizing bookmark sections
- Adaptive text color for publication date over hero images
@@ -1235,7 +1271,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Mobile bookmark button visibility across all pages
- Now visible on `/p/` (profile), `/explore`, `/me`, and `/support` pages
- Now visible on `/p/` (profile), `/explore`, `/my`, and `/support` pages
- Only hidden on settings page or when scrolling down while reading
- Prevents users from getting stuck without navigation options
- Mobile highlights button behavior at page top
@@ -1416,7 +1452,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Highlights tab on `/explore` page
- View highlights from friends and followed users
- Tab structure matching `/me` and profile pages
- Tab structure matching `/my` and profile pages
- Grid layout for highlights with cards
- Highlights shown first, writings second
- Clicking highlight opens source article and scrolls to position
@@ -1573,7 +1609,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Writings tab on `/me` page to display user's published articles
- Writings tab on `/my` page to display user's published articles
- Comprehensive headline styling (h1-h6) with Tailwind typography
- List styling for ordered and unordered lists in articles
- Blockquote styling with indentation and italics
@@ -1596,7 +1632,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Horizontal overflow from code blocks and wide content on mobile
- Settings view now mobile-friendly with proper width constraints
- Long relay URLs no longer cause horizontal overflow on mobile
- Sidebar/highlights toggle buttons hidden on settings/explore/me pages
- Sidebar/highlights toggle buttons hidden on settings/explore/my pages
- Video titles now show filename instead of 'Error Loading Content'
- AddBookmarkModal z-index issue fixed using React Portal
- Highlight matching for text spanning multiple DOM nodes/inline elements
@@ -1651,7 +1687,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- YouTube video metadata extraction with title, description, and captions
- Responsive video player with aspect ratio support
- Thumbnail images in compact view
- URL routing for /me page tabs
- URL routing for /my page tabs
- Bookmark navigation in reading list
- Video duration display for video URLs
- Three-dot menu for videos with open/native/copy/share actions
@@ -1681,7 +1717,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Style
- Hide tab counts on mobile for /me page
- Hide tab counts on mobile for /my page
- Remove max-width on main pane, constrain reader instead
- Full width layout for videos
- Reader-video specific styles
@@ -1694,11 +1730,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Prism.js syntax highlighting for code blocks
- Inline image rendering in nostr-native blog posts
- Image placeholders on blog post cards in `/explore`
- Caching on `/me` page for faster loading
- Caching on `/my` page for faster loading
### Changed
- Reading List on `/me` now uses the same components as the bookmarks sidebar
- Reading List on `/my` now uses the same components as the bookmarks sidebar
- Improve bookmarks sidebar visual design
- Make article menu button more subtle by removing border
@@ -1718,8 +1754,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- `/me` page with tabbed layout featuring Highlights, Reading List, and Library tabs
- Two-pane layout for `/me` page with article sources and highlights
- `/my` page with tabbed layout featuring Highlights, Reading List, and Library tabs
- Two-pane layout for `/my` page with article sources and highlights
- Custom FontAwesome Pro books icon for Archive tab
- CompactButton component for highlight cards
- Instant mark-as-read functionality with checkmark animation and read status checking
@@ -1728,7 +1764,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Rename Library tab to Archive
- Move highlight timestamp to top-right corner of cards
- Replace username with AuthorCard component on `/me` page
- Replace username with AuthorCard component on `/my` page
- Use user's custom highlight color for Highlights tab
- Render library articles using BlogPostCard component for consistency
- Use faBooks icon for Mark as Read button
@@ -1748,12 +1784,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Style
- Match `/me` profile card width to highlight cards
- Improve Me page mobile tabs and avoid overlap with sidebar buttons
- Match `/my` profile card width to highlight cards
- Improve My page mobile tabs and avoid overlap with sidebar buttons
- Reduce margins/paddings to make highlight cards more compact
- Tighten vertical spacing on highlight cards
- Left-align text inside author card
- Constrain `/me` page content width to match author card (600px)
- Constrain `/my` page content width to match author card (600px)
- Improve tab border styling for dark theme
- Make relay indicator match CompactButton (same look as menu)
- Align relay indicator within footer with symmetric spacing
@@ -1858,7 +1894,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Highlights: merge remote results after local for article/url
- Explore: always query remote relays after local; stream merge into UI
- Improve mobile touch targets for highlight icons
- Color `/me` highlights with "my highlights" color setting
- Color `/my` highlights with "my highlights" color setting
### Performance
@@ -1890,7 +1926,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Confirmation dialog prevents accidental deletions
- Styled to match relay indicator (subtle, same size)
- Removes highlights from UI immediately after deletion request
- `/me` page showing user's recent highlights
- `/my` page showing user's recent highlights
- Accessible by clicking profile picture in bookmark sidebar
- Displays all highlights created by the logged-in user
- Uses same rendering as Settings and Explore pages

View File

@@ -40,7 +40,7 @@
- **Explore**: Discover friends' highlights and writings, plus a "nostrverse" feed.
- **Filters**: Visibility toggles (mine, friends, nostrverse) apply to Explore highlights.
- **Profiles**: View your own (`/me`) or other users (`/p/:npub`) with tabs for Highlights, Bookmarks, Archive, and Writings.
- **Profiles**: View your own (`/my`) or other users (`/p/:npub`) with tabs for Highlights, Bookmarks, Archive, and Writings.
## Support

View File

@@ -1,6 +1,6 @@
{
"name": "boris",
"version": "0.10.17",
"version": "0.10.18",
"description": "A minimal nostr client for bookmark management",
"homepage": "https://read.withboris.com/",
"type": "module",

View File

@@ -237,11 +237,11 @@ function AppRoutes({
}
/>
<Route
path="/me"
element={<Navigate to="/me/highlights" replace />}
path="/my"
element={<Navigate to="/my/highlights" replace />}
/>
<Route
path="/me/highlights"
path="/my/highlights"
element={
<Bookmarks
relayPool={relayPool}
@@ -253,7 +253,7 @@ function AppRoutes({
}
/>
<Route
path="/me/bookmarks"
path="/my/bookmarks"
element={
<Bookmarks
relayPool={relayPool}
@@ -265,7 +265,7 @@ function AppRoutes({
}
/>
<Route
path="/me/reads"
path="/my/reads"
element={
<Bookmarks
relayPool={relayPool}
@@ -277,7 +277,7 @@ function AppRoutes({
}
/>
<Route
path="/me/reads/:filter"
path="/my/reads/:filter"
element={
<Bookmarks
relayPool={relayPool}
@@ -289,7 +289,7 @@ function AppRoutes({
}
/>
<Route
path="/me/links"
path="/my/links"
element={
<Bookmarks
relayPool={relayPool}
@@ -301,7 +301,7 @@ function AppRoutes({
}
/>
<Route
path="/me/links/:filter"
path="/my/links/:filter"
element={
<Bookmarks
relayPool={relayPool}
@@ -313,7 +313,7 @@ function AppRoutes({
}
/>
<Route
path="/me/writings"
path="/my/writings"
element={
<Bookmarks
relayPool={relayPool}

View File

@@ -53,7 +53,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({
const showSettings = location.pathname === '/settings'
const showExplore = location.pathname.startsWith('/explore')
const showMe = location.pathname.startsWith('/me')
const showMe = location.pathname.startsWith('/my')
const showProfile = location.pathname.startsWith('/p/')
const showSupport = location.pathname === '/support'
const eventId = eventIdParam
@@ -62,12 +62,12 @@ const Bookmarks: React.FC<BookmarksProps> = ({
const exploreTab = location.pathname === '/explore/writings' ? 'writings' : 'highlights'
// Extract tab from me routes
const meTab = location.pathname === '/me' ? 'highlights' :
location.pathname === '/me/highlights' ? 'highlights' :
location.pathname === '/me/bookmarks' ? 'bookmarks' :
location.pathname.startsWith('/me/reads') ? 'reads' :
location.pathname.startsWith('/me/links') ? 'links' :
location.pathname === '/me/writings' ? 'writings' : 'highlights'
const meTab = location.pathname === '/my' ? 'highlights' :
location.pathname === '/my/highlights' ? 'highlights' :
location.pathname === '/my/bookmarks' ? 'bookmarks' :
location.pathname.startsWith('/my/reads') ? 'reads' :
location.pathname.startsWith('/my/links') ? 'links' :
location.pathname === '/my/writings' ? 'writings' : 'highlights'
// Extract tab from profile routes
const profileTab = location.pathname.endsWith('/writings') ? 'writings' : 'highlights'
@@ -87,7 +87,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({
}
}
// Track previous location for going back from settings/me/explore/profile
// Track previous location for going back from settings/my/explore/profile
useEffect(() => {
if (!showSettings && !showMe && !showExplore && !showProfile) {
previousLocationRef.current = location.pathname

View File

@@ -144,15 +144,15 @@ const Me: React.FC<MeProps> = ({
setReadingProgressFilter(filter)
if (activeTab === 'reads') {
if (filter === 'all') {
navigate('/me/reads', { replace: true })
navigate('/my/reads', { replace: true })
} else {
navigate(`/me/reads/${filter}`, { replace: true })
navigate(`/my/reads/${filter}`, { replace: true })
}
} else if (activeTab === 'links') {
if (filter === 'all') {
navigate('/me/links', { replace: true })
navigate('/my/links', { replace: true })
} else {
navigate(`/me/links/${filter}`, { replace: true })
navigate(`/my/links/${filter}`, { replace: true })
}
}
}
@@ -867,7 +867,7 @@ const Me: React.FC<MeProps> = ({
<button
className={`me-tab ${activeTab === 'highlights' ? 'active' : ''}`}
data-tab="highlights"
onClick={() => navigate('/me/highlights')}
onClick={() => navigate('/my/highlights')}
>
<FontAwesomeIcon icon={faHighlighter} />
<span className="tab-label">Highlights</span>
@@ -875,7 +875,7 @@ const Me: React.FC<MeProps> = ({
<button
className={`me-tab ${activeTab === 'bookmarks' ? 'active' : ''}`}
data-tab="bookmarks"
onClick={() => navigate('/me/bookmarks')}
onClick={() => navigate('/my/bookmarks')}
>
<FontAwesomeIcon icon={faBookmark} />
<span className="tab-label">Bookmarks</span>
@@ -883,7 +883,7 @@ const Me: React.FC<MeProps> = ({
<button
className={`me-tab ${activeTab === 'reads' ? 'active' : ''}`}
data-tab="reads"
onClick={() => navigate('/me/reads')}
onClick={() => navigate('/my/reads')}
>
<FontAwesomeIcon icon={faBooks} />
<span className="tab-label">Reads</span>
@@ -891,7 +891,7 @@ const Me: React.FC<MeProps> = ({
<button
className={`me-tab ${activeTab === 'links' ? 'active' : ''}`}
data-tab="links"
onClick={() => navigate('/me/links')}
onClick={() => navigate('/my/links')}
>
<FontAwesomeIcon icon={faLink} />
<span className="tab-label">Links</span>
@@ -899,7 +899,7 @@ const Me: React.FC<MeProps> = ({
<button
className={`me-tab ${activeTab === 'writings' ? 'active' : ''}`}
data-tab="writings"
onClick={() => navigate('/me/writings')}
onClick={() => navigate('/my/writings')}
>
<FontAwesomeIcon icon={faPenToSquare} />
<span className="tab-label">Writings</span>

View File

@@ -6,10 +6,8 @@ import { RelayPool } from 'applesauce-relay'
import { nip19 } from 'nostr-tools'
import { useNavigate } from 'react-router-dom'
import { HighlightItem } from './HighlightItem'
import { BlogPostPreview, fetchBlogPostsFromAuthors } from '../services/exploreService'
import { fetchHighlights } from '../services/highlightService'
import { BlogPostPreview } from '../services/exploreService'
import { KINDS } from '../config/kinds'
import { getActiveRelayUrls } from '../services/relayManager'
import AuthorCard from './AuthorCard'
import BlogPostCard from './BlogPostCard'
import { BlogPostSkeleton, HighlightSkeleton } from './Skeletons'
@@ -20,6 +18,8 @@ import { usePullToRefresh } from 'use-pull-to-refresh'
import RefreshIndicator from './RefreshIndicator'
import { Hooks } from 'applesauce-react'
import { readingProgressController } from '../services/readingProgressController'
import { writingsController } from '../services/writingsController'
import { highlightsController } from '../services/highlightsController'
interface ProfileProps {
relayPool: RelayPool
@@ -103,17 +103,16 @@ const Profile: React.FC<ProfileProps> = ({
})
}, [activeAccount?.pubkey, relayPool, eventStore, refreshTrigger])
// Background fetch to populate event store (non-blocking)
// Background fetch via controllers to populate event store
useEffect(() => {
if (!pubkey || !relayPool || !eventStore) return
// Fetch all highlights and writings in background (no limits)
const relayUrls = getActiveRelayUrls(relayPool)
fetchHighlights(relayPool, pubkey, undefined, undefined, false, eventStore)
// Start controllers to fetch and populate event store
// Controllers handle streaming, deduplication, and storage
highlightsController.start({ relayPool, eventStore, pubkey })
.catch(err => console.warn('⚠️ [Profile] Failed to fetch highlights:', err))
fetchBlogPostsFromAuthors(relayPool, [pubkey], relayUrls, undefined, null, eventStore)
writingsController.start({ relayPool, eventStore, pubkey, force: refreshTrigger > 0 })
.catch(err => console.warn('⚠️ [Profile] Failed to fetch writings:', err))
}, [pubkey, relayPool, eventStore, refreshTrigger])

View File

@@ -60,7 +60,7 @@ export default function ShareTargetHandler({ relayPool }: ShareTargetHandlerProp
getActiveRelayUrls(relayPool)
)
showToast('Bookmark saved!')
navigate('/me/links')
navigate('/my/links')
} catch (err) {
console.error('Failed to save shared bookmark:', err)
showToast('Failed to save bookmark')

View File

@@ -40,7 +40,7 @@ const SidebarHeader: React.FC<SidebarHeaderProps> = ({ onToggleCollapse, onLogou
<button
className="profile-avatar-button"
title={getUserDisplayName()}
onClick={() => navigate('/me')}
onClick={() => navigate('/my')}
aria-label={`Profile: ${getUserDisplayName()}`}
>
{profileImage ? (

View File

@@ -8,8 +8,6 @@ import { eventToHighlight, sortHighlights } from './highlightEventProcessor'
type HighlightsCallback = (highlights: Highlight[]) => void
type LoadingCallback = (loading: boolean) => void
const LAST_SYNCED_KEY = 'highlights_last_synced'
/**
* Shared highlights controller
* Manages the user's highlights centrally, similar to bookmarkController
@@ -68,37 +66,10 @@ class HighlightsController {
this.emitHighlights(this.currentHighlights)
}
/**
* Get last synced timestamp for incremental loading
*/
private getLastSyncedAt(pubkey: string): number | null {
try {
const data = localStorage.getItem(LAST_SYNCED_KEY)
if (!data) return null
const parsed = JSON.parse(data)
return parsed[pubkey] || null
} catch {
return null
}
}
/**
* Update last synced timestamp
*/
private setLastSyncedAt(pubkey: string, timestamp: number): void {
try {
const data = localStorage.getItem(LAST_SYNCED_KEY)
const parsed = data ? JSON.parse(data) : {}
parsed[pubkey] = timestamp
localStorage.setItem(LAST_SYNCED_KEY, JSON.stringify(parsed))
} catch (err) {
console.warn('[highlights] Failed to save last synced timestamp:', err)
}
}
/**
* Load highlights for a user
* Streams results and stores in event store
* Always fetches ALL highlights to ensure completeness
*/
async start(options: {
relayPool: RelayPool
@@ -124,15 +95,12 @@ class HighlightsController {
const seenIds = new Set<string>()
const highlightsMap = new Map<string, Highlight>()
// Get last synced timestamp for incremental loading
const lastSyncedAt = force ? null : this.getLastSyncedAt(pubkey)
const filter: { kinds: number[]; authors: string[]; since?: number } = {
// Fetch ALL highlights without limits (no since filter)
// This ensures we get complete results for profile/my pages
const filter = {
kinds: [KINDS.Highlights],
authors: [pubkey]
}
if (lastSyncedAt) {
filter.since = lastSyncedAt
}
const events = await queryEvents(
relayPool,
@@ -179,12 +147,6 @@ class HighlightsController {
this.lastLoadedPubkey = pubkey
this.emitHighlights(sorted)
// Update last synced timestamp
if (sorted.length > 0) {
const newestTimestamp = Math.max(...sorted.map(h => h.created_at))
this.setLastSyncedAt(pubkey, newestTimestamp)
}
} catch (error) {
console.error('[highlights] ❌ Failed to load highlights:', error)
this.currentHighlights = []

View File

@@ -10,8 +10,6 @@ const { getArticleTitle, getArticleSummary, getArticleImage, getArticlePublished
type WritingsCallback = (posts: BlogPostPreview[]) => void
type LoadingCallback = (loading: boolean) => void
const LAST_SYNCED_KEY = 'writings_last_synced'
/**
* Shared writings controller
* Manages the user's nostr-native long-form articles (kind:30023) centrally,
@@ -71,34 +69,6 @@ class WritingsController {
this.emitWritings(this.currentPosts)
}
/**
* Get last synced timestamp for incremental loading
*/
private getLastSyncedAt(pubkey: string): number | null {
try {
const data = localStorage.getItem(LAST_SYNCED_KEY)
if (!data) return null
const parsed = JSON.parse(data)
return parsed[pubkey] || null
} catch {
return null
}
}
/**
* Update last synced timestamp
*/
private setLastSyncedAt(pubkey: string, timestamp: number): void {
try {
const data = localStorage.getItem(LAST_SYNCED_KEY)
const parsed = data ? JSON.parse(data) : {}
parsed[pubkey] = timestamp
localStorage.setItem(LAST_SYNCED_KEY, JSON.stringify(parsed))
} catch (err) {
console.warn('[writings] Failed to save last synced timestamp:', err)
}
}
/**
* Convert NostrEvent to BlogPostPreview using applesauce Helpers
*/
@@ -127,6 +97,7 @@ class WritingsController {
/**
* Load writings for a user (kind:30023)
* Streams results and stores in event store
* Always fetches ALL writings to ensure completeness
*/
async start(options: {
relayPool: RelayPool
@@ -152,15 +123,12 @@ class WritingsController {
const seenIds = new Set<string>()
const uniqueByReplaceable = new Map<string, BlogPostPreview>()
// Get last synced timestamp for incremental loading
const lastSyncedAt = force ? null : this.getLastSyncedAt(pubkey)
const filter: { kinds: number[]; authors: string[]; since?: number } = {
// Fetch ALL writings without limits (no since filter)
// This ensures we get complete results for profile/my pages
const filter = {
kinds: [KINDS.BlogPost],
authors: [pubkey]
}
if (lastSyncedAt) {
filter.since = lastSyncedAt
}
const events = await queryEvents(
relayPool,
@@ -221,12 +189,6 @@ class WritingsController {
this.lastLoadedPubkey = pubkey
this.emitWritings(sorted)
// Update last synced timestamp
if (sorted.length > 0) {
const newestTimestamp = Math.max(...sorted.map(p => p.event.created_at))
this.setLastSyncedAt(pubkey, newestTimestamp)
}
} catch (error) {
console.error('[writings] ❌ Failed to load writings:', error)
this.currentPosts = []

View File

@@ -1,4 +1,4 @@
/* Me page tabs */
/* My page tabs */
.me-tabs {
display: flex;
gap: 0.5rem;
@@ -71,7 +71,7 @@
padding-top: 0.25rem;
}
/* Align highlight list width with profile card width on /me */
/* Align highlight list width with profile card width on /my */
.me-highlights-list { padding-left: 0; padding-right: 0; }
.explore-header .author-card { max-width: 600px; margin: 0 auto; width: 100%; }
@@ -83,7 +83,7 @@
text-align: left; /* Override center alignment from .app */
}
/* Bookmark filters in Me page */
/* Bookmark filters in My page */
.me-tab-content .bookmark-filters {
background: transparent;
border: none;