potential fix for not jumping to reading position sometimes

This commit is contained in:
Stefan Hardegger
2026-02-16 17:12:55 +01:00
parent 28fa346b63
commit 02fa9dab4f
5 changed files with 118 additions and 20 deletions

View File

@@ -307,10 +307,11 @@ public class Story {
this.isRead = true;
this.lastReadAt = LocalDateTime.now();
// Set reading position to the end of content if available
if (contentPlain != null) {
this.readingPosition = contentPlain.length();
} else if (contentHtml != null) {
// ALWAYS use contentHtml for consistency (frontend uses contentHtml for position tracking)
if (contentHtml != null) {
this.readingPosition = contentHtml.length();
} else if (contentPlain != null) {
this.readingPosition = contentPlain.length();
}
}

View File

@@ -238,17 +238,20 @@ export default function StoryReadingPage() {
// Auto-scroll to saved reading position or URL hash when story content is loaded
useEffect(() => {
if (story && sanitizedContent && !hasScrolledToPosition) {
// Use a small delay to ensure content is rendered
const timeout = setTimeout(() => {
let cancelled = false;
const performScroll = () => {
if (cancelled) return;
debug.log('Initializing reading position tracking, saved position:', story.readingPosition);
// Check if there's a hash in the URL (for TOC navigation)
const hash = window.location.hash.substring(1);
if (hash && hash.startsWith('heading-')) {
debug.log('Auto-scrolling to heading from URL hash:', hash);
const element = document.getElementById(hash);
if (element) {
element.scrollIntoView({
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
@@ -256,7 +259,7 @@ export default function StoryReadingPage() {
return;
}
}
// Otherwise, use saved reading position
if (story.readingPosition && story.readingPosition > 0) {
debug.log('Auto-scrolling to saved position:', story.readingPosition);
@@ -269,9 +272,57 @@ export default function StoryReadingPage() {
setReadingPercentage(0);
setHasScrolledToPosition(true);
}
}, 500);
return () => clearTimeout(timeout);
};
// Wait for images to load before scrolling so scrollHeight is accurate
const waitForImagesAndScroll = () => {
if (!contentRef.current) {
// Content not in DOM yet, use a small delay
setTimeout(performScroll, 500);
return;
}
const images = contentRef.current.querySelectorAll('img');
const unloadedImages = Array.from(images).filter(img => !img.complete);
if (unloadedImages.length === 0) {
// No pending images, scroll after a brief delay for layout
debug.log('No pending images, scrolling immediately');
setTimeout(performScroll, 100);
return;
}
debug.log(`Waiting for ${unloadedImages.length} images to load before scrolling`);
let loadedCount = 0;
// Fallback timeout in case images take too long
const fallbackTimeout = setTimeout(() => {
debug.log('Image load timeout reached, scrolling anyway');
performScroll();
}, 5000);
const onImageReady = () => {
loadedCount++;
if (loadedCount >= unloadedImages.length) {
clearTimeout(fallbackTimeout);
// Small delay after last image loads for layout to settle
setTimeout(performScroll, 50);
}
};
unloadedImages.forEach(img => {
img.addEventListener('load', onImageReady, { once: true });
img.addEventListener('error', onImageReady, { once: true });
});
};
// Small delay to ensure content is in the DOM
const timeout = setTimeout(waitForImagesAndScroll, 100);
return () => {
cancelled = true;
clearTimeout(timeout);
};
}
}, [story, sanitizedContent, scrollToCharacterPosition, calculateReadingPercentage, hasScrolledToPosition]);

View File

@@ -39,8 +39,8 @@ export default function CollectionReadingView({
(scrolled - contentTop + windowHeight * 0.3) / contentHeight
));
// Convert to character position in the plain text content
const textLength = story.contentPlain?.length || story.contentHtml?.length || 0;
// Convert to character position (ALWAYS use contentHtml for consistency with backend)
const textLength = story.contentHtml?.length || 0;
return Math.floor(scrollRatio * textLength);
}, [story]);
@@ -48,7 +48,7 @@ export default function CollectionReadingView({
const calculateReadingPercentage = useCallback((currentPosition: number): number => {
if (!story) return 0;
const totalLength = story.contentPlain?.length || story.contentHtml?.length || 0;
const totalLength = story.contentHtml?.length || 0;
if (totalLength === 0) return 0;
return Math.round((currentPosition / totalLength) * 100);
@@ -58,7 +58,7 @@ export default function CollectionReadingView({
const scrollToCharacterPosition = useCallback((position: number) => {
if (!contentRef.current || !story || hasScrolledToPosition) return;
const textLength = story.contentPlain?.length || story.contentHtml?.length || 0;
const textLength = story.contentHtml?.length || 0;
if (textLength === 0 || position === 0) return;
const ratio = position / textLength;
@@ -109,7 +109,11 @@ export default function CollectionReadingView({
// Auto-scroll to saved reading position when story content is loaded
useEffect(() => {
if (story && !hasScrolledToPosition) {
const timeout = setTimeout(() => {
let cancelled = false;
const performScroll = () => {
if (cancelled) return;
console.log('Collection view - initializing reading position tracking, saved position:', story.readingPosition);
if (story.readingPosition && story.readingPosition > 0) {
console.log('Collection view - auto-scrolling to saved position:', story.readingPosition);
@@ -121,9 +125,51 @@ export default function CollectionReadingView({
setReadingPercentage(0);
setHasScrolledToPosition(true);
}
}, 500);
return () => clearTimeout(timeout);
};
// Wait for images to load before scrolling so scrollHeight is accurate
const waitForImagesAndScroll = () => {
if (!contentRef.current) {
setTimeout(performScroll, 500);
return;
}
const images = contentRef.current.querySelectorAll('img');
const unloadedImages = Array.from(images).filter(img => !img.complete);
if (unloadedImages.length === 0) {
setTimeout(performScroll, 100);
return;
}
console.log(`Collection view - waiting for ${unloadedImages.length} images to load before scrolling`);
let loadedCount = 0;
const fallbackTimeout = setTimeout(() => {
console.log('Collection view - image load timeout reached, scrolling anyway');
performScroll();
}, 5000);
const onImageReady = () => {
loadedCount++;
if (loadedCount >= unloadedImages.length) {
clearTimeout(fallbackTimeout);
setTimeout(performScroll, 50);
}
};
unloadedImages.forEach(img => {
img.addEventListener('load', onImageReady, { once: true });
img.addEventListener('error', onImageReady, { once: true });
});
};
const timeout = setTimeout(waitForImagesAndScroll, 100);
return () => {
cancelled = true;
clearTimeout(timeout);
};
}
}, [story, scrollToCharacterPosition, calculateReadingPercentage, hasScrolledToPosition]);

File diff suppressed because one or more lines are too long

Binary file not shown.