Skip to content

Commit

Permalink
refactor: toc scroller logic
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Aug 29, 2024
1 parent 5d4e958 commit 09950e0
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/renderer/src/components/ui/markdown/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const HTML = <A extends keyof JSX.IntrinsicElements = "div">(
return (
<MarkdownRenderContainerRefContext.Provider value={ref.current}>
{createElement(as, { ...rest, ref }, markdownElement)}
{accessory}
{accessory && <span key={children}>{accessory}</span>}
</MarkdownRenderContainerRefContext.Provider>
)
}
30 changes: 20 additions & 10 deletions src/renderer/src/components/ui/markdown/components/Toc.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { getViewport } from "@renderer/atoms/hooks/viewport"
import { springScrollToElement } from "@renderer/lib/scroller"
import { cn } from "@renderer/lib/utils"
import { useGetWrappedElementPosition } from "@renderer/providers/wrapped-element-provider"
import { throttle } from "lodash-es"
import {
memo,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react"
import { memo, useContext, useEffect, useMemo, useRef, useState } from "react"
import { useEventCallback } from "usehooks-ts"

import { useScrollViewElement } from "../../scroll-area/hooks"
Expand Down Expand Up @@ -109,11 +103,17 @@ export const Toc: Component = ({ className }) => {
if (!scrollContainerElement) return

const handler = throttle(() => {
const top = scrollContainerElement.scrollTop + getWrappedElPos().y
const { y } = getWrappedElPos()
const top = scrollContainerElement.scrollTop + y
const winHeight = getViewport().h
const deltaHeight =
top >= winHeight ? winHeight : (top / winHeight) * winHeight

const actualTop = Math.floor(Math.max(0, top - y + deltaHeight)) || 0

// current top is in which range?
const currentRangeIndex = titleBetweenPositionTopRangeMap.findIndex(
([start, end]) => top >= start && top <= end,
([start, end]) => actualTop >= start && actualTop <= end,
)
const currentRange = titleBetweenPositionTopRangeMap[currentRangeIndex]

Expand All @@ -125,6 +125,16 @@ export const Toc: Component = ({ className }) => {

// position , precent
setCurrentScrollRange([currentRangeIndex, precent])
} else {
const last = titleBetweenPositionTopRangeMap.at(-1) || [0, 0]
if (top > last[1]) {
setCurrentScrollRange([
titleBetweenPositionTopRangeMap.length - 1,
1,
])
} else {
setCurrentScrollRange([-1, 0])
}
}
}, 100)
scrollContainerElement.addEventListener("scroll", handler)
Expand Down
22 changes: 18 additions & 4 deletions src/renderer/src/components/ui/markdown/renderers/Heading.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { springScrollToElement } from "@renderer/lib/scroller"
import { cn } from "@renderer/lib/utils"
import { useWrappedElementSize } from "@renderer/providers/wrapped-element-provider"
import { useContext, useId, useLayoutEffect, useRef, useState } from "react"

import { useScrollViewElement } from "../../scroll-area/hooks"
Expand All @@ -23,13 +24,16 @@ export const createHeadingRenderer =
const ref = useRef<HTMLHeadingElement>(null)

const [currentTitleTop, setCurrentTitleTop] = useState(0)
const { h } = useWrappedElementSize()
useLayoutEffect(() => {
const $heading = ref.current
if (!$heading) return
const { top } = $heading.getBoundingClientRect()
// eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-layout-effect
setCurrentTitleTop(top | 0)
}, [])
// const { top } = $heading.getBoundingClientRect()
// // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-layout-effect
// setCurrentTitleTop(top | 0)
const top = getElementTop($heading)
setCurrentTitleTop(top)
}, [h])

return (
<As
Expand Down Expand Up @@ -65,3 +69,13 @@ export const createHeadingRenderer =
</As>
)
}

const getElementTop = (element: HTMLElement) => {
let actualTop = element.offsetTop
let current = element.offsetParent as HTMLElement
while (current !== null) {
actualTop += current.offsetTop
current = current.offsetParent as HTMLElement
}
return actualTop
}
11 changes: 3 additions & 8 deletions src/renderer/src/modules/entry-content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({
{!isInReadabilityMode ? (
<ShadowDOM>
<HTML
accessory={<ContainerToc entryId={entryId} />}
accessory={<ContainerToc key={entryId} />}
as="article"
className="prose dark:prose-invert prose-h1:text-[1.6em]"
renderInlineStyle={readerRenderInlineStyle}
Expand Down Expand Up @@ -437,18 +437,13 @@ const RenderError: FallbackRender = ({ error }) => {
)
}

const ContainerToc: FC<{
entryId: string
}> = (props) => {
const ContainerToc: FC = () => {
const wrappedElement = useWrappedElement()
return (
<RootPortal to={wrappedElement!}>
<div className="absolute right-[-130px] top-0 h-full w-[100px]">
<div className="sticky top-0">
<Toc
className="flex flex-col items-end animate-in fade-in-0 slide-in-from-bottom-12 easing-spring-soft @[900px]:items-start"
key={props.entryId}
/>
<Toc className="flex flex-col items-end animate-in fade-in-0 slide-in-from-bottom-12 easing-spring-soft @[900px]:items-start" />
</div>
</div>
</RootPortal>
Expand Down

0 comments on commit 09950e0

Please sign in to comment.