fix: move progress indicator outside reader and fix position tracking

- Move ReadingProgressIndicator outside reader div for true fixed positioning
- Replace position-indicator library with custom scroll tracking
- Track document scroll position instead of content scroll
- Remove unused position-indicator dependency
- Ensure progress indicator is always visible and shows correct percentage
This commit is contained in:
Gigi
2025-10-13 21:04:39 +02:00
parent ec34bc3d04
commit e921967082
4 changed files with 29 additions and 45 deletions

7
package-lock.json generated
View File

@@ -22,7 +22,6 @@
"applesauce-relay": "^4.0.0",
"date-fns": "^4.1.0",
"nostr-tools": "^2.4.0",
"position-indicator": "^0.0.12",
"prismjs": "^1.30.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -8973,12 +8972,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/position-indicator": {
"version": "0.0.12",
"resolved": "https://registry.npmjs.org/position-indicator/-/position-indicator-0.0.12.tgz",
"integrity": "sha512-qHQejEylblB7rZ3MfXSI5hu1+Dq7EBv1BYwUIVWzJ3nZ8d6V7LFBi1zC5/XwT/01Wxddf9kaFoOy3L70/5tC+A==",
"license": "MIT"
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",

View File

@@ -25,7 +25,6 @@
"applesauce-relay": "^4.0.0",
"date-fns": "^4.1.0",
"nostr-tools": "^2.4.0",
"position-indicator": "^0.0.12",
"prismjs": "^1.30.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -374,8 +374,8 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
const highlightRgb = hexToRgb(highlightColor)
return (
<div className="reader" style={{ '--highlight-rgb': highlightRgb } as React.CSSProperties}>
{/* Reading Progress Indicator */}
<>
{/* Reading Progress Indicator - Outside reader for fixed positioning */}
{isTextContent && (
<ReadingProgressIndicator
progress={progressPercentage}
@@ -384,6 +384,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
/>
)}
<div className="reader" style={{ '--highlight-rgb': highlightRgb } as React.CSSProperties}>
{/* Hidden markdown preview to convert markdown to HTML */}
{markdown && (
<div ref={markdownPreviewRef} style={{ display: 'none' }}>
@@ -591,6 +592,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
</div>
)}
</div>
</>
)
}

View File

@@ -1,19 +1,8 @@
import { useEffect, useRef, useState } from 'react'
// @ts-ignore - position-indicator types issue
import { createPositionIndicator } from 'position-indicator'
interface ReadingPositionData {
position: number // 0 to 1
prevPosition: number
hasUpdated: boolean
hasScroll: boolean
eventType: 'scroll' | 'resize' | 'heightChange' | 'init'
eventDate: number
}
interface UseReadingPositionOptions {
enabled?: boolean
onPositionChange?: (data: ReadingPositionData) => void
onPositionChange?: (position: number) => void
onReadingComplete?: () => void
readingCompleteThreshold?: number // Default 0.9 (90%)
}
@@ -26,44 +15,45 @@ export const useReadingPosition = ({
}: UseReadingPositionOptions = {}) => {
const [position, setPosition] = useState(0)
const [isReadingComplete, setIsReadingComplete] = useState(false)
const positionIndicatorRef = useRef<any>(null)
const hasTriggeredComplete = useRef(false)
useEffect(() => {
if (!enabled) return
const handleInit = (data: ReadingPositionData) => {
setPosition(data.position)
onPositionChange?.(data)
}
const handleScroll = () => {
// Get the main content area (reader content)
const readerContent = document.querySelector('.reader-html, .reader-markdown')
if (!readerContent) return
const handleUpdate = (data: ReadingPositionData) => {
setPosition(data.position)
onPositionChange?.(data)
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
const windowHeight = window.innerHeight
const documentHeight = document.documentElement.scrollHeight
// Calculate position based on how much of the content has been scrolled through
const scrollProgress = Math.min(scrollTop / (documentHeight - windowHeight), 1)
const clampedProgress = Math.max(0, Math.min(1, scrollProgress))
setPosition(clampedProgress)
onPositionChange?.(clampedProgress)
// Check if reading is complete
if (data.position >= readingCompleteThreshold && !hasTriggeredComplete.current) {
if (clampedProgress >= readingCompleteThreshold && !hasTriggeredComplete.current) {
setIsReadingComplete(true)
hasTriggeredComplete.current = true
onReadingComplete?.()
}
}
const positionIndicator = createPositionIndicator({
onInit: handleInit,
onUpdate: handleUpdate,
useResizeListener: true,
useResizeObserver: true
})
// Initial calculation
handleScroll()
positionIndicator.init()
positionIndicatorRef.current = positionIndicator
// Add scroll listener
window.addEventListener('scroll', handleScroll, { passive: true })
window.addEventListener('resize', handleScroll, { passive: true })
return () => {
if (positionIndicatorRef.current) {
positionIndicatorRef.current.destroy()
positionIndicatorRef.current = null
}
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('resize', handleScroll)
}
}, [enabled, onPositionChange, onReadingComplete, readingCompleteThreshold])