mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Simplify top positioning
This commit is contained in:
parent
cc4b07a5ed
commit
8cf0a6ecb2
7 changed files with 20 additions and 81 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
import {
|
||||
getTopOmnivoreAnchorElement,
|
||||
parseDomTree,
|
||||
useReadingProgressAnchor,
|
||||
} from '../../../lib/hooks/useReadingProgressAnchor'
|
||||
import {
|
||||
|
|
@ -18,6 +19,7 @@ export type ArticleProps = {
|
|||
content: string
|
||||
initialAnchorIndex: number
|
||||
initialReadingProgress?: number
|
||||
initialReadingProgressTop?: number
|
||||
highlightHref: MutableRefObject<string | null>
|
||||
articleMutations: ArticleMutations
|
||||
}
|
||||
|
|
@ -93,6 +95,8 @@ export function Article(props: ArticleProps): JSX.Element {
|
|||
|
||||
setShouldScrollToInitialPosition(false)
|
||||
|
||||
parseDomTree(articleContentRef.current)
|
||||
|
||||
// If we are scrolling to a highlight, dont scroll to read position
|
||||
if (props.highlightHref.current) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
|
|||
content={props.article.content}
|
||||
highlightHref={highlightHref}
|
||||
initialAnchorIndex={props.article.readingProgressAnchorIndex}
|
||||
initialReadingProgressTop={props.article.readingProgressTopPercent}
|
||||
articleMutations={props.articleMutations}
|
||||
/>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const getTopOmnivoreAnchorElement = (
|
|||
): string | undefined => {
|
||||
let lastVisibleAnchor: Element | undefined = undefined
|
||||
const anchors = Array.from(
|
||||
articleContentElement.querySelectorAll(`[data-omnivore-anchor-idx]`)
|
||||
document.querySelectorAll(`[data-omnivore-anchor-idx]`)
|
||||
).reverse()
|
||||
|
||||
for (const anchor of anchors) {
|
||||
|
|
@ -35,87 +35,16 @@ export const getTopOmnivoreAnchorElement = (
|
|||
}
|
||||
|
||||
export const useReadingProgressAnchor = (
|
||||
articleContentRef: React.MutableRefObject<HTMLDivElement | null>,
|
||||
setReadingAnchorIndex: React.Dispatch<React.SetStateAction<number>>
|
||||
articleContentRef: React.MutableRefObject<HTMLDivElement | null>
|
||||
): void => {
|
||||
useEffect(() => {
|
||||
const visitedNodeList = parseDomTree(articleContentRef.current)
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
// we only track elements on becoming completely visible.
|
||||
threshold: [1],
|
||||
}
|
||||
|
||||
function intersectionCallback(entries: IntersectionObserverEntry[]): void {
|
||||
let topIntersectingElemId = 0
|
||||
let minTopElem = 100000
|
||||
entries.forEach(function (entry: IntersectionObserverEntry) {
|
||||
const elem = entry.target
|
||||
const elemId = elem.getAttribute('data-omnivore-anchor-idx') || '0'
|
||||
|
||||
if (entry.isIntersecting && entry.intersectionRatio === 1) {
|
||||
// Among all intersecting elements, find the topmost element.
|
||||
if (entry.boundingClientRect.top < minTopElem) {
|
||||
minTopElem = entry.boundingClientRect.top
|
||||
topIntersectingElemId = parseInt(elemId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (topIntersectingElemId > 0) {
|
||||
/*
|
||||
* Intersection observer is great in finding us the last element on the
|
||||
* page that becomes visible on scroll. But for better user experience
|
||||
* we are interested in the topmost element visible on the page that we
|
||||
* can scroll to at the top on the next article page reader view. We
|
||||
* iterate in reverse over anchor elements here us to find the topmost
|
||||
* visible element.
|
||||
*/
|
||||
let topVisibleElemId = topIntersectingElemId
|
||||
while (topVisibleElemId - 1 > 0) {
|
||||
const elem = document.querySelector(
|
||||
`[data-omnivore-anchor-idx='${(topVisibleElemId - 1).toString()}']`
|
||||
)
|
||||
if (elem) {
|
||||
const rect = elem.getBoundingClientRect()
|
||||
if (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <=
|
||||
(window.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.right <=
|
||||
(window.innerWidth || document.documentElement.clientWidth)
|
||||
) {
|
||||
/* Is Visible */
|
||||
topVisibleElemId = topVisibleElemId - 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// Prevents the Event loop from the eternal blocking
|
||||
throw new Error('Unable to find previous intersection element!')
|
||||
}
|
||||
}
|
||||
setReadingAnchorIndex(topVisibleElemId)
|
||||
}
|
||||
}
|
||||
|
||||
const nodeObserver = new IntersectionObserver(
|
||||
intersectionCallback,
|
||||
observerOptions
|
||||
)
|
||||
visitedNodeList?.forEach((elem) => {
|
||||
nodeObserver.observe(elem)
|
||||
})
|
||||
|
||||
return () => {
|
||||
nodeObserver.disconnect()
|
||||
}
|
||||
}, [articleContentRef, setReadingAnchorIndex])
|
||||
parseDomTree(articleContentRef.current)
|
||||
}, [articleContentRef])
|
||||
}
|
||||
|
||||
function parseDomTree(pageNode: HTMLDivElement | null): HTMLDivElement[] {
|
||||
export function parseDomTree(
|
||||
pageNode: HTMLDivElement | null
|
||||
): HTMLDivElement[] {
|
||||
if (!pageNode || pageNode.childNodes.length == 0) {
|
||||
return []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const articleFragment = gql`
|
|||
contentReader
|
||||
originalArticleUrl
|
||||
readingProgressPercent
|
||||
readingProgressTopPercent
|
||||
readingProgressAnchorIndex
|
||||
slug
|
||||
isArchived
|
||||
|
|
@ -52,6 +53,7 @@ export type ArticleFragmentData = {
|
|||
contentReader?: ContentReader
|
||||
originalArticleUrl: string
|
||||
readingProgressPercent: number
|
||||
readingProgressTopPercent?: number
|
||||
readingProgressAnchorIndex: number
|
||||
slug: string
|
||||
isArchived: boolean
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export async function searchQuery({
|
|||
limit = 10,
|
||||
searchQuery,
|
||||
}: LibraryItemsQueryInput): Promise<LibraryItemsData | undefined> {
|
||||
const query = gql`
|
||||
const query = gql`
|
||||
query Search($after: String, $first: Int, $query: String) {
|
||||
search(first: $first, after: $after, query: $query) {
|
||||
... on SearchSuccess {
|
||||
|
|
@ -27,6 +27,7 @@ export async function searchQuery({
|
|||
createdAt
|
||||
isArchived
|
||||
readingProgressPercent
|
||||
readingProgressTopPercent
|
||||
readingProgressAnchorIndex
|
||||
author
|
||||
image
|
||||
|
|
@ -71,8 +72,8 @@ export async function searchQuery({
|
|||
}
|
||||
|
||||
try {
|
||||
const data = (await gqlFetcher(query, {...variables}))
|
||||
return data as LibraryItemsData || undefined;
|
||||
const data = await gqlFetcher(query, { ...variables })
|
||||
return (data as LibraryItemsData) || undefined
|
||||
} catch (error) {
|
||||
console.log('search error', error)
|
||||
return undefined
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export type ArticleAttributes = {
|
|||
description?: string
|
||||
contentReader: ContentReader
|
||||
readingProgressPercent: number
|
||||
readingProgressTopPercent?: number
|
||||
readingProgressAnchorIndex: number
|
||||
slug: string
|
||||
savedByViewer?: boolean
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export type LibraryItemNode = {
|
|||
contentReader?: ContentReader
|
||||
originalArticleUrl: string
|
||||
readingProgressPercent: number
|
||||
readingProgressTopPercent?: number
|
||||
readingProgressAnchorIndex: number
|
||||
slug: string
|
||||
isArchived: boolean
|
||||
|
|
|
|||
Loading…
Reference in a new issue