-
Notifications
You must be signed in to change notification settings - Fork 515
/
Copy pathindex.tsx
106 lines (96 loc) · 3.23 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import React, { useState } from "react";
import "./index.scss";
import { Toc } from "../../../../../libs/types/document";
import { useFirstVisibleElement } from "../../hooks";
import { useGleanClick } from "../../../telemetry/glean-context";
import { TOC_CLICK } from "../../../telemetry/constants";
import { useLocale } from "../../../hooks";
import { DEFAULT_LOCALE } from "../../../../../libs/constants";
const DEFAULT_TITLE = {
de: "In diesem Artikel",
"en-US": "In this article",
es: "En este artículo",
fr: "Dans cet article",
ja: "この記事では",
ko: "목차",
"pt-BR": "Neste artigo",
ru: "В этой статье",
"zh-CN": "在本文中",
"zh-TW": "在本文中",
};
export function TOC({ toc, title }: { toc: Toc[]; title?: string }) {
const locale = useLocale();
const [currentViewedTocItem, setCurrentViewedTocItem] = useState("");
const observedElements = React.useCallback(() => {
const mainElement = document.querySelector("main") ?? document;
const elements = mainElement.querySelectorAll(
"h1, h1 ~ *:not(section), h2:not(.document-toc-heading), h2:not(.document-toc-heading) ~ *:not(section), h3, h3 ~ *:not(section)"
);
return Array.from(elements);
}, []);
const referencedIds = toc.map(({ id }) => id);
const idByObservedElement = React.useRef(new Map<Element, string>());
React.useEffect(() => {
observedElements().reduce((currentId, observedElement) => {
const observedId = observedElement.id.toLowerCase();
if (observedId && referencedIds.includes(observedId)) {
currentId = observedId;
}
idByObservedElement.current.set(observedElement, currentId);
return currentId;
}, "");
}, [observedElements, referencedIds]);
useFirstVisibleElement(observedElements, (element: Element | null) => {
const id = element ? (idByObservedElement.current.get(element) ?? "") : "";
if (id !== currentViewedTocItem) {
setCurrentViewedTocItem(id);
}
});
return (
<>
<div className="document-toc-container">
<section className="document-toc">
<header>
<h2 className="document-toc-heading">
{title || DEFAULT_TITLE[locale] || DEFAULT_TITLE[DEFAULT_LOCALE]}
</h2>
</header>
<ul className="document-toc-list">
{toc.map((item) => {
return (
<TOCItem
key={item.id}
id={item.id}
text={item.text}
sub={item.sub}
currentViewedTocItem={currentViewedTocItem}
/>
);
})}
</ul>
</section>
</div>
</>
);
}
function TOCItem({
id,
text,
sub,
currentViewedTocItem,
}: Toc & { currentViewedTocItem: string }) {
const gleanClick = useGleanClick();
const href = id && `#${id.toLowerCase()}`;
return (
<li className={`document-toc-item ${sub ? "document-toc-item-sub" : ""}`}>
<a
className="document-toc-link"
key={id}
aria-current={currentViewedTocItem === id?.toLowerCase() || undefined}
href={href}
dangerouslySetInnerHTML={{ __html: text }}
onClick={() => gleanClick(`${TOC_CLICK}: ${href}`)}
/>
</li>
);
}