Skip to content

Commit f51dfdd

Browse files
committed
fix(toc): should focus when toc item clicked
Signed-off-by: Innei <i@innei.in>
1 parent 2ba5886 commit f51dfdd

File tree

4 files changed

+36
-4
lines changed

4 files changed

+36
-4
lines changed

apps/renderer/src/components/ui/markdown/components/Toc.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,19 @@ export interface ITocItem {
4242
$heading: HTMLHeadingElement
4343
}
4444

45+
interface TocProps {
46+
onItemClick?: (index: number, $el: HTMLElement | null, anchorId: string) => void
47+
}
48+
4549
const WiderTocStyle = {
4650
width: 200,
4751
} satisfies React.CSSProperties
48-
export const Toc: Component = ({ className }) => {
52+
export const Toc: Component<TocProps> = ({ className, onItemClick }) => {
4953
const markdownElement = useContext(MarkdownRenderContainerRefContext)
5054
const { toc, rootDepth } = useTocItems(markdownElement)
51-
const { currentScrollRange, handleScrollTo } = useScrollTracking(toc)
55+
const { currentScrollRange, handleScrollTo } = useScrollTracking(toc, {
56+
onItemClick,
57+
})
5258

5359
const renderContentElementPosition = useWrappedElementPosition()
5460
const renderContentElementSize = useWrappedElementSize()
@@ -269,7 +275,7 @@ const useTocItems = (markdownElement: HTMLElement | null) => {
269275
return { toc, rootDepth }
270276
}
271277

272-
const useScrollTracking = (toc: ITocItem[]) => {
278+
const useScrollTracking = (toc: ITocItem[], options: Pick<TocProps, "onItemClick">) => {
273279
const scrollContainerElement = useScrollViewElement()
274280
const [currentScrollRange, setCurrentScrollRange] = useState([-1, 0] as [number, number])
275281
const { h } = useWrappedElementSize()
@@ -361,6 +367,7 @@ const useScrollTracking = (toc: ITocItem[]) => {
361367

362368
const handleScrollTo = useEventCallback(
363369
(i: number, $el: HTMLElement | null, _anchorId: string) => {
370+
options.onItemClick?.(i, $el, _anchorId)
364371
if ($el) {
365372
const handle = () => {
366373
springScrollToElement($el, -100, scrollContainerElement!).then(() => {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { EventBus } from "@follow/utils/event-bus"
2+
import { useEffect } from "react"
3+
4+
declare module "@follow/utils/event-bus" {
5+
export interface CustomEvent {
6+
FOCUS_ENTRY_CONTAINER: never
7+
}
8+
}
9+
10+
export const useFocusEntryContainerSubscriptions = (ref: React.RefObject<HTMLDivElement>) => {
11+
useEffect(() => {
12+
return EventBus.subscribe("FOCUS_ENTRY_CONTAINER", () => {
13+
ref.current?.focus()
14+
})
15+
}, [ref])
16+
}

apps/renderer/src/modules/entry-content/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useTitle } from "@follow/hooks"
66
import type { FeedModel, InboxModel } from "@follow/models/types"
77
import { IN_ELECTRON } from "@follow/shared/constants"
88
import { stopPropagation } from "@follow/utils/dom"
9+
import { EventBus } from "@follow/utils/event-bus"
910
import { cn } from "@follow/utils/utils"
1011
import type { FallbackRender } from "@sentry/react"
1112
import { ErrorBoundary } from "@sentry/react"
@@ -51,6 +52,7 @@ import { EntryTitle } from "./components/EntryTitle"
5152
import { SourceContentPanel } from "./components/SourceContentView"
5253
import { SupportCreator } from "./components/SupportCreator"
5354
import { EntryHeader } from "./header"
55+
import { useFocusEntryContainerSubscriptions } from "./hooks"
5456
import { EntryContentLoading } from "./loading"
5557

5658
export interface EntryContentClassNames {
@@ -129,6 +131,7 @@ export const EntryContentRender: Component<{
129131
() => (isPeekModal ? undefined : <ContainerToc key={entryId} />),
130132
[entryId, isPeekModal],
131133
)
134+
useFocusEntryContainerSubscriptions(scrollerRef)
132135
const stableRenderStyle = useMemo(
133136
() =>
134137
readerFontFamily
@@ -457,11 +460,15 @@ const RenderError: FallbackRender = ({ error }) => {
457460

458461
const ContainerToc: FC = memo(() => {
459462
const wrappedElement = useWrappedElement()
463+
460464
return (
461465
<RootPortal to={wrappedElement!}>
462466
<div className="group absolute right-[-130px] top-0 h-full w-[100px]">
463467
<div className="sticky top-0">
464468
<Toc
469+
onItemClick={() => {
470+
EventBus.dispatch("FOCUS_ENTRY_CONTAINER")
471+
}}
465472
className={cn(
466473
"flex flex-col items-end animate-in fade-in-0 slide-in-from-bottom-12 easing-spring spring-soft",
467474
"max-h-[calc(100vh-100px)] overflow-auto scrollbar-none",

packages/utils/src/event-bus.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ class EventBusEvent extends Event {
1313
}
1414
type AnyObject = Record<string, any>
1515
class EventBusStatic<E extends AnyObject> {
16-
dispatch<T extends keyof E>(event: T, data: E[T]) {
16+
dispatch<T extends keyof E>(event: T, data: E[T]): void
17+
dispatch<T extends keyof E>(event: T): void
18+
dispatch<T extends keyof E>(event: T, data?: E[T]) {
1719
window.dispatchEvent(new EventBusEvent(event as string, data))
1820
}
1921

0 commit comments

Comments
 (0)