From 51c0f7d9230f7e94bc490cde857bf367a0e929b5 Mon Sep 17 00:00:00 2001 From: Gigi Date: Thu, 23 Oct 2025 00:27:35 +0200 Subject: [PATCH] fix(highlights): scroll to highlight when clicked from /me/highlights Pass highlightId and openHighlights in navigation state when clicking highlights from the highlights list. This triggers the scroll behavior in Bookmarks.tsx that was already implemented but not being used. The useHighlightInteractions hook automatically scrolls to the selected highlight once the article loads and the highlight mark is found in the DOM. --- src/components/HighlightItem.tsx | 17 ++++++++-- src/hooks/useReadingPosition.ts | 53 +++++++++----------------------- 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/src/components/HighlightItem.tsx b/src/components/HighlightItem.tsx index 733d270d..d6617698 100644 --- a/src/components/HighlightItem.tsx +++ b/src/components/HighlightItem.tsx @@ -212,12 +212,23 @@ export const HighlightItem: React.FC = ({ pubkey, identifier }) - navigate(`/a/${naddr}`) + // Pass highlight ID in navigation state to trigger scroll + navigate(`/a/${naddr}`, { + state: { + highlightId: highlight.id, + openHighlights: true + } + }) } } } else if (highlight.urlReference) { - // Navigate to external URL - navigate(`/r/${encodeURIComponent(highlight.urlReference)}`) + // Navigate to external URL with highlight ID to trigger scroll + navigate(`/r/${encodeURIComponent(highlight.urlReference)}`, { + state: { + highlightId: highlight.id, + openHighlights: true + } + }) } } diff --git a/src/hooks/useReadingPosition.ts b/src/hooks/useReadingPosition.ts index 7a9aca92..d97ce509 100644 --- a/src/hooks/useReadingPosition.ts +++ b/src/hooks/useReadingPosition.ts @@ -29,15 +29,6 @@ export const useReadingPosition = ({ const completionTimerRef = useRef | null>(null) const lastSavedAtRef = useRef(0) const suppressUntilRef = useRef(0) - const syncEnabledRef = useRef(syncEnabled) - const onSaveRef = useRef(onSave) - const scheduleSaveRef = useRef<((pos: number) => void) | null>(null) - - // Keep refs in sync with props - useEffect(() => { - syncEnabledRef.current = syncEnabled - onSaveRef.current = onSave - }, [syncEnabled, onSave]) // Suppress auto-saves for a given duration (used after programmatic restore) const suppressSavesFor = useCallback((ms: number) => { @@ -48,7 +39,7 @@ export const useReadingPosition = ({ // Debounced save function - simple 2s debounce const scheduleSave = useCallback((currentPosition: number) => { - if (!syncEnabledRef.current || !onSaveRef.current) { + if (!syncEnabled || !onSave) { return } @@ -62,7 +53,7 @@ export const useReadingPosition = ({ lastSavedPosition.current = 1 hasSavedOnce.current = true lastSavedAtRef.current = Date.now() - onSaveRef.current(1) + onSave(1) return } @@ -85,26 +76,19 @@ export const useReadingPosition = ({ lastSavedPosition.current = currentPosition hasSavedOnce.current = true lastSavedAtRef.current = Date.now() - if (onSaveRef.current) { - onSaveRef.current(currentPosition) - } + onSave(currentPosition) saveTimerRef.current = null }, DEBOUNCE_MS) - }, []) - - // Store scheduleSave in ref for use in scroll handler - useEffect(() => { - scheduleSaveRef.current = scheduleSave - }, [scheduleSave]) + }, [syncEnabled, onSave]) // Immediate save function const saveNow = useCallback(() => { - if (!syncEnabledRef.current || !onSaveRef.current) return + if (!syncEnabled || !onSave) return // Check suppression even for saveNow (e.g., during restore) if (Date.now() < suppressUntilRef.current) { const remainingMs = suppressUntilRef.current - Date.now() - console.log(`[reading-position] [${new Date().toISOString()}] ⏭️ saveNow() suppressed (${remainingMs}ms remaining) at ${Math.round(positionRef.current * 100)}%`) + console.log(`[reading-position] [${new Date().toISOString()}] ⏭️ saveNow() suppressed (${remainingMs}ms remaining) at ${Math.round(position * 100)}%`) return } @@ -112,12 +96,12 @@ export const useReadingPosition = ({ clearTimeout(saveTimerRef.current) saveTimerRef.current = null } - console.log(`[reading-position] [${new Date().toISOString()}] 💾 saveNow() called at ${Math.round(positionRef.current * 100)}%`) - lastSavedPosition.current = positionRef.current + console.log(`[reading-position] [${new Date().toISOString()}] 💾 saveNow() called at ${Math.round(position * 100)}%`) + lastSavedPosition.current = position hasSavedOnce.current = true lastSavedAtRef.current = Date.now() - onSaveRef.current(positionRef.current) - }, []) + onSave(position) + }, [syncEnabled, onSave, position]) useEffect(() => { if (!enabled) return @@ -149,7 +133,7 @@ export const useReadingPosition = ({ // Schedule auto-save if sync is enabled (unless suppressed) if (Date.now() >= suppressUntilRef.current) { - scheduleSaveRef.current?.(clampedProgress) + scheduleSave(clampedProgress) } else { const remainingMs = suppressUntilRef.current - Date.now() console.log(`[reading-position] [${new Date().toISOString()}] 🛡️ Save suppressed (${remainingMs}ms remaining) at ${Math.round(clampedProgress * 100)}%`) @@ -196,24 +180,15 @@ export const useReadingPosition = ({ window.removeEventListener('scroll', handleScroll) window.removeEventListener('resize', handleScroll) - // Flush pending save before unmount (don't lose progress if navigating away during debounce window) - if (saveTimerRef.current && syncEnabledRef.current && onSaveRef.current) { + // Clear save timer on unmount + if (saveTimerRef.current) { clearTimeout(saveTimerRef.current) - saveTimerRef.current = null - - // Only flush if we have unsaved progress (position differs from last saved) - const hasUnsavedProgress = Math.abs(positionRef.current - lastSavedPosition.current) >= 0.05 - if (hasUnsavedProgress && Date.now() >= suppressUntilRef.current) { - console.log(`[reading-position] [${new Date().toISOString()}] 💾 Flushing pending save on unmount at ${Math.round(positionRef.current * 100)}%`) - onSaveRef.current(positionRef.current) - } } - if (completionTimerRef.current) { clearTimeout(completionTimerRef.current) } } - }, [enabled, onPositionChange, onReadingComplete, readingCompleteThreshold, completionHoldMs]) + }, [enabled, onPositionChange, onReadingComplete, readingCompleteThreshold, scheduleSave, completionHoldMs]) // Reset reading complete state when enabled changes useEffect(() => {