potential fix for not jumping to reading position sometimes
This commit is contained in:
@@ -307,10 +307,11 @@ public class Story {
|
|||||||
this.isRead = true;
|
this.isRead = true;
|
||||||
this.lastReadAt = LocalDateTime.now();
|
this.lastReadAt = LocalDateTime.now();
|
||||||
// Set reading position to the end of content if available
|
// Set reading position to the end of content if available
|
||||||
if (contentPlain != null) {
|
// ALWAYS use contentHtml for consistency (frontend uses contentHtml for position tracking)
|
||||||
this.readingPosition = contentPlain.length();
|
if (contentHtml != null) {
|
||||||
} else if (contentHtml != null) {
|
|
||||||
this.readingPosition = contentHtml.length();
|
this.readingPosition = contentHtml.length();
|
||||||
|
} else if (contentPlain != null) {
|
||||||
|
this.readingPosition = contentPlain.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,8 +238,11 @@ export default function StoryReadingPage() {
|
|||||||
// Auto-scroll to saved reading position or URL hash when story content is loaded
|
// Auto-scroll to saved reading position or URL hash when story content is loaded
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (story && sanitizedContent && !hasScrolledToPosition) {
|
if (story && sanitizedContent && !hasScrolledToPosition) {
|
||||||
// Use a small delay to ensure content is rendered
|
let cancelled = false;
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
|
const performScroll = () => {
|
||||||
|
if (cancelled) return;
|
||||||
|
|
||||||
debug.log('Initializing reading position tracking, saved position:', story.readingPosition);
|
debug.log('Initializing reading position tracking, saved position:', story.readingPosition);
|
||||||
|
|
||||||
// Check if there's a hash in the URL (for TOC navigation)
|
// Check if there's a hash in the URL (for TOC navigation)
|
||||||
@@ -269,9 +272,57 @@ export default function StoryReadingPage() {
|
|||||||
setReadingPercentage(0);
|
setReadingPercentage(0);
|
||||||
setHasScrolledToPosition(true);
|
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]);
|
}, [story, sanitizedContent, scrollToCharacterPosition, calculateReadingPercentage, hasScrolledToPosition]);
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export default function CollectionReadingView({
|
|||||||
(scrolled - contentTop + windowHeight * 0.3) / contentHeight
|
(scrolled - contentTop + windowHeight * 0.3) / contentHeight
|
||||||
));
|
));
|
||||||
|
|
||||||
// Convert to character position in the plain text content
|
// Convert to character position (ALWAYS use contentHtml for consistency with backend)
|
||||||
const textLength = story.contentPlain?.length || story.contentHtml?.length || 0;
|
const textLength = story.contentHtml?.length || 0;
|
||||||
return Math.floor(scrollRatio * textLength);
|
return Math.floor(scrollRatio * textLength);
|
||||||
}, [story]);
|
}, [story]);
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ export default function CollectionReadingView({
|
|||||||
const calculateReadingPercentage = useCallback((currentPosition: number): number => {
|
const calculateReadingPercentage = useCallback((currentPosition: number): number => {
|
||||||
if (!story) return 0;
|
if (!story) return 0;
|
||||||
|
|
||||||
const totalLength = story.contentPlain?.length || story.contentHtml?.length || 0;
|
const totalLength = story.contentHtml?.length || 0;
|
||||||
if (totalLength === 0) return 0;
|
if (totalLength === 0) return 0;
|
||||||
|
|
||||||
return Math.round((currentPosition / totalLength) * 100);
|
return Math.round((currentPosition / totalLength) * 100);
|
||||||
@@ -58,7 +58,7 @@ export default function CollectionReadingView({
|
|||||||
const scrollToCharacterPosition = useCallback((position: number) => {
|
const scrollToCharacterPosition = useCallback((position: number) => {
|
||||||
if (!contentRef.current || !story || hasScrolledToPosition) return;
|
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;
|
if (textLength === 0 || position === 0) return;
|
||||||
|
|
||||||
const ratio = position / textLength;
|
const ratio = position / textLength;
|
||||||
@@ -109,7 +109,11 @@ export default function CollectionReadingView({
|
|||||||
// Auto-scroll to saved reading position when story content is loaded
|
// Auto-scroll to saved reading position when story content is loaded
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (story && !hasScrolledToPosition) {
|
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);
|
console.log('Collection view - initializing reading position tracking, saved position:', story.readingPosition);
|
||||||
if (story.readingPosition && story.readingPosition > 0) {
|
if (story.readingPosition && story.readingPosition > 0) {
|
||||||
console.log('Collection view - auto-scrolling to saved position:', story.readingPosition);
|
console.log('Collection view - auto-scrolling to saved position:', story.readingPosition);
|
||||||
@@ -121,9 +125,51 @@ export default function CollectionReadingView({
|
|||||||
setReadingPercentage(0);
|
setReadingPercentage(0);
|
||||||
setHasScrolledToPosition(true);
|
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]);
|
}, [story, scrollToCharacterPosition, calculateReadingPercentage, hasScrolledToPosition]);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Reference in New Issue
Block a user