mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Save bottom of doc as readingPercentage and save top most visible element as the anchor index
This commit is contained in:
parent
4fd4d5a4db
commit
0e73a62914
5 changed files with 87 additions and 56 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
|
|
|
|||
Loading…
Reference in a new issue