@astrojs/starlight 0.5.6 → 0.6.0
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/404.astro +17 -17
- package/CHANGELOG.md +40 -2
- package/components/CallToAction.astro +32 -35
- package/components/ContentPanel.astro +18 -18
- package/components/EditLink.astro +23 -23
- package/components/FallbackContentNotice.astro +16 -18
- package/components/Footer.astro +28 -34
- package/components/HeadSEO.astro +69 -75
- package/components/Header.astro +47 -51
- package/components/Hero.astro +108 -121
- package/components/Icons.ts +73 -71
- package/components/LanguageSelect.astro +31 -31
- package/components/LastUpdated.astro +16 -18
- package/components/MarkdownContent.astro +105 -117
- package/components/MobileMenuToggle.astro +80 -80
- package/components/PrevNextLinks.astro +60 -60
- package/components/RightSidebar.astro +17 -17
- package/components/RightSidebarPanel.astro +41 -41
- package/components/Search.astro +293 -306
- package/components/Select.astro +64 -65
- package/components/Sidebar.astro +23 -23
- package/components/SidebarSublist.astro +96 -95
- package/components/SiteTitle.astro +65 -63
- package/components/SkipLink.astro +17 -17
- package/components/SocialIcons.astro +36 -34
- package/components/TableOfContents/MobileTableOfContents.astro +124 -124
- package/components/TableOfContents/TableOfContentsList.astro +64 -69
- package/components/TableOfContents/generateToC.ts +41 -43
- package/components/TableOfContents/starlight-toc.ts +84 -90
- package/components/TableOfContents.astro +8 -8
- package/components/ThemeProvider.astro +28 -32
- package/components/ThemeSelect.astro +66 -71
- package/global.d.ts +3 -3
- package/index.astro +1 -1
- package/index.ts +52 -59
- package/integrations/asides.ts +109 -111
- package/integrations/sitemap.ts +10 -13
- package/integrations/virtual-user-config.ts +37 -37
- package/layout/Page.astro +84 -72
- package/layout/PageFrame.astro +67 -69
- package/layout/TwoColumnContent.astro +40 -42
- package/package.json +2 -2
- package/schema.ts +126 -113
- package/schemas/favicon.ts +40 -0
- package/schemas/head.ts +12 -23
- package/schemas/i18n.ts +146 -160
- package/schemas/logo.ts +22 -22
- package/schemas/prevNextLink.ts +14 -14
- package/schemas/tableOfContents.ts +14 -18
- package/style/asides.css +27 -27
- package/style/props.css +168 -172
- package/style/reset.css +13 -13
- package/style/shiki.css +11 -11
- package/style/util.css +30 -30
- package/translations/fr.json +21 -21
- package/translations/index.ts +4 -4
- package/translations/it.json +20 -20
- package/translations/tr.json +1 -1
- package/translations/zh.json +0 -1
- package/user-components/Card.astro +50 -54
- package/user-components/CardGrid.astro +21 -21
- package/user-components/Icon.astro +19 -21
- package/user-components/TabItem.astro +3 -3
- package/user-components/Tabs.astro +113 -117
- package/user-components/rehype-tabs.ts +72 -72
- package/utils/base.ts +6 -6
- package/utils/git.ts +55 -55
- package/utils/head.ts +56 -54
- package/utils/i18n.ts +3 -3
- package/utils/localizedUrl.ts +25 -25
- package/utils/navigation.ts +225 -203
- package/utils/routing.ts +74 -85
- package/utils/slugs.ts +41 -51
- package/utils/translations.ts +26 -32
- package/utils/user-config.ts +278 -278
- package/virtual.d.ts +8 -8
|
@@ -5,46 +5,48 @@ import Icon from '../user-components/Icon.astro';
|
|
|
5
5
|
type Platform = keyof NonNullable<typeof config.social>;
|
|
6
6
|
|
|
7
7
|
const labels: Record<Platform, string> = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
github: 'GitHub',
|
|
9
|
+
gitlab: 'GitLab',
|
|
10
|
+
discord: 'Discord',
|
|
11
|
+
twitter: 'Twitter',
|
|
12
|
+
mastodon: 'Mastodon',
|
|
13
|
+
codeberg: 'Codeberg',
|
|
14
|
+
youtube: 'YouTube',
|
|
15
|
+
threads: 'Threads',
|
|
16
|
+
linkedin: 'LinkedIn',
|
|
17
|
+
twitch: 'Twitch',
|
|
17
18
|
};
|
|
18
19
|
|
|
19
|
-
const links = Object.entries(config.social || {}).filter(([, url]) =>
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const links = Object.entries(config.social || {}).filter(([, url]) => Boolean(url)) as [
|
|
21
|
+
platform: Platform,
|
|
22
|
+
url: string,
|
|
23
|
+
][];
|
|
22
24
|
---
|
|
23
25
|
|
|
24
26
|
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
links.length > 0 && (
|
|
28
|
+
<>
|
|
29
|
+
{links.map(([platform, url]) => (
|
|
30
|
+
<a href={url} rel="me">
|
|
31
|
+
<span class="sr-only">{labels[platform]}</span>
|
|
32
|
+
<Icon name={platform} />
|
|
33
|
+
</a>
|
|
34
|
+
))}
|
|
35
|
+
<div class="divider" />
|
|
36
|
+
</>
|
|
37
|
+
)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
<style>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
a {
|
|
42
|
+
display: flex;
|
|
43
|
+
color: var(--sl-color-text-accent);
|
|
44
|
+
}
|
|
45
|
+
a:hover {
|
|
46
|
+
opacity: 0.66;
|
|
47
|
+
}
|
|
48
|
+
.divider {
|
|
49
|
+
height: 2rem;
|
|
50
|
+
border-inline-end: 1px solid var(--sl-color-gray-5);
|
|
51
|
+
}
|
|
50
52
|
</style>
|
|
@@ -5,10 +5,10 @@ import TableOfContentsList from './TableOfContentsList.astro';
|
|
|
5
5
|
import type { TocItem } from './generateToC';
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
toc: TocItem[];
|
|
9
|
+
locale: string | undefined;
|
|
10
|
+
maxHeadingLevel: number;
|
|
11
|
+
minHeadingLevel: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const { locale, toc, maxHeadingLevel, minHeadingLevel } = Astro.props;
|
|
@@ -16,138 +16,138 @@ const t = useTranslations(locale);
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
<mobile-starlight-toc data-min-h={minHeadingLevel} data-max-h={maxHeadingLevel}>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
<nav aria-labelledby="starlight__on-this-page--mobile" class="lg:hidden">
|
|
20
|
+
<details id="starlight__mobile-toc">
|
|
21
|
+
<summary id="starlight__on-this-page--mobile" class="flex">
|
|
22
|
+
<div class="toggle flex">
|
|
23
|
+
{t('tableOfContents.onThisPage')}
|
|
24
|
+
<Icon name={'right-caret'} class="caret" size="1rem" />
|
|
25
|
+
</div>
|
|
26
|
+
<span class="display-current">{toc[0]?.text}</span>
|
|
27
|
+
</summary>
|
|
28
|
+
<div class="dropdown">
|
|
29
|
+
<TableOfContentsList toc={toc} isMobile />
|
|
30
|
+
</div>
|
|
31
|
+
</details>
|
|
32
|
+
</nav>
|
|
33
33
|
</mobile-starlight-toc>
|
|
34
34
|
|
|
35
35
|
<style>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
nav {
|
|
37
|
+
position: fixed;
|
|
38
|
+
top: calc(var(--sl-nav-height) - 1px);
|
|
39
|
+
inset-inline: 0;
|
|
40
|
+
border-top: 1px solid var(--sl-color-gray-5);
|
|
41
|
+
background-color: var(--sl-color-bg-nav);
|
|
42
|
+
}
|
|
43
|
+
@media (min-width: 50rem) {
|
|
44
|
+
nav {
|
|
45
|
+
inset-inline-start: var(--sl-content-inline-start, 0);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
summary {
|
|
50
|
+
gap: 0.5rem;
|
|
51
|
+
align-items: center;
|
|
52
|
+
height: var(--sl-mobile-toc-height);
|
|
53
|
+
border-bottom: 1px solid var(--sl-color-hairline-shade);
|
|
54
|
+
padding: 0.5rem 1rem;
|
|
55
|
+
font-size: var(--sl-text-xs);
|
|
56
|
+
outline-offset: var(--sl-outline-offset-inside);
|
|
57
|
+
}
|
|
58
|
+
summary::marker,
|
|
59
|
+
summary::-webkit-details-marker {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
63
|
+
.toggle {
|
|
64
|
+
flex-shrink: 0;
|
|
65
|
+
gap: 1rem;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: space-between;
|
|
68
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
69
|
+
border-radius: 0.5rem;
|
|
70
|
+
padding-block: 0.5rem;
|
|
71
|
+
padding-inline-start: 0.75rem;
|
|
72
|
+
padding-inline-end: 0.5rem;
|
|
73
|
+
line-height: 1;
|
|
74
|
+
background-color: var(--sl-color-black);
|
|
75
|
+
user-select: none;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
}
|
|
78
|
+
details[open] .toggle {
|
|
79
|
+
color: var(--sl-color-white);
|
|
80
|
+
border-color: var(--sl-color-accent);
|
|
81
|
+
}
|
|
82
|
+
details .toggle:hover {
|
|
83
|
+
color: var(--sl-color-white);
|
|
84
|
+
border-color: var(--sl-color-gray-2);
|
|
85
|
+
}
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
:global([dir='rtl']) .caret {
|
|
88
|
+
transform: rotateZ(180deg);
|
|
89
|
+
}
|
|
90
|
+
details[open] .caret {
|
|
91
|
+
transform: rotateZ(90deg);
|
|
92
|
+
}
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
.display-current {
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
text-overflow: ellipsis;
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
color: var(--sl-color-white);
|
|
99
|
+
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
.dropdown {
|
|
102
|
+
--border-top: 1px;
|
|
103
|
+
margin-top: calc(-1 * var(--border-top));
|
|
104
|
+
border: var(--border-top) solid var(--sl-color-gray-6);
|
|
105
|
+
border-top-color: var(--sl-color-hairline-shade);
|
|
106
|
+
max-height: calc(85vh - var(--sl-nav-height) - var(--sl-mobile-toc-height));
|
|
107
|
+
overflow-y: auto;
|
|
108
|
+
background-color: var(--sl-color-black);
|
|
109
|
+
box-shadow: var(--sl-shadow-md);
|
|
110
|
+
}
|
|
111
111
|
</style>
|
|
112
112
|
|
|
113
113
|
<script>
|
|
114
|
-
|
|
114
|
+
import { StarlightTOC } from './starlight-toc';
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
class MobileStarlightTOC extends StarlightTOC {
|
|
117
|
+
override set current(link: HTMLAnchorElement) {
|
|
118
|
+
super.current = link;
|
|
119
|
+
const display = this.querySelector('.display-current') as HTMLSpanElement;
|
|
120
|
+
if (display) display.textContent = link.textContent;
|
|
121
|
+
}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
123
|
+
constructor() {
|
|
124
|
+
super({ smallViewport: true });
|
|
125
|
+
const details = this.querySelector('details');
|
|
126
|
+
if (!details) return;
|
|
127
|
+
const closeToC = () => {
|
|
128
|
+
details.open = false;
|
|
129
|
+
};
|
|
130
|
+
// Close the table of contents whenever a link is clicked.
|
|
131
|
+
details.querySelectorAll('a').forEach((a) => {
|
|
132
|
+
a.addEventListener('click', closeToC);
|
|
133
|
+
});
|
|
134
|
+
// Close the table of contents when a user clicks outside of it.
|
|
135
|
+
window.addEventListener('click', (e) => {
|
|
136
|
+
if (!details.contains(e.target as Node)) closeToC();
|
|
137
|
+
});
|
|
138
|
+
// Or when they press the escape key.
|
|
139
|
+
window.addEventListener('keydown', (e) => {
|
|
140
|
+
if (e.key === 'Escape' && details.open) {
|
|
141
|
+
const hasFocus = details.contains(document.activeElement);
|
|
142
|
+
closeToC();
|
|
143
|
+
if (hasFocus) {
|
|
144
|
+
const summary = details.querySelector('summary');
|
|
145
|
+
if (summary) summary.focus();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
customElements.define('mobile-starlight-toc', MobileStarlightTOC);
|
|
153
153
|
</script>
|
|
@@ -2,82 +2,77 @@
|
|
|
2
2
|
import type { TocItem } from './generateToC';
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
toc: TocItem[];
|
|
6
|
+
depth?: number;
|
|
7
|
+
isMobile?: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const { toc, isMobile = false, depth = 0 } = Astro.props;
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
<ul class:list={{ isMobile }}>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
)}
|
|
27
|
-
</li>
|
|
28
|
-
))
|
|
29
|
-
}
|
|
14
|
+
{
|
|
15
|
+
toc.map((heading) => (
|
|
16
|
+
<li>
|
|
17
|
+
<a href={'#' + heading.slug} aria-current={heading.current && 'true'}>
|
|
18
|
+
<span>{heading.text}</span>
|
|
19
|
+
</a>
|
|
20
|
+
{heading.children.length > 0 && (
|
|
21
|
+
<Astro.self toc={heading.children} depth={depth + 1} isMobile={isMobile} />
|
|
22
|
+
)}
|
|
23
|
+
</li>
|
|
24
|
+
))
|
|
25
|
+
}
|
|
30
26
|
</ul>
|
|
31
27
|
|
|
32
28
|
<style define:vars={{ depth }}>
|
|
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
|
-
}
|
|
29
|
+
ul {
|
|
30
|
+
padding: 0;
|
|
31
|
+
list-style: none;
|
|
32
|
+
}
|
|
33
|
+
a {
|
|
34
|
+
--pad-inline: 0.5rem;
|
|
35
|
+
display: block;
|
|
36
|
+
border-radius: 0.25rem;
|
|
37
|
+
padding-block: 0.25rem;
|
|
38
|
+
padding-inline: calc(1rem * var(--depth) + var(--pad-inline)) var(--pad-inline);
|
|
39
|
+
line-height: 1.25;
|
|
40
|
+
}
|
|
41
|
+
a[aria-current='true'],
|
|
42
|
+
a[aria-current='true']:hover,
|
|
43
|
+
a[aria-current='true']:focus {
|
|
44
|
+
font-weight: 600;
|
|
45
|
+
color: var(--sl-color-text-invert);
|
|
46
|
+
background-color: var(--sl-color-text-accent);
|
|
47
|
+
}
|
|
48
|
+
.isMobile a {
|
|
49
|
+
--pad-inline: 1rem;
|
|
50
|
+
display: flex;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
gap: var(--pad-inline);
|
|
53
|
+
border-top: 1px solid var(--sl-color-gray-6);
|
|
54
|
+
border-radius: 0;
|
|
55
|
+
padding-block: 0.5rem;
|
|
56
|
+
color: var(--sl-color-text);
|
|
57
|
+
font-size: var(--sl-text-sm);
|
|
58
|
+
text-decoration: none;
|
|
59
|
+
outline-offset: var(--sl-outline-offset-inside);
|
|
60
|
+
}
|
|
61
|
+
.isMobile:first-child > li:first-child > a {
|
|
62
|
+
border-top: 0;
|
|
63
|
+
}
|
|
64
|
+
.isMobile a[aria-current='true'],
|
|
65
|
+
.isMobile a[aria-current='true']:hover,
|
|
66
|
+
.isMobile a[aria-current='true']:focus {
|
|
67
|
+
color: var(--sl-color-white);
|
|
68
|
+
background-color: unset;
|
|
69
|
+
}
|
|
70
|
+
.isMobile a[aria-current='true']::after {
|
|
71
|
+
content: '';
|
|
72
|
+
width: 1rem;
|
|
73
|
+
background-color: var(--sl-color-text-accent);
|
|
74
|
+
/* Check mark SVG icon */
|
|
75
|
+
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxNCAxNCc+PHBhdGggZD0nTTEwLjkxNCA0LjIwNmEuNTgzLjU4MyAwIDAgMC0uODI4IDBMNS43NCA4LjU1NyAzLjkxNCA2LjcyNmEuNTk2LjU5NiAwIDAgMC0uODI4Ljg1N2wyLjI0IDIuMjRhLjU4My41ODMgMCAwIDAgLjgyOCAwbDQuNzYtNC43NmEuNTgzLjU4MyAwIDAgMCAwLS44NTdaJy8+PC9zdmc+Cg==');
|
|
76
|
+
mask-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxNCAxNCc+PHBhdGggZD0nTTEwLjkxNCA0LjIwNmEuNTgzLjU4MyAwIDAgMC0uODI4IDBMNS43NCA4LjU1NyAzLjkxNCA2LjcyNmEuNTk2LjU5NiAwIDAgMC0uODI4Ljg1N2wyLjI0IDIuMjRhLjU4My41ODMgMCAwIDAgLjgyOCAwbDQuNzYtNC43NmEuNTgzLjU4MyAwIDAgMCAwLS44NTdaJy8+PC9zdmc+Cg==');
|
|
77
|
+
}
|
|
83
78
|
</style>
|
|
@@ -1,58 +1,56 @@
|
|
|
1
1
|
import type { MarkdownHeading } from 'astro';
|
|
2
2
|
|
|
3
3
|
export interface TocItem extends MarkdownHeading {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
children: TocItem[];
|
|
5
|
+
current?: boolean;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function diveChildren(item: TocItem, depth: number): TocItem[] {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
if (depth === 1) {
|
|
10
|
+
return item.children;
|
|
11
|
+
} else if (item.children.length > 0) {
|
|
12
|
+
return diveChildren(item.children.at(-1)!, depth - 1);
|
|
13
|
+
} else {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
interface TocOpts {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
minHeadingLevel: number;
|
|
20
|
+
maxHeadingLevel: number;
|
|
21
|
+
title?: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function generateToC(
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
headings: MarkdownHeading[],
|
|
26
|
+
{ minHeadingLevel, maxHeadingLevel, title = 'Overview' }: TocOpts
|
|
27
27
|
) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
];
|
|
35
|
-
const toc: Array<TocItem> = [];
|
|
28
|
+
const overview = { depth: 2, slug: '_top', text: title };
|
|
29
|
+
headings = [
|
|
30
|
+
overview,
|
|
31
|
+
...headings.filter(({ depth }) => depth >= minHeadingLevel && depth <= maxHeadingLevel),
|
|
32
|
+
];
|
|
33
|
+
const toc: Array<TocItem> = [];
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
35
|
+
for (const heading of headings) {
|
|
36
|
+
if (toc.length === 0) {
|
|
37
|
+
toc.push({ ...heading, children: [], current: true });
|
|
38
|
+
} else {
|
|
39
|
+
const lastItemInToc = toc.at(-1)!;
|
|
40
|
+
if (heading.depth < lastItemInToc.depth) {
|
|
41
|
+
throw new Error(`Orphan heading found: ${heading.text}.`);
|
|
42
|
+
}
|
|
43
|
+
if (heading.depth === lastItemInToc.depth) {
|
|
44
|
+
// same depth
|
|
45
|
+
toc.push({ ...heading, children: [] });
|
|
46
|
+
} else {
|
|
47
|
+
// higher depth
|
|
48
|
+
// push into children, or children's children alike
|
|
49
|
+
const gap = heading.depth - lastItemInToc.depth;
|
|
50
|
+
const target = diveChildren(lastItemInToc, gap);
|
|
51
|
+
target.push({ ...heading, children: [] });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return toc;
|
|
58
56
|
}
|