boltdocs 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{cache-EHR7SXRU.mjs → cache-GQHF6BXI.mjs} +1 -1
- package/dist/{chunk-GSYECEZY.mjs → chunk-CYBWLFOG.mjs} +5 -1
- package/dist/node/index.js +36 -20
- package/dist/node/index.mjs +34 -22
- package/package.json +1 -1
- package/src/client/app/index.tsx +344 -344
- package/src/client/app/preload.tsx +56 -56
- package/src/client/index.ts +40 -40
- package/src/client/ssr.tsx +51 -51
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -76
- package/src/client/theme/components/CodeBlock/index.ts +1 -1
- package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -154
- package/src/client/theme/components/PackageManagerTabs/index.ts +1 -1
- package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -64
- package/src/client/theme/components/Playground/Playground.tsx +124 -124
- package/src/client/theme/components/Playground/index.ts +1 -1
- package/src/client/theme/components/Playground/playground.css +168 -168
- package/src/client/theme/components/Video/Video.tsx +84 -84
- package/src/client/theme/components/Video/index.ts +1 -1
- package/src/client/theme/components/Video/video.css +41 -41
- package/src/client/theme/components/mdx/Admonition.tsx +80 -80
- package/src/client/theme/components/mdx/Badge.tsx +31 -31
- package/src/client/theme/components/mdx/Button.tsx +50 -50
- package/src/client/theme/components/mdx/Card.tsx +80 -80
- package/src/client/theme/components/mdx/List.tsx +57 -57
- package/src/client/theme/components/mdx/Tabs.tsx +94 -94
- package/src/client/theme/components/mdx/index.ts +18 -18
- package/src/client/theme/components/mdx/mdx-components.css +424 -424
- package/src/client/theme/icons/bun.tsx +62 -62
- package/src/client/theme/icons/deno.tsx +20 -20
- package/src/client/theme/icons/discord.tsx +12 -12
- package/src/client/theme/icons/github.tsx +15 -15
- package/src/client/theme/icons/npm.tsx +13 -13
- package/src/client/theme/icons/pnpm.tsx +72 -72
- package/src/client/theme/icons/twitter.tsx +12 -12
- package/src/client/theme/styles/markdown.css +343 -343
- package/src/client/theme/styles/variables.css +162 -162
- package/src/client/theme/styles.css +37 -37
- package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -10
- package/src/client/theme/ui/BackgroundGradient/index.ts +1 -1
- package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -68
- package/src/client/theme/ui/Breadcrumbs/index.ts +1 -1
- package/src/client/theme/ui/Footer/footer.css +32 -32
- package/src/client/theme/ui/Head/Head.tsx +69 -69
- package/src/client/theme/ui/Head/index.ts +1 -1
- package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -125
- package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -1
- package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -98
- package/src/client/theme/ui/Layout/Layout.tsx +202 -202
- package/src/client/theme/ui/Layout/base.css +76 -76
- package/src/client/theme/ui/Layout/index.ts +2 -2
- package/src/client/theme/ui/Layout/pagination.css +72 -72
- package/src/client/theme/ui/Layout/responsive.css +36 -36
- package/src/client/theme/ui/Link/Link.tsx +254 -254
- package/src/client/theme/ui/Link/index.ts +2 -2
- package/src/client/theme/ui/Loading/Loading.tsx +10 -10
- package/src/client/theme/ui/Loading/index.ts +1 -1
- package/src/client/theme/ui/Loading/loading.css +30 -30
- package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -27
- package/src/client/theme/ui/Navbar/Navbar.tsx +145 -145
- package/src/client/theme/ui/Navbar/index.ts +2 -2
- package/src/client/theme/ui/Navbar/navbar.css +233 -233
- package/src/client/theme/ui/NotFound/NotFound.tsx +19 -19
- package/src/client/theme/ui/NotFound/index.ts +1 -1
- package/src/client/theme/ui/NotFound/not-found.css +64 -64
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +235 -235
- package/src/client/theme/ui/OnThisPage/index.ts +1 -1
- package/src/client/theme/ui/OnThisPage/toc.css +132 -132
- package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -18
- package/src/client/theme/ui/PoweredBy/index.ts +1 -1
- package/src/client/theme/ui/PoweredBy/powered-by.css +76 -76
- package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -199
- package/src/client/theme/ui/SearchDialog/index.ts +1 -1
- package/src/client/theme/ui/SearchDialog/search.css +152 -152
- package/src/client/theme/ui/Sidebar/Sidebar.tsx +204 -204
- package/src/client/theme/ui/Sidebar/index.ts +1 -1
- package/src/client/theme/ui/Sidebar/sidebar.css +236 -236
- package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -69
- package/src/client/theme/ui/ThemeToggle/index.ts +1 -1
- package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -136
- package/src/client/theme/ui/VersionSwitcher/index.ts +1 -1
- package/src/client/types.ts +50 -50
- package/src/client/utils.ts +26 -26
- package/src/node/cache.ts +408 -408
- package/src/node/config.ts +192 -192
- package/src/node/index.ts +21 -21
- package/src/node/mdx.ts +120 -120
- package/src/node/plugin/entry.ts +58 -58
- package/src/node/plugin/html.ts +55 -55
- package/src/node/plugin/index.ts +193 -193
- package/src/node/plugin/types.ts +11 -11
- package/src/node/routes/cache.ts +28 -28
- package/src/node/routes/index.ts +167 -167
- package/src/node/routes/parser.ts +153 -127
- package/src/node/routes/sorter.ts +42 -42
- package/src/node/routes/types.ts +49 -49
- package/src/node/ssg/index.ts +114 -114
- package/src/node/ssg/meta.ts +33 -34
- package/src/node/ssg/options.ts +13 -13
- package/src/node/ssg/sitemap.ts +55 -54
- package/src/node/utils.ts +145 -134
- package/tsconfig.json +20 -20
- package/tsup.config.ts +22 -22
|
@@ -1,235 +1,235 @@
|
|
|
1
|
-
import React, { useEffect, useState, useRef, useCallback } from "react";
|
|
2
|
-
import { useLocation } from "react-router-dom";
|
|
3
|
-
import { Pencil, CircleHelp } from "lucide-react";
|
|
4
|
-
|
|
5
|
-
interface TocHeading {
|
|
6
|
-
id: string;
|
|
7
|
-
text: string;
|
|
8
|
-
level: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function OnThisPage({
|
|
12
|
-
headings = [],
|
|
13
|
-
editLink,
|
|
14
|
-
communityHelp,
|
|
15
|
-
filePath,
|
|
16
|
-
}: {
|
|
17
|
-
headings?: TocHeading[];
|
|
18
|
-
editLink?: string;
|
|
19
|
-
communityHelp?: string;
|
|
20
|
-
filePath?: string;
|
|
21
|
-
}) {
|
|
22
|
-
const [activeId, setActiveId] = useState<string>("");
|
|
23
|
-
const [indicatorStyle, setIndicatorStyle] = useState<React.CSSProperties>({});
|
|
24
|
-
const observerRef = useRef<IntersectionObserver | null>(null);
|
|
25
|
-
const location = useLocation();
|
|
26
|
-
const listRef = useRef<HTMLUListElement>(null);
|
|
27
|
-
|
|
28
|
-
// Reset active ID when path changes
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (headings.length > 0) {
|
|
31
|
-
// Check if there's a hash in the URL
|
|
32
|
-
const hash = window.location.hash.substring(1);
|
|
33
|
-
if (hash && headings.some((h) => h.id === hash)) {
|
|
34
|
-
setActiveId(hash);
|
|
35
|
-
} else {
|
|
36
|
-
setActiveId(headings[0].id);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}, [location.pathname, headings]);
|
|
40
|
-
|
|
41
|
-
// Update indicator position
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (!activeId || !listRef.current) return;
|
|
44
|
-
|
|
45
|
-
const activeElement = listRef.current.querySelector(
|
|
46
|
-
`a[href="#${activeId}"]`,
|
|
47
|
-
) as HTMLElement;
|
|
48
|
-
|
|
49
|
-
if (activeElement) {
|
|
50
|
-
const { offsetTop, offsetHeight } = activeElement;
|
|
51
|
-
setIndicatorStyle({
|
|
52
|
-
transform: `translateY(${offsetTop}px)`,
|
|
53
|
-
height: `${offsetHeight}px`,
|
|
54
|
-
opacity: 1,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}, [activeId, headings]);
|
|
58
|
-
|
|
59
|
-
// IntersectionObserver for active heading tracking
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (headings.length === 0) return;
|
|
62
|
-
|
|
63
|
-
if (observerRef.current) {
|
|
64
|
-
observerRef.current.disconnect();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const callback: IntersectionObserverCallback = (entries) => {
|
|
68
|
-
// Find all entries that are intersecting
|
|
69
|
-
const visibleEntries = entries.filter((entry) => entry.isIntersecting);
|
|
70
|
-
|
|
71
|
-
if (visibleEntries.length > 0) {
|
|
72
|
-
// If we have visible entries, find the one closest to the top of the viewport
|
|
73
|
-
const closest = visibleEntries.reduce((prev, curr) => {
|
|
74
|
-
return Math.abs(curr.boundingClientRect.top - 100) <
|
|
75
|
-
Math.abs(prev.boundingClientRect.top - 100)
|
|
76
|
-
? curr
|
|
77
|
-
: prev;
|
|
78
|
-
});
|
|
79
|
-
setActiveId(closest.target.id);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
observerRef.current = new IntersectionObserver(callback, {
|
|
84
|
-
rootMargin: "-100px 0px -70% 0px",
|
|
85
|
-
threshold: [0, 1],
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const observeHeadings = () => {
|
|
89
|
-
headings.forEach(({ id }) => {
|
|
90
|
-
const el = document.getElementById(id);
|
|
91
|
-
if (el) {
|
|
92
|
-
observerRef.current!.observe(el);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Initial observation
|
|
98
|
-
observeHeadings();
|
|
99
|
-
|
|
100
|
-
// Re-observe if content changes
|
|
101
|
-
const timeoutId = setTimeout(observeHeadings, 1000);
|
|
102
|
-
|
|
103
|
-
// Scroll listener to detect bottom of page
|
|
104
|
-
const handleScroll = () => {
|
|
105
|
-
const scrollPosition = window.innerHeight + window.pageYOffset;
|
|
106
|
-
const bodyHeight = document.documentElement.scrollHeight;
|
|
107
|
-
|
|
108
|
-
// If we're within 50px of the bottom, activate the last heading
|
|
109
|
-
if (scrollPosition >= bodyHeight - 50) {
|
|
110
|
-
setActiveId(headings[headings.length - 1].id);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
115
|
-
|
|
116
|
-
return () => {
|
|
117
|
-
observerRef.current?.disconnect();
|
|
118
|
-
clearTimeout(timeoutId);
|
|
119
|
-
window.removeEventListener("scroll", handleScroll);
|
|
120
|
-
};
|
|
121
|
-
}, [headings, location.pathname]);
|
|
122
|
-
|
|
123
|
-
// Autoscroll TOC list when activeId changes
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (!activeId || !listRef.current) return;
|
|
126
|
-
|
|
127
|
-
const activeLink = listRef.current.querySelector(
|
|
128
|
-
`a[href="#${activeId}"]`,
|
|
129
|
-
) as HTMLElement;
|
|
130
|
-
|
|
131
|
-
if (activeLink) {
|
|
132
|
-
const container = listRef.current.closest(
|
|
133
|
-
".boltdocs-on-this-page",
|
|
134
|
-
) as HTMLElement;
|
|
135
|
-
if (!container) return;
|
|
136
|
-
|
|
137
|
-
const linkRect = activeLink.getBoundingClientRect();
|
|
138
|
-
const containerRect = container.getBoundingClientRect();
|
|
139
|
-
|
|
140
|
-
const isVisible =
|
|
141
|
-
linkRect.top >= containerRect.top &&
|
|
142
|
-
linkRect.bottom <= containerRect.bottom;
|
|
143
|
-
|
|
144
|
-
if (!isVisible) {
|
|
145
|
-
activeLink.scrollIntoView({
|
|
146
|
-
behavior: "smooth",
|
|
147
|
-
block: "nearest",
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}, [activeId]);
|
|
152
|
-
|
|
153
|
-
const handleClick = useCallback(
|
|
154
|
-
(e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
|
|
155
|
-
e.preventDefault();
|
|
156
|
-
const el = document.getElementById(id);
|
|
157
|
-
if (el) {
|
|
158
|
-
const offset = 80;
|
|
159
|
-
const bodyRect = document.body.getBoundingClientRect().top;
|
|
160
|
-
const elementRect = el.getBoundingClientRect().top;
|
|
161
|
-
const elementPosition = elementRect - bodyRect;
|
|
162
|
-
const offsetPosition = elementPosition - offset;
|
|
163
|
-
|
|
164
|
-
window.scrollTo({
|
|
165
|
-
top: offsetPosition,
|
|
166
|
-
behavior: "smooth",
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
setActiveId(id);
|
|
170
|
-
window.history.pushState(null, "", `#${id}`);
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
[],
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
if (headings.length === 0) return null;
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
<nav className="boltdocs-on-this-page" aria-label="Table of contents">
|
|
180
|
-
<p className="on-this-page-title">On this page</p>
|
|
181
|
-
<div className="on-this-page-container">
|
|
182
|
-
<div className="toc-indicator" style={indicatorStyle} />
|
|
183
|
-
<ul className="on-this-page-list" ref={listRef}>
|
|
184
|
-
{headings.map((h) => (
|
|
185
|
-
<li key={h.id} className={h.level === 3 ? "toc-indent" : ""}>
|
|
186
|
-
<a
|
|
187
|
-
href={`#${h.id}`}
|
|
188
|
-
className={`toc-link ${activeId === h.id ? "active" : ""}`}
|
|
189
|
-
aria-current={activeId === h.id ? "true" : undefined}
|
|
190
|
-
onClick={(e) => handleClick(e, h.id)}
|
|
191
|
-
>
|
|
192
|
-
{h.text}
|
|
193
|
-
</a>
|
|
194
|
-
</li>
|
|
195
|
-
))}
|
|
196
|
-
</ul>
|
|
197
|
-
</div>
|
|
198
|
-
|
|
199
|
-
{/* Need help? section */}
|
|
200
|
-
{(editLink || communityHelp) && (
|
|
201
|
-
<div className="toc-help">
|
|
202
|
-
<p className="toc-help-title">Need help?</p>
|
|
203
|
-
<ul className="toc-help-links">
|
|
204
|
-
{editLink && filePath && (
|
|
205
|
-
<li>
|
|
206
|
-
<a
|
|
207
|
-
href={editLink.replace(":path", filePath)}
|
|
208
|
-
target="_blank"
|
|
209
|
-
rel="noopener noreferrer"
|
|
210
|
-
className="toc-help-link"
|
|
211
|
-
>
|
|
212
|
-
<Pencil size={16} />
|
|
213
|
-
Edit this page
|
|
214
|
-
</a>
|
|
215
|
-
</li>
|
|
216
|
-
)}
|
|
217
|
-
{communityHelp && (
|
|
218
|
-
<li>
|
|
219
|
-
<a
|
|
220
|
-
href={communityHelp}
|
|
221
|
-
target="_blank"
|
|
222
|
-
rel="noopener noreferrer"
|
|
223
|
-
className="toc-help-link"
|
|
224
|
-
>
|
|
225
|
-
<CircleHelp size={16} />
|
|
226
|
-
Community help
|
|
227
|
-
</a>
|
|
228
|
-
</li>
|
|
229
|
-
)}
|
|
230
|
-
</ul>
|
|
231
|
-
</div>
|
|
232
|
-
)}
|
|
233
|
-
</nav>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
1
|
+
import React, { useEffect, useState, useRef, useCallback } from "react";
|
|
2
|
+
import { useLocation } from "react-router-dom";
|
|
3
|
+
import { Pencil, CircleHelp } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
interface TocHeading {
|
|
6
|
+
id: string;
|
|
7
|
+
text: string;
|
|
8
|
+
level: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function OnThisPage({
|
|
12
|
+
headings = [],
|
|
13
|
+
editLink,
|
|
14
|
+
communityHelp,
|
|
15
|
+
filePath,
|
|
16
|
+
}: {
|
|
17
|
+
headings?: TocHeading[];
|
|
18
|
+
editLink?: string;
|
|
19
|
+
communityHelp?: string;
|
|
20
|
+
filePath?: string;
|
|
21
|
+
}) {
|
|
22
|
+
const [activeId, setActiveId] = useState<string>("");
|
|
23
|
+
const [indicatorStyle, setIndicatorStyle] = useState<React.CSSProperties>({});
|
|
24
|
+
const observerRef = useRef<IntersectionObserver | null>(null);
|
|
25
|
+
const location = useLocation();
|
|
26
|
+
const listRef = useRef<HTMLUListElement>(null);
|
|
27
|
+
|
|
28
|
+
// Reset active ID when path changes
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (headings.length > 0) {
|
|
31
|
+
// Check if there's a hash in the URL
|
|
32
|
+
const hash = window.location.hash.substring(1);
|
|
33
|
+
if (hash && headings.some((h) => h.id === hash)) {
|
|
34
|
+
setActiveId(hash);
|
|
35
|
+
} else {
|
|
36
|
+
setActiveId(headings[0].id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, [location.pathname, headings]);
|
|
40
|
+
|
|
41
|
+
// Update indicator position
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!activeId || !listRef.current) return;
|
|
44
|
+
|
|
45
|
+
const activeElement = listRef.current.querySelector(
|
|
46
|
+
`a[href="#${activeId}"]`,
|
|
47
|
+
) as HTMLElement;
|
|
48
|
+
|
|
49
|
+
if (activeElement) {
|
|
50
|
+
const { offsetTop, offsetHeight } = activeElement;
|
|
51
|
+
setIndicatorStyle({
|
|
52
|
+
transform: `translateY(${offsetTop}px)`,
|
|
53
|
+
height: `${offsetHeight}px`,
|
|
54
|
+
opacity: 1,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}, [activeId, headings]);
|
|
58
|
+
|
|
59
|
+
// IntersectionObserver for active heading tracking
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (headings.length === 0) return;
|
|
62
|
+
|
|
63
|
+
if (observerRef.current) {
|
|
64
|
+
observerRef.current.disconnect();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const callback: IntersectionObserverCallback = (entries) => {
|
|
68
|
+
// Find all entries that are intersecting
|
|
69
|
+
const visibleEntries = entries.filter((entry) => entry.isIntersecting);
|
|
70
|
+
|
|
71
|
+
if (visibleEntries.length > 0) {
|
|
72
|
+
// If we have visible entries, find the one closest to the top of the viewport
|
|
73
|
+
const closest = visibleEntries.reduce((prev, curr) => {
|
|
74
|
+
return Math.abs(curr.boundingClientRect.top - 100) <
|
|
75
|
+
Math.abs(prev.boundingClientRect.top - 100)
|
|
76
|
+
? curr
|
|
77
|
+
: prev;
|
|
78
|
+
});
|
|
79
|
+
setActiveId(closest.target.id);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
observerRef.current = new IntersectionObserver(callback, {
|
|
84
|
+
rootMargin: "-100px 0px -70% 0px",
|
|
85
|
+
threshold: [0, 1],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const observeHeadings = () => {
|
|
89
|
+
headings.forEach(({ id }) => {
|
|
90
|
+
const el = document.getElementById(id);
|
|
91
|
+
if (el) {
|
|
92
|
+
observerRef.current!.observe(el);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Initial observation
|
|
98
|
+
observeHeadings();
|
|
99
|
+
|
|
100
|
+
// Re-observe if content changes
|
|
101
|
+
const timeoutId = setTimeout(observeHeadings, 1000);
|
|
102
|
+
|
|
103
|
+
// Scroll listener to detect bottom of page
|
|
104
|
+
const handleScroll = () => {
|
|
105
|
+
const scrollPosition = window.innerHeight + window.pageYOffset;
|
|
106
|
+
const bodyHeight = document.documentElement.scrollHeight;
|
|
107
|
+
|
|
108
|
+
// If we're within 50px of the bottom, activate the last heading
|
|
109
|
+
if (scrollPosition >= bodyHeight - 50) {
|
|
110
|
+
setActiveId(headings[headings.length - 1].id);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
observerRef.current?.disconnect();
|
|
118
|
+
clearTimeout(timeoutId);
|
|
119
|
+
window.removeEventListener("scroll", handleScroll);
|
|
120
|
+
};
|
|
121
|
+
}, [headings, location.pathname]);
|
|
122
|
+
|
|
123
|
+
// Autoscroll TOC list when activeId changes
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!activeId || !listRef.current) return;
|
|
126
|
+
|
|
127
|
+
const activeLink = listRef.current.querySelector(
|
|
128
|
+
`a[href="#${activeId}"]`,
|
|
129
|
+
) as HTMLElement;
|
|
130
|
+
|
|
131
|
+
if (activeLink) {
|
|
132
|
+
const container = listRef.current.closest(
|
|
133
|
+
".boltdocs-on-this-page",
|
|
134
|
+
) as HTMLElement;
|
|
135
|
+
if (!container) return;
|
|
136
|
+
|
|
137
|
+
const linkRect = activeLink.getBoundingClientRect();
|
|
138
|
+
const containerRect = container.getBoundingClientRect();
|
|
139
|
+
|
|
140
|
+
const isVisible =
|
|
141
|
+
linkRect.top >= containerRect.top &&
|
|
142
|
+
linkRect.bottom <= containerRect.bottom;
|
|
143
|
+
|
|
144
|
+
if (!isVisible) {
|
|
145
|
+
activeLink.scrollIntoView({
|
|
146
|
+
behavior: "smooth",
|
|
147
|
+
block: "nearest",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [activeId]);
|
|
152
|
+
|
|
153
|
+
const handleClick = useCallback(
|
|
154
|
+
(e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
const el = document.getElementById(id);
|
|
157
|
+
if (el) {
|
|
158
|
+
const offset = 80;
|
|
159
|
+
const bodyRect = document.body.getBoundingClientRect().top;
|
|
160
|
+
const elementRect = el.getBoundingClientRect().top;
|
|
161
|
+
const elementPosition = elementRect - bodyRect;
|
|
162
|
+
const offsetPosition = elementPosition - offset;
|
|
163
|
+
|
|
164
|
+
window.scrollTo({
|
|
165
|
+
top: offsetPosition,
|
|
166
|
+
behavior: "smooth",
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
setActiveId(id);
|
|
170
|
+
window.history.pushState(null, "", `#${id}`);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
[],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (headings.length === 0) return null;
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<nav className="boltdocs-on-this-page" aria-label="Table of contents">
|
|
180
|
+
<p className="on-this-page-title">On this page</p>
|
|
181
|
+
<div className="on-this-page-container">
|
|
182
|
+
<div className="toc-indicator" style={indicatorStyle} />
|
|
183
|
+
<ul className="on-this-page-list" ref={listRef}>
|
|
184
|
+
{headings.map((h) => (
|
|
185
|
+
<li key={h.id} className={h.level === 3 ? "toc-indent" : ""}>
|
|
186
|
+
<a
|
|
187
|
+
href={`#${h.id}`}
|
|
188
|
+
className={`toc-link ${activeId === h.id ? "active" : ""}`}
|
|
189
|
+
aria-current={activeId === h.id ? "true" : undefined}
|
|
190
|
+
onClick={(e) => handleClick(e, h.id)}
|
|
191
|
+
>
|
|
192
|
+
{h.text}
|
|
193
|
+
</a>
|
|
194
|
+
</li>
|
|
195
|
+
))}
|
|
196
|
+
</ul>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{/* Need help? section */}
|
|
200
|
+
{(editLink || communityHelp) && (
|
|
201
|
+
<div className="toc-help">
|
|
202
|
+
<p className="toc-help-title">Need help?</p>
|
|
203
|
+
<ul className="toc-help-links">
|
|
204
|
+
{editLink && filePath && (
|
|
205
|
+
<li>
|
|
206
|
+
<a
|
|
207
|
+
href={editLink.replace(":path", filePath)}
|
|
208
|
+
target="_blank"
|
|
209
|
+
rel="noopener noreferrer"
|
|
210
|
+
className="toc-help-link"
|
|
211
|
+
>
|
|
212
|
+
<Pencil size={16} />
|
|
213
|
+
Edit this page
|
|
214
|
+
</a>
|
|
215
|
+
</li>
|
|
216
|
+
)}
|
|
217
|
+
{communityHelp && (
|
|
218
|
+
<li>
|
|
219
|
+
<a
|
|
220
|
+
href={communityHelp}
|
|
221
|
+
target="_blank"
|
|
222
|
+
rel="noopener noreferrer"
|
|
223
|
+
className="toc-help-link"
|
|
224
|
+
>
|
|
225
|
+
<CircleHelp size={16} />
|
|
226
|
+
Community help
|
|
227
|
+
</a>
|
|
228
|
+
</li>
|
|
229
|
+
)}
|
|
230
|
+
</ul>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
</nav>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { OnThisPage } from "./OnThisPage";
|
|
1
|
+
export { OnThisPage } from "./OnThisPage";
|