Save bottom of doc as readingPercentage and save top most visible element as the anchor index

This commit is contained in:
Jackson Harper 2023-02-15 17:05:43 +08:00
parent 4fd4d5a4db
commit 0e73a62914
5 changed files with 87 additions and 56 deletions

View file

@ -519,9 +519,7 @@ struct WebReaderContainerView: View {
try? WebViewManager.shared().dispatchEvent(.saveReadPosition)
}
.onDisappear {
try? WebViewManager.shared().dispatchEvent(.saveReadPosition)
// Clear the shared webview content when exiting
// WebViewManager.shared().loadHTMLString("<html></html>", baseURL: nil)
WebViewManager.shared().loadHTMLString("<html></html>", baseURL: nil)
}
}

View file

@ -9,7 +9,6 @@ extension DataService {
guard let self = self else { return }
guard let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.backgroundContext) else { return }
print("updateLinkReadingProgress", readingProgress, anchorIndex)
linkedItem.update(
inContext: self.backgroundContext,
newReadingProgress: readingProgress,

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,8 @@
import { Box } from '../../elements/LayoutPrimitives'
import { useReadingProgressAnchor } from '../../../lib/hooks/useReadingProgressAnchor'
import {
getTopOmnivoreAnchorElement,
useReadingProgressAnchor,
} from '../../../lib/hooks/useReadingProgressAnchor'
import {
ScrollOffsetChangeset,
useScrollWatcher,
@ -15,8 +18,9 @@ import {
import { Tweet } from 'react-twitter-widgets'
import { render } from 'react-dom'
import { isDarkTheme } from '../../../lib/themeUpdater'
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
import { ArticleMutations } from '../../../lib/articleActions'
import { ArticleReadingProgressMutationInput } from '../../../lib/networking/mutations/articleReadingProgressMutation'
export type ArticleProps = {
articleId: string
@ -29,53 +33,71 @@ export type ArticleProps = {
export function Article(props: ArticleProps): JSX.Element {
const highlightTheme = isDarkTheme() ? 'dark' : 'default'
const articleContentRef = useRef<HTMLDivElement | null>(null)
const [readingProgress, setReadingProgress] = useState(
props.initialReadingProgress
)
const [readingAnchorIndex, setReadingAnchorIndex] = useState(
props.initialAnchorIndex
)
const [shouldScrollToInitialPosition, setShouldScrollToInitialPosition] =
useState(true)
const articleContentRef = useRef<HTMLDivElement | null>(null)
useReadingProgressAnchor(articleContentRef, setReadingAnchorIndex)
const debouncedSetReadingProgress = useMemo(
const throttledSetReadingProgress = useMemo(
() =>
debounce((readingProgress: number) => {
setReadingProgress(readingProgress)
}, 2000),
[]
throttle(
(readingProgress: number) => {
syncReadingProgress(
props.articleId,
readingProgress,
props.articleMutations.articleReadingProgressMutation
)
setReadingProgress(readingProgress)
},
2000,
{ leading: true }
),
[
props.articleId,
props.articleMutations.articleReadingProgressMutation,
readingProgress,
setReadingProgress,
]
)
// Stop the invocation of the debounced function
// after unmounting
// Flush the invocation of the throttled function when unmounting
useEffect(() => {
return () => {
debouncedSetReadingProgress.cancel()
throttledSetReadingProgress.flush()
}
}, [])
}, [readingProgress, setReadingProgress])
useEffect(() => {
const syncReadingProgress = (
articleId: string,
readingProgress: number,
mutation: (input: ArticleReadingProgressMutationInput) => Promise<boolean>
) => {
;(async () => {
if (!readingProgress) return
await props.articleMutations.articleReadingProgressMutation({
id: props.articleId,
// round reading progress to 100% if more than that
readingProgressPercent: readingProgress > 100 ? 100 : readingProgress,
readingProgressAnchorIndex: readingAnchorIndex,
})
})()
// We don't react to changes to readingAnchorIndex we
// only care about the progress (scroll position) changed.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.articleId, readingProgress])
const newAnchorIndex = articleContentRef.current
? getTopOmnivoreAnchorElement(articleContentRef.current)
: undefined
const adjustedReadingProgress = Math.min(
100,
(readingProgress > 0.92 ? 1 : readingProgress) * 100
)
console.log('reading progress: ', readingProgress, newAnchorIndex)
if (newAnchorIndex && !Number.isNaN(Number(newAnchorIndex)))
await mutation({
id: articleId,
// round reading progress to 100% if more than that
readingProgressPercent: adjustedReadingProgress,
readingProgressAnchorIndex: Number(newAnchorIndex),
})
})()
}
// Post message to webkit so apple app embeds get progress updates
// TODO: verify if ios still needs this code...seeems to be duplicated
@ -92,9 +114,8 @@ export function Article(props: ArticleProps): JSX.Element {
const newReadingProgress =
(window.scrollY + window.innerHeight) /
window.document.scrollingElement.scrollHeight
const adjustedReadingProgress =
newReadingProgress > 0.92 ? 1 : newReadingProgress
debouncedSetReadingProgress(adjustedReadingProgress * 100)
throttledSetReadingProgress(newReadingProgress)
}
}, 1000)
@ -120,22 +141,6 @@ export function Article(props: ArticleProps): JSX.Element {
[]
)
useEffect(() => {
const saveReadPosition = () => {
console.log(
'saving read position from article: ',
readingProgress,
readingAnchorIndex
)
}
document.addEventListener('saveReadPosition', saveReadPosition)
return () => {
document.removeEventListener('saveReadPosition', saveReadPosition)
}
}, [readingProgress, readingAnchorIndex])
// Scroll to initial anchor position
useEffect(() => {
if (typeof window === 'undefined') {

View file

@ -6,6 +6,35 @@ const ANCHOR_ELEMENTS_BLOCKED_ATTRIBUTES = [
'data-instagram-id',
]
// We search in reverse so we can find the last element
// that is visible on the page
export const getTopOmnivoreAnchorElement = (
articleContentElement: HTMLDivElement
): string | undefined => {
var lastVisibleAnchor: Element | undefined = undefined
const anchors = Array.from(
articleContentElement.querySelectorAll(`[data-omnivore-anchor-idx]`)
).reverse()
for (const anchor of anchors) {
const rect = anchor.getBoundingClientRect()
if (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= articleContentElement.clientHeight
) {
lastVisibleAnchor = anchor
} else if (lastVisibleAnchor) {
break
}
}
console.log('last', lastVisibleAnchor)
return (
lastVisibleAnchor?.getAttribute(`data-omnivore-anchor-idx`) ?? undefined
)
}
export const useReadingProgressAnchor = (
articleContentRef: React.MutableRefObject<HTMLDivElement | null>,
setReadingAnchorIndex: React.Dispatch<React.SetStateAction<number>>