starlight-sidebar-topics-dropdown 0.4.1 → 0.5.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/CHANGELOG.md +64 -0
- package/components/TopicsDropdown.astro +281 -0
- package/index.ts +1 -94
- package/package.json +4 -6
- package/components/Topics.astro +0 -253
- package/components/starlight/Sidebar.astro +0 -28
- package/libs/config.ts +0 -82
- package/libs/content.ts +0 -15
- package/libs/i18n.ts +0 -27
- package/libs/locals.ts +0 -14
- package/libs/pathname.ts +0 -20
- package/libs/plugin.ts +0 -9
- package/libs/sidebar.ts +0 -210
- package/libs/starlight.ts +0 -24
- package/libs/vite.ts +0 -36
- package/middleware.ts +0 -43
- package/overrides/Sidebar.astro +0 -7
- package/schema.ts +0 -10
- package/starlight.d.ts +0 -5
- package/virtual.d.ts +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# starlight-sidebar-topics-dropdown
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#40](https://github.com/trueberryless-org/starlight-sidebar-topics-dropdown/pull/40) [`acf6f15`](https://github.com/trueberryless-org/starlight-sidebar-topics-dropdown/commit/acf6f156d4e8eaa00b2205dbb89befbd5dea27c7) Thanks [@trueberryless](https://github.com/trueberryless)! - ⚠️ **BREAKING CHANGE:** This plugin now uses the [Starlight Sidebar Topics](https://starlight-sidebar-topics.netlify.app/) plugin as a peer dependency. Please follow the upgrade guide below to migrate to the new version.
|
|
8
|
+
|
|
9
|
+
1. Install the [Starlight Sidebar Topics](https://starlight-sidebar-topics.netlify.app/) plugin:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm i starlight-sidebar-topics
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
2. Update the `starlight-sidebar-topics-dropdown` plugin in your `astro.config.mjs` (use the `starlight-sidebar-topics` plugin instead):
|
|
16
|
+
|
|
17
|
+
```diff lang="js"
|
|
18
|
+
// astro.config.mjs
|
|
19
|
+
-import starlightSidebarTopicsDropdown from "starlight-sidebar-topics-dropdown";
|
|
20
|
+
+import starlightSidebarTopics from "starlight-sidebar-topics";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
3. Exchange the `starlight-sidebar-topics-dropdown` plugin with the `starlight-sidebar-topics` plugin and add a manual override for the `Sidebar` component where you can use the dropdown component from the `starlight-sidebar-topics-dropdown` plugin:
|
|
24
|
+
|
|
25
|
+
```diff lang="js"
|
|
26
|
+
// astro.config.mjs
|
|
27
|
+
export default defineConfig({
|
|
28
|
+
integrations: [
|
|
29
|
+
starlight({
|
|
30
|
+
plugins: [
|
|
31
|
+
- starlightSidebarTopicsDropdown([
|
|
32
|
+
+ starlightSidebarTopics([
|
|
33
|
+
// Your Starlight Sidebar Topics configuration here (unchanged).
|
|
34
|
+
]),
|
|
35
|
+
],
|
|
36
|
+
+ components: {
|
|
37
|
+
+ Sidebar: './src/components/Sidebar.astro',
|
|
38
|
+
+ },
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
4. Create an Astro component to replace the default Starlight `<Sidebar>` component with which will render the topic list dropdown menu and [re-use the default Starlight sidebar](https://starlight.astro.build/guides/overriding-components/#reuse-a-built-in-component):
|
|
45
|
+
|
|
46
|
+
```astro
|
|
47
|
+
---
|
|
48
|
+
// src/components/Sidebar.astro
|
|
49
|
+
import Default from "@astrojs/starlight/components/Sidebar.astro";
|
|
50
|
+
import TopicsDropdown from "starlight-sidebar-topics-dropdown/TopicsDropdown.astro";
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
{/* Render the topics dropdown menu. */}
|
|
54
|
+
<TopicsDropdown />
|
|
55
|
+
{/* Render the default sidebar. */}
|
|
56
|
+
<Default><slot /></Default>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
5. Update the schema import in `src/content.config.ts`:
|
|
60
|
+
|
|
61
|
+
```diff lang="ts"
|
|
62
|
+
// src/content.config.ts
|
|
63
|
+
-import { topicSchema } from "starlight-sidebar-topics-dropdown/schema";
|
|
64
|
+
+import { topicSchema } from "starlight-sidebar-topics/schema";
|
|
65
|
+
```
|
|
66
|
+
|
|
3
67
|
## 0.4.1
|
|
4
68
|
|
|
5
69
|
### Patch Changes
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Badge, Icon } from "@astrojs/starlight/components";
|
|
3
|
+
import type { ComponentProps } from "astro/types";
|
|
4
|
+
|
|
5
|
+
const { topics } = Astro.locals.starlightSidebarTopics;
|
|
6
|
+
const currentTopic = topics.find((topic) => topic.isCurrent);
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="starlight-sidebar-topics-dropdown-dropdown">
|
|
10
|
+
<button
|
|
11
|
+
id="starlight-sidebar-topics-dropdown-button"
|
|
12
|
+
class="starlight-sidebar-topics-dropdown-button"
|
|
13
|
+
>
|
|
14
|
+
{
|
|
15
|
+
currentTopic.icon && (
|
|
16
|
+
<div class="starlight-sidebar-topics-dropdown-button-icon">
|
|
17
|
+
<Icon
|
|
18
|
+
name={currentTopic.icon as ComponentProps<typeof Icon>["name"]}
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
<span class="starlight-sidebar-topics-dropdown-button-label"
|
|
24
|
+
>{currentTopic.label}</span
|
|
25
|
+
>
|
|
26
|
+
{
|
|
27
|
+
currentTopic.badge && (
|
|
28
|
+
<Badge
|
|
29
|
+
class="starlight-sidebar-topics-dropdown-badge"
|
|
30
|
+
text={currentTopic.badge.text}
|
|
31
|
+
variant={currentTopic.badge.variant}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
<svg
|
|
36
|
+
width="16"
|
|
37
|
+
height="16"
|
|
38
|
+
viewBox="0 0 15 15"
|
|
39
|
+
fill="none"
|
|
40
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
41
|
+
class="h-4 w-4 opacity-50"
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
><path
|
|
44
|
+
d="M4.93179 5.43179C4.75605 5.60753 4.75605 5.89245 4.93179 6.06819C5.10753 6.24392 5.39245 6.24392 5.56819 6.06819L7.49999 4.13638L9.43179 6.06819C9.60753 6.24392 9.89245 6.24392 10.0682 6.06819C10.2439 5.89245 10.2439 5.60753 10.0682 5.43179L7.81819 3.18179C7.73379 3.0974 7.61933 3.04999 7.49999 3.04999C7.38064 3.04999 7.26618 3.0974 7.18179 3.18179L4.93179 5.43179ZM10.0682 9.56819C10.2439 9.39245 10.2439 9.10753 10.0682 8.93179C9.89245 8.75606 9.60753 8.75606 9.43179 8.93179L7.49999 10.8636L5.56819 8.93179C5.39245 8.75606 5.10753 8.75606 4.93179 8.93179C4.75605 9.10753 4.75605 9.39245 4.93179 9.56819L7.18179 11.8182C7.35753 11.9939 7.64245 11.9939 7.81819 11.8182L10.0682 9.56819Z"
|
|
45
|
+
fill="currentColor"
|
|
46
|
+
fill-rule="evenodd"
|
|
47
|
+
clip-rule="evenodd"></path></svg
|
|
48
|
+
>
|
|
49
|
+
</button>
|
|
50
|
+
|
|
51
|
+
<div
|
|
52
|
+
id="starlight-sidebar-topics-dropdown-menu"
|
|
53
|
+
class="starlight-sidebar-topics-dropdown-menu"
|
|
54
|
+
hidden
|
|
55
|
+
>
|
|
56
|
+
{
|
|
57
|
+
topics.map((topic) => {
|
|
58
|
+
return (
|
|
59
|
+
<a
|
|
60
|
+
href={topic.link}
|
|
61
|
+
class:list={{
|
|
62
|
+
"starlight-sidebar-topics-dropdown-item": true,
|
|
63
|
+
"starlight-sidebar-topics-dropdown-current-item": topic.isCurrent,
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{topic.icon && (
|
|
67
|
+
<div class="starlight-sidebar-topics-dropdown-icon">
|
|
68
|
+
<Icon
|
|
69
|
+
name={topic.icon as ComponentProps<typeof Icon>["name"]}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
<span class="starlight-sidebar-topics-dropdown-label">
|
|
74
|
+
{topic.label}
|
|
75
|
+
</span>
|
|
76
|
+
{topic.badge && (
|
|
77
|
+
<Badge
|
|
78
|
+
class="starlight-sidebar-topics-dropdown-badge"
|
|
79
|
+
text={topic.badge.text}
|
|
80
|
+
variant={topic.badge.variant}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
</a>
|
|
84
|
+
);
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<style>
|
|
91
|
+
.starlight-sidebar-topics-dropdown-dropdown {
|
|
92
|
+
position: relative;
|
|
93
|
+
display: block;
|
|
94
|
+
width: calc(100% - 2 * 2px);
|
|
95
|
+
margin-inline: 2px;
|
|
96
|
+
margin-top: 4px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.starlight-sidebar-topics-dropdown-button {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
background-color: var(--sl-color-gray-6);
|
|
103
|
+
border: none;
|
|
104
|
+
border-radius: 0.25rem;
|
|
105
|
+
color: var(--sl-color-white);
|
|
106
|
+
padding: 0.3rem 0.5rem;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
width: 100%;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.starlight-sidebar-topics-dropdown-button::before {
|
|
112
|
+
content: "";
|
|
113
|
+
background-color: var(--sl-color-gray-5);
|
|
114
|
+
border-radius: 0.25rem;
|
|
115
|
+
inset: -2px;
|
|
116
|
+
position: absolute;
|
|
117
|
+
z-index: -1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.starlight-sidebar-topics-dropdown-button:has(
|
|
121
|
+
.starlight-sidebar-topics-dropdown-button-icon
|
|
122
|
+
)::before {
|
|
123
|
+
content: "";
|
|
124
|
+
background-image: linear-gradient(
|
|
125
|
+
to right,
|
|
126
|
+
var(--sl-color-text-accent) calc(1.4rem + 16px + 2px),
|
|
127
|
+
var(--sl-color-gray-5) 0
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.starlight-sidebar-topics-dropdown-button:has(
|
|
132
|
+
.starlight-sidebar-topics-dropdown-button-icon
|
|
133
|
+
) {
|
|
134
|
+
background: linear-gradient(
|
|
135
|
+
to right,
|
|
136
|
+
var(--sl-color-text-accent) calc(1.4rem + 16px),
|
|
137
|
+
var(--sl-color-gray-6) 0
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.starlight-sidebar-topics-dropdown-button-icon {
|
|
142
|
+
align-items: center;
|
|
143
|
+
border-radius: 0.25rem;
|
|
144
|
+
display: flex;
|
|
145
|
+
justify-content: center;
|
|
146
|
+
padding: 0.2rem;
|
|
147
|
+
background-color: var(--sl-color-text-accent);
|
|
148
|
+
color: var(--sl-color-text-invert);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.starlight-sidebar-topics-dropdown-button-label {
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
margin-right: 0.5rem;
|
|
154
|
+
overflow: hidden;
|
|
155
|
+
text-overflow: ellipsis;
|
|
156
|
+
white-space: nowrap;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.starlight-sidebar-topics-dropdown-button:has(
|
|
160
|
+
.starlight-sidebar-topics-dropdown-button-icon
|
|
161
|
+
)
|
|
162
|
+
.starlight-sidebar-topics-dropdown-button-label {
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
margin-left: 1rem;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.starlight-sidebar-topics-dropdown-label {
|
|
168
|
+
font-weight: 600;
|
|
169
|
+
margin-right: 0.5rem;
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
text-overflow: ellipsis;
|
|
172
|
+
white-space: nowrap;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.starlight-sidebar-topics-dropdown-item:has(
|
|
176
|
+
.starlight-sidebar-topics-dropdown-icon
|
|
177
|
+
)
|
|
178
|
+
.starlight-sidebar-topics-dropdown-label {
|
|
179
|
+
margin-left: 0.5rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.starlight-sidebar-topics-dropdown-button > svg {
|
|
183
|
+
margin-left: auto;
|
|
184
|
+
font-size: 1rem;
|
|
185
|
+
color: var(--sl-color-white);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.starlight-sidebar-topics-dropdown-menu {
|
|
189
|
+
display: block;
|
|
190
|
+
position: absolute;
|
|
191
|
+
margin-top: 0.5rem;
|
|
192
|
+
width: 100%;
|
|
193
|
+
background-color: var(--sl-color-gray-6);
|
|
194
|
+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
|
195
|
+
min-width: 200px;
|
|
196
|
+
padding: 0.5rem;
|
|
197
|
+
border-radius: 0.25rem;
|
|
198
|
+
z-index: 1000000;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.starlight-sidebar-topics-dropdown-menu[hidden] {
|
|
202
|
+
display: none;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.starlight-sidebar-topics-dropdown-item {
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
padding: 0.3em;
|
|
209
|
+
color: var(--sl-color-white);
|
|
210
|
+
text-decoration: none;
|
|
211
|
+
font-weight: 600;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.starlight-sidebar-topics-dropdown-icon {
|
|
215
|
+
align-items: center;
|
|
216
|
+
border-radius: 0.25rem;
|
|
217
|
+
border: 1px solid var(--sl-color-gray-4);
|
|
218
|
+
display: flex;
|
|
219
|
+
justify-content: center;
|
|
220
|
+
padding: 0.25rem;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.starlight-sidebar-topics-dropdown-button:is(
|
|
224
|
+
.starlight-sidebar-topics-dropdown-current,
|
|
225
|
+
:hover,
|
|
226
|
+
:focus-visible
|
|
227
|
+
)
|
|
228
|
+
.starlight-sidebar-topics-dropdown-icon {
|
|
229
|
+
background-color: var(--sl-color-text-accent);
|
|
230
|
+
border-color: var(--sl-color-text-accent);
|
|
231
|
+
color: var(--sl-color-text-invert);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.starlight-sidebar-topics-dropdown-item:is(
|
|
235
|
+
:hover,
|
|
236
|
+
.starlight-sidebar-topics-dropdown-current-item
|
|
237
|
+
)
|
|
238
|
+
.starlight-sidebar-topics-dropdown-icon {
|
|
239
|
+
background-color: var(--sl-color-text-accent);
|
|
240
|
+
border-color: var(--sl-color-text-accent);
|
|
241
|
+
color: var(--sl-color-text-invert);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.starlight-sidebar-topics-dropdown-badge {
|
|
245
|
+
margin-left: 0.25rem;
|
|
246
|
+
overflow: hidden;
|
|
247
|
+
text-overflow: ellipsis;
|
|
248
|
+
white-space: nowrap;
|
|
249
|
+
}
|
|
250
|
+
</style>
|
|
251
|
+
|
|
252
|
+
<script is:inline>
|
|
253
|
+
const dropdownButton = document.getElementById(
|
|
254
|
+
"starlight-sidebar-topics-dropdown-button"
|
|
255
|
+
);
|
|
256
|
+
const dropdownMenu = document.getElementById(
|
|
257
|
+
"starlight-sidebar-topics-dropdown-menu"
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (!dropdownButton || !dropdownMenu) {
|
|
261
|
+
throw new Error("Dropdown elements not found");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
dropdownButton.addEventListener("click", () => {
|
|
265
|
+
const isOpen = !dropdownMenu.hidden;
|
|
266
|
+
dropdownMenu.hidden = isOpen;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
function closeDropdown(event) {
|
|
270
|
+
if (
|
|
271
|
+
dropdownMenu &&
|
|
272
|
+
!dropdownMenu.contains(event.target) &&
|
|
273
|
+
dropdownButton &&
|
|
274
|
+
!dropdownButton.contains(event.target)
|
|
275
|
+
) {
|
|
276
|
+
dropdownMenu.hidden = true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
document.addEventListener("click", closeDropdown);
|
|
281
|
+
</script>
|
package/index.ts
CHANGED
|
@@ -1,94 +1 @@
|
|
|
1
|
-
|
|
2
|
-
StarlightPlugin,
|
|
3
|
-
StarlightUserConfig,
|
|
4
|
-
} from "@astrojs/starlight/types";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
StarlightSidebarTopicsDropdownConfigSchema,
|
|
8
|
-
type StarlightSidebarTopicsDropdownUserConfig,
|
|
9
|
-
} from "./libs/config";
|
|
10
|
-
import { throwPluginError } from "./libs/plugin";
|
|
11
|
-
import { vitePluginStarlightSidebarTopicsDropdown } from "./libs/vite";
|
|
12
|
-
import { overrideStarlightComponent } from "./libs/starlight";
|
|
13
|
-
|
|
14
|
-
export type {
|
|
15
|
-
StarlightSidebarTopicsDropdownConfig,
|
|
16
|
-
StarlightSidebarTopicsDropdownUserConfig,
|
|
17
|
-
} from "./libs/config";
|
|
18
|
-
|
|
19
|
-
export default function starlightSidebarTopicsDropdownPlugin(
|
|
20
|
-
userConfig: StarlightSidebarTopicsDropdownUserConfig
|
|
21
|
-
): StarlightPlugin {
|
|
22
|
-
const parsedConfig =
|
|
23
|
-
StarlightSidebarTopicsDropdownConfigSchema.safeParse(userConfig);
|
|
24
|
-
|
|
25
|
-
if (!parsedConfig.success) {
|
|
26
|
-
throwPluginError(
|
|
27
|
-
`The provided plugin configuration is invalid.\n${parsedConfig.error.issues
|
|
28
|
-
.map((issue) => issue.message)
|
|
29
|
-
.join("\n")}`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const config = parsedConfig.data;
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
name: "starlight-sidebar-topics-dropdown-plugin",
|
|
37
|
-
hooks: {
|
|
38
|
-
"config:setup"({
|
|
39
|
-
addIntegration,
|
|
40
|
-
addRouteMiddleware,
|
|
41
|
-
command,
|
|
42
|
-
config: starlightConfig,
|
|
43
|
-
logger,
|
|
44
|
-
updateConfig,
|
|
45
|
-
}) {
|
|
46
|
-
if (command !== "dev" && command !== "build") return;
|
|
47
|
-
|
|
48
|
-
if (starlightConfig.sidebar) {
|
|
49
|
-
throwPluginError(
|
|
50
|
-
"It looks like you have a `sidebar` configured in your Starlight configuration. To use `starlight-sidebar-topics-dropdown`, create a new topic with your sidebar items.",
|
|
51
|
-
"Learn more about topic configuration at https://starlight-sidebar-topics-dropdown.trueberryless.org/docs/configuration/"
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
addRouteMiddleware({
|
|
56
|
-
entrypoint: "starlight-sidebar-topics-dropdown/middleware",
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const sidebar: StarlightUserConfig["sidebar"] = [];
|
|
60
|
-
|
|
61
|
-
for (const [index, topic] of config.entries()) {
|
|
62
|
-
if ("items" in topic) {
|
|
63
|
-
sidebar.push({ label: String(index), items: topic.items });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
updateConfig({
|
|
67
|
-
components: {
|
|
68
|
-
...starlightConfig.components,
|
|
69
|
-
...overrideStarlightComponent(
|
|
70
|
-
starlightConfig.components,
|
|
71
|
-
logger,
|
|
72
|
-
"Sidebar",
|
|
73
|
-
"Sidebar"
|
|
74
|
-
),
|
|
75
|
-
},
|
|
76
|
-
sidebar,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
addIntegration({
|
|
80
|
-
name: "starlight-sidebar-topics-dropdown-integration",
|
|
81
|
-
hooks: {
|
|
82
|
-
"astro:config:setup": ({ updateConfig }) => {
|
|
83
|
-
updateConfig({
|
|
84
|
-
vite: {
|
|
85
|
-
plugins: [vitePluginStarlightSidebarTopicsDropdown(config)],
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|
|
1
|
+
export { default as TopicsDropdown } from "./components/TopicsDropdown.astro";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-sidebar-topics-dropdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Starlight plugin to split your docs page into multiple subpages and switch between them with a dropdown menu in the sidebar.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"starlight",
|
|
@@ -24,10 +24,7 @@
|
|
|
24
24
|
"type": "module",
|
|
25
25
|
"exports": {
|
|
26
26
|
".": "./index.ts",
|
|
27
|
-
"./
|
|
28
|
-
"./middleware": "./middleware.ts",
|
|
29
|
-
"./overrides/Sidebar.astro": "./overrides/Sidebar.astro",
|
|
30
|
-
"./schema": "./schema.ts",
|
|
27
|
+
"./TopicsDropdown.astro": "./components/TopicsDropdown.astro",
|
|
31
28
|
"./package.json": "./package.json"
|
|
32
29
|
},
|
|
33
30
|
"devDependencies": {
|
|
@@ -35,7 +32,8 @@
|
|
|
35
32
|
"astro": "^5.3.0"
|
|
36
33
|
},
|
|
37
34
|
"peerDependencies": {
|
|
38
|
-
"@astrojs/starlight": ">=0.30"
|
|
35
|
+
"@astrojs/starlight": ">=0.30",
|
|
36
|
+
"starlight-sidebar-topics": ">=0.6"
|
|
39
37
|
},
|
|
40
38
|
"packageManager": "pnpm@9.3.0",
|
|
41
39
|
"engines": {
|
package/components/Topics.astro
DELETED
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { Badge, Icon } from '@astrojs/starlight/components'
|
|
3
|
-
import { AstroError } from 'astro/errors'
|
|
4
|
-
import type { ComponentProps } from 'astro/types'
|
|
5
|
-
import { getRelativeLocaleUrl } from 'astro:i18n'
|
|
6
|
-
import starlightConfig from 'virtual:starlight/user-config'
|
|
7
|
-
import config from 'virtual:starlight-sidebar-topics-dropdown/config'
|
|
8
|
-
|
|
9
|
-
import type { StarlightSidebarTopicsDropdownSharedConfig } from '../libs/config'
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
current: StarlightSidebarTopicsDropdownSharedConfig[number]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const { current } = Astro.props
|
|
16
|
-
|
|
17
|
-
const defaultLang = starlightConfig.defaultLocale?.lang || starlightConfig.defaultLocale?.locale || 'en'
|
|
18
|
-
|
|
19
|
-
function getTranslation(translations: Record<string, string>, link: string, description: string) {
|
|
20
|
-
const defaultTranslation = translations[defaultLang]
|
|
21
|
-
|
|
22
|
-
if (!defaultTranslation) {
|
|
23
|
-
throw new AstroError(
|
|
24
|
-
`The ${description} for "${link}" must have a key for the default language "${defaultLang}".`,
|
|
25
|
-
'Update the Starlight config to include a topic label for the default language.',
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let translation = defaultTranslation
|
|
30
|
-
|
|
31
|
-
if (Astro.currentLocale) {
|
|
32
|
-
translation = translations[Astro.currentLocale] ?? defaultTranslation
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return translation
|
|
36
|
-
}
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
<div class="starlight-sidebar-topics-dropdown-dropdown">
|
|
40
|
-
<button id="starlight-sidebar-topics-dropdown-button" class="starlight-sidebar-topics-dropdown-button">
|
|
41
|
-
{current.icon && (
|
|
42
|
-
<div class="starlight-sidebar-topics-dropdown-button-icon">
|
|
43
|
-
<Icon name={current.icon as ComponentProps<typeof Icon>['name']} />
|
|
44
|
-
</div>
|
|
45
|
-
)}
|
|
46
|
-
<span class="starlight-sidebar-topics-dropdown-button-label">{typeof current.label === 'string' ? current.label : getTranslation(current.label, current.link, 'topic label')}</span>
|
|
47
|
-
{current.badge && (
|
|
48
|
-
<Badge
|
|
49
|
-
class="starlight-sidebar-topics-dropdown-badge"
|
|
50
|
-
text={
|
|
51
|
-
typeof current.badge.text === 'string'
|
|
52
|
-
? current.badge.text
|
|
53
|
-
: getTranslation(current.badge.text, current.link, 'current badge text')
|
|
54
|
-
}
|
|
55
|
-
variant={current.badge.variant}
|
|
56
|
-
/>
|
|
57
|
-
)}
|
|
58
|
-
<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 opacity-50" aria-hidden="true"><path d="M4.93179 5.43179C4.75605 5.60753 4.75605 5.89245 4.93179 6.06819C5.10753 6.24392 5.39245 6.24392 5.56819 6.06819L7.49999 4.13638L9.43179 6.06819C9.60753 6.24392 9.89245 6.24392 10.0682 6.06819C10.2439 5.89245 10.2439 5.60753 10.0682 5.43179L7.81819 3.18179C7.73379 3.0974 7.61933 3.04999 7.49999 3.04999C7.38064 3.04999 7.26618 3.0974 7.18179 3.18179L4.93179 5.43179ZM10.0682 9.56819C10.2439 9.39245 10.2439 9.10753 10.0682 8.93179C9.89245 8.75606 9.60753 8.75606 9.43179 8.93179L7.49999 10.8636L5.56819 8.93179C5.39245 8.75606 5.10753 8.75606 4.93179 8.93179C4.75605 9.10753 4.75605 9.39245 4.93179 9.56819L7.18179 11.8182C7.35753 11.9939 7.64245 11.9939 7.81819 11.8182L10.0682 9.56819Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
|
59
|
-
</button>
|
|
60
|
-
|
|
61
|
-
<div id="starlight-sidebar-topics-dropdown-menu" class="starlight-sidebar-topics-dropdown-menu" hidden>
|
|
62
|
-
{
|
|
63
|
-
config.map((topic) => {
|
|
64
|
-
const isLinkTopic = topic.type === 'link'
|
|
65
|
-
const isCurrent = isLinkTopic ? false : topic.label === current.label && topic.link === current.link
|
|
66
|
-
const link =
|
|
67
|
-
!isLinkTopic && Astro.currentLocale ? getRelativeLocaleUrl(Astro.currentLocale, topic.link) : topic.link
|
|
68
|
-
const label =
|
|
69
|
-
typeof topic.label === 'string' ? topic.label : getTranslation(topic.label, topic.link, 'topic label')
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<a href={link} class:list={{ 'starlight-sidebar-topics-dropdown-item': true, 'starlight-sidebar-topics-dropdown-current-item': isCurrent }}>
|
|
73
|
-
{topic.icon && (
|
|
74
|
-
<div class="starlight-sidebar-topics-dropdown-icon">
|
|
75
|
-
<Icon name={topic.icon as ComponentProps<typeof Icon>['name']} />
|
|
76
|
-
</div>
|
|
77
|
-
)}
|
|
78
|
-
<span class="starlight-sidebar-topics-dropdown-label">{label}</span>
|
|
79
|
-
{topic.badge && (
|
|
80
|
-
<Badge
|
|
81
|
-
class="starlight-sidebar-topics-dropdown-badge"
|
|
82
|
-
text={
|
|
83
|
-
typeof topic.badge.text === 'string'
|
|
84
|
-
? topic.badge.text
|
|
85
|
-
: getTranslation(topic.badge.text, topic.link, 'topic badge text')
|
|
86
|
-
}
|
|
87
|
-
variant={topic.badge.variant}
|
|
88
|
-
/>
|
|
89
|
-
)}
|
|
90
|
-
</a>
|
|
91
|
-
)
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<style>
|
|
98
|
-
.starlight-sidebar-topics-dropdown-dropdown {
|
|
99
|
-
position: relative;
|
|
100
|
-
display: block;
|
|
101
|
-
width: calc(100% - 2 * 2px);
|
|
102
|
-
margin-inline: 2px;
|
|
103
|
-
margin-top: 4px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.starlight-sidebar-topics-dropdown-button {
|
|
107
|
-
display: flex;
|
|
108
|
-
align-items: center;
|
|
109
|
-
background-color: var(--sl-color-gray-6);
|
|
110
|
-
border: none;
|
|
111
|
-
border-radius: 0.25rem;
|
|
112
|
-
color: var(--sl-color-white);
|
|
113
|
-
padding: 0.3rem 0.5rem;
|
|
114
|
-
cursor: pointer;
|
|
115
|
-
width: 100%;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.starlight-sidebar-topics-dropdown-button::before {
|
|
119
|
-
content: '';
|
|
120
|
-
background-color: var(--sl-color-gray-5);
|
|
121
|
-
border-radius: 0.25rem;
|
|
122
|
-
inset: -2px;
|
|
123
|
-
position: absolute;
|
|
124
|
-
z-index: -1;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.starlight-sidebar-topics-dropdown-button:has(.starlight-sidebar-topics-dropdown-button-icon)::before {
|
|
129
|
-
content: '';
|
|
130
|
-
background-image: linear-gradient(to right, var(--sl-color-text-accent) calc(1.4rem + 16px + 2px), var(--sl-color-gray-5) 0);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.starlight-sidebar-topics-dropdown-button:has(.starlight-sidebar-topics-dropdown-button-icon) {
|
|
134
|
-
background: linear-gradient(to right, var(--sl-color-text-accent) calc(1.4rem + 16px), var(--sl-color-gray-6) 0);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.starlight-sidebar-topics-dropdown-button-icon {
|
|
138
|
-
align-items: center;
|
|
139
|
-
border-radius: 0.25rem;
|
|
140
|
-
display: flex;
|
|
141
|
-
justify-content: center;
|
|
142
|
-
padding: 0.2rem;
|
|
143
|
-
background-color: var(--sl-color-text-accent);
|
|
144
|
-
color: var(--sl-color-text-invert);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.starlight-sidebar-topics-dropdown-button-label {
|
|
148
|
-
font-weight: 600;
|
|
149
|
-
margin-right: 0.5rem;
|
|
150
|
-
overflow: hidden;
|
|
151
|
-
text-overflow: ellipsis;
|
|
152
|
-
white-space: nowrap;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.starlight-sidebar-topics-dropdown-button:has(.starlight-sidebar-topics-dropdown-button-icon) .starlight-sidebar-topics-dropdown-button-label {
|
|
156
|
-
font-weight: 600;
|
|
157
|
-
margin-left: 1rem;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.starlight-sidebar-topics-dropdown-label {
|
|
161
|
-
font-weight: 600;
|
|
162
|
-
margin-right: 0.5rem;
|
|
163
|
-
overflow: hidden;
|
|
164
|
-
text-overflow: ellipsis;
|
|
165
|
-
white-space: nowrap;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.starlight-sidebar-topics-dropdown-item:has(.starlight-sidebar-topics-dropdown-icon) .starlight-sidebar-topics-dropdown-label {
|
|
169
|
-
margin-left: 0.5rem;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.starlight-sidebar-topics-dropdown-button>svg {
|
|
173
|
-
margin-left: auto;
|
|
174
|
-
font-size: 1rem;
|
|
175
|
-
color: var(--sl-color-white);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.starlight-sidebar-topics-dropdown-menu {
|
|
179
|
-
display: block;
|
|
180
|
-
position: absolute;
|
|
181
|
-
margin-top: 0.5rem;
|
|
182
|
-
width: 100%;
|
|
183
|
-
background-color: var(--sl-color-gray-6);
|
|
184
|
-
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
|
185
|
-
min-width: 200px;
|
|
186
|
-
padding: 0.5rem;
|
|
187
|
-
border-radius: 0.25rem;
|
|
188
|
-
z-index: 1000000;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.starlight-sidebar-topics-dropdown-menu[hidden] {
|
|
192
|
-
display: none;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.starlight-sidebar-topics-dropdown-item {
|
|
196
|
-
display: flex;
|
|
197
|
-
align-items: center;
|
|
198
|
-
padding: 0.3em;
|
|
199
|
-
color: var(--sl-color-white);
|
|
200
|
-
text-decoration: none;
|
|
201
|
-
font-weight: 600;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
.starlight-sidebar-topics-dropdown-icon {
|
|
205
|
-
align-items: center;
|
|
206
|
-
border-radius: 0.25rem;
|
|
207
|
-
border: 1px solid var(--sl-color-gray-4);
|
|
208
|
-
display: flex;
|
|
209
|
-
justify-content: center;
|
|
210
|
-
padding: 0.25rem;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.starlight-sidebar-topics-dropdown-button:is(.starlight-sidebar-topics-dropdown-current, :hover, :focus-visible) .starlight-sidebar-topics-dropdown-icon {
|
|
214
|
-
background-color: var(--sl-color-text-accent);
|
|
215
|
-
border-color: var(--sl-color-text-accent);
|
|
216
|
-
color: var(--sl-color-text-invert);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.starlight-sidebar-topics-dropdown-item:is(:hover, .starlight-sidebar-topics-dropdown-current-item) .starlight-sidebar-topics-dropdown-icon {
|
|
220
|
-
background-color: var(--sl-color-text-accent);
|
|
221
|
-
border-color: var(--sl-color-text-accent);
|
|
222
|
-
color: var(--sl-color-text-invert);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.starlight-sidebar-topics-dropdown-badge {
|
|
226
|
-
margin-left: 0.25rem;
|
|
227
|
-
overflow: hidden;
|
|
228
|
-
text-overflow: ellipsis;
|
|
229
|
-
white-space: nowrap;
|
|
230
|
-
}
|
|
231
|
-
</style>
|
|
232
|
-
|
|
233
|
-
<script is:inline>
|
|
234
|
-
const dropdownButton = document.getElementById('starlight-sidebar-topics-dropdown-button');
|
|
235
|
-
const dropdownMenu = document.getElementById('starlight-sidebar-topics-dropdown-menu');
|
|
236
|
-
|
|
237
|
-
if (!dropdownButton || !dropdownMenu) {
|
|
238
|
-
throw new Error('Dropdown elements not found');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
dropdownButton.addEventListener('click', () => {
|
|
242
|
-
const isOpen = !dropdownMenu.hidden;
|
|
243
|
-
dropdownMenu.hidden = isOpen;
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
function closeDropdown(event) {
|
|
247
|
-
if (dropdownMenu && !dropdownMenu.contains(event.target) && dropdownButton && !dropdownButton.contains(event.target)) {
|
|
248
|
-
dropdownMenu.hidden = true;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
document.addEventListener('click', closeDropdown);
|
|
253
|
-
</script>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { StarlightSidebarTopicsDropdownLocalsSymbol, type StarlightSidebarTopicsDropdownLocals } from '../../libs/locals'
|
|
3
|
-
import Topics from '../Topics.astro'
|
|
4
|
-
const { entry } = Astro.locals.starlightRoute
|
|
5
|
-
// @ts-expect-error - See `libs/locals` for more information.
|
|
6
|
-
const currentTopic = Astro.locals[StarlightSidebarTopicsDropdownLocalsSymbol] as StarlightSidebarTopicsDropdownLocals
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
{entry['data'].template !== 'splash' && currentTopic && <Topics current={currentTopic.config} />}
|
|
10
|
-
|
|
11
|
-
<style is:global>
|
|
12
|
-
sl-sidebar-state-persist ul.top-level > li:not(:has(details)) {
|
|
13
|
-
margin-top: 0rem;
|
|
14
|
-
}
|
|
15
|
-
sl-sidebar-state-persist ul.top-level > li:has(details) {
|
|
16
|
-
margin-block: 0.5rem;
|
|
17
|
-
}
|
|
18
|
-
/* sl-sidebar-state-persist ul.top-level > li > a {
|
|
19
|
-
font-size: var(--sl-text-sm);
|
|
20
|
-
} */
|
|
21
|
-
sl-sidebar-state-persist ul.top-level > li > a[aria-current='page'] {
|
|
22
|
-
font-weight: 600;
|
|
23
|
-
}
|
|
24
|
-
sl-sidebar-state-persist ul.top-level > li > a[aria-current='false'] {
|
|
25
|
-
font-weight: 400;
|
|
26
|
-
color: var(--sl-color-gray-2);
|
|
27
|
-
}
|
|
28
|
-
</style>
|
package/libs/config.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import type { StarlightUserConfig } from "@astrojs/starlight/types";
|
|
2
|
-
import { z } from "astro/zod";
|
|
3
|
-
|
|
4
|
-
const sidebarTopicBadgeSchema = z.object({
|
|
5
|
-
text: z.union([z.string(), z.record(z.string())]),
|
|
6
|
-
variant: z
|
|
7
|
-
.enum(["note", "danger", "success", "caution", "tip", "default"])
|
|
8
|
-
.default("default"),
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const sidebarTopicBaseSchema = z.object({
|
|
12
|
-
/**
|
|
13
|
-
* An optional badge to display next to the topic label.
|
|
14
|
-
*
|
|
15
|
-
* This option accepts the same configuration as the Starlight badge sidebar item configuration.
|
|
16
|
-
* @see https://starlight.astro.build/guides/sidebar/#badges
|
|
17
|
-
*/
|
|
18
|
-
badge: z
|
|
19
|
-
.union([z.string(), sidebarTopicBadgeSchema])
|
|
20
|
-
.transform((badge) =>
|
|
21
|
-
typeof badge === "string"
|
|
22
|
-
? { variant: "default" as const, text: badge }
|
|
23
|
-
: badge
|
|
24
|
-
)
|
|
25
|
-
.optional(),
|
|
26
|
-
/**
|
|
27
|
-
* The name of an optional icon to display before the topic label set to one of Starlight’s built-in icons.
|
|
28
|
-
* @see https://starlight.astro.build/reference/icons/#all-icons
|
|
29
|
-
*/
|
|
30
|
-
icon: z.string().optional(),
|
|
31
|
-
/**
|
|
32
|
-
* The topic label visible at the top of the sidebar.
|
|
33
|
-
*
|
|
34
|
-
* The value can be a string, or for multilingual sites, an object with values for each different locale. When using
|
|
35
|
-
* the object form, the keys must be BCP-47 tags (e.g. en, fr, or zh-CN).
|
|
36
|
-
*/
|
|
37
|
-
label: z.union([z.string(), z.record(z.string())]),
|
|
38
|
-
/**
|
|
39
|
-
* The link to the topic’s content which an be a relative link to local files or the full URL of an external page.
|
|
40
|
-
*
|
|
41
|
-
* For internal links, the link can either be a page included in the items array or a different page acting as the
|
|
42
|
-
* topic’s landing page.
|
|
43
|
-
*/
|
|
44
|
-
link: z.string(),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const sidebarTopicLinkSchema = sidebarTopicBaseSchema;
|
|
48
|
-
|
|
49
|
-
const sidebarTopicGroupSchema = sidebarTopicBaseSchema.extend({
|
|
50
|
-
/**
|
|
51
|
-
* An optional unique identifier for the topic that can be used to associate content pages that are not listed in any
|
|
52
|
-
* topic sidebar configuration with this topic.
|
|
53
|
-
*/
|
|
54
|
-
id: z.string().optional(),
|
|
55
|
-
/**
|
|
56
|
-
* The sidebar items (links and subcategories) to display for this topic.
|
|
57
|
-
*
|
|
58
|
-
* The topic’s sidebar navigation items. This represents the sidebar displayed when the topic `link` page or any of
|
|
59
|
-
* the pages configured in the `items` array is the current page.
|
|
60
|
-
*/
|
|
61
|
-
items: z.any().array() as z.Schema<
|
|
62
|
-
NonNullable<StarlightUserConfig["sidebar"]>
|
|
63
|
-
>,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
export const StarlightSidebarTopicsDropdownConfigSchema = z
|
|
67
|
-
.union([sidebarTopicGroupSchema, sidebarTopicLinkSchema])
|
|
68
|
-
.array();
|
|
69
|
-
|
|
70
|
-
export type StarlightSidebarTopicsDropdownUserConfig = z.input<
|
|
71
|
-
typeof StarlightSidebarTopicsDropdownConfigSchema
|
|
72
|
-
>;
|
|
73
|
-
export type StarlightSidebarTopicsDropdownConfig = z.output<
|
|
74
|
-
typeof StarlightSidebarTopicsDropdownConfigSchema
|
|
75
|
-
>;
|
|
76
|
-
|
|
77
|
-
export type StarlightSidebarTopicsDropdownSharedConfig = (
|
|
78
|
-
| (z.output<typeof sidebarTopicLinkSchema> & { type: "link" })
|
|
79
|
-
| (Omit<z.output<typeof sidebarTopicGroupSchema>, "items"> & {
|
|
80
|
-
type: "group";
|
|
81
|
-
})
|
|
82
|
-
)[];
|
package/libs/content.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { StarlightRouteData } from "@astrojs/starlight/route-data";
|
|
2
|
-
import type { TopicFrontmatterSchema } from "../schema";
|
|
3
|
-
export function isStarlightEntryWithTopic(
|
|
4
|
-
entry: StarlightEntry
|
|
5
|
-
): entry is StarlightEntryWithTopic {
|
|
6
|
-
return (
|
|
7
|
-
"data" in entry &&
|
|
8
|
-
"topic" in (entry as StarlightEntryWithTopic).data &&
|
|
9
|
-
typeof (entry as StarlightEntryWithTopic).data.topic === "string"
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
export type StarlightEntry = StarlightRouteData["entry"];
|
|
13
|
-
export type StarlightEntryWithTopic = StarlightEntry & {
|
|
14
|
-
data: TopicFrontmatterSchema;
|
|
15
|
-
};
|
package/libs/i18n.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import starlightConfig from "virtual:starlight/user-config";
|
|
2
|
-
|
|
3
|
-
import { stripTrailingSlash } from "./pathname";
|
|
4
|
-
|
|
5
|
-
export function getLocalizedSlug(
|
|
6
|
-
slug: string,
|
|
7
|
-
locale: string | undefined
|
|
8
|
-
): string {
|
|
9
|
-
const slugLocale = getLocaleFromSlug(slug);
|
|
10
|
-
if (slugLocale === locale) return slug;
|
|
11
|
-
locale ??= "";
|
|
12
|
-
if (slugLocale === slug) return locale;
|
|
13
|
-
|
|
14
|
-
if (slugLocale) {
|
|
15
|
-
return stripTrailingSlash(
|
|
16
|
-
slug.replace(`${slugLocale}/`, locale ? `${locale}/` : "")
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return slug ? `${locale}/${slug}` : locale;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function getLocaleFromSlug(slug: string): string | undefined {
|
|
24
|
-
const locales = Object.keys(starlightConfig.locales ?? {});
|
|
25
|
-
const baseSegment = slug.split("/")[0];
|
|
26
|
-
return baseSegment && locales.includes(baseSegment) ? baseSegment : undefined;
|
|
27
|
-
}
|
package/libs/locals.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { StarlightSidebarTopicsDropdownSharedConfig } from "./config";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* We keep track of the current topic configuration using a variable stored using `Astro.locals` which will be reset for
|
|
5
|
-
* each new page. The value is tracked using an untyped symbol on purpose to avoid users to get autocomplete for it and
|
|
6
|
-
* avoid potential clashes with user-defined variables.
|
|
7
|
-
*/
|
|
8
|
-
export const StarlightSidebarTopicsDropdownLocalsSymbol = Symbol.for(
|
|
9
|
-
"starlight-sidebar-topics-dropdown:locals"
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
export interface StarlightSidebarTopicsDropdownLocals {
|
|
13
|
-
config: StarlightSidebarTopicsDropdownSharedConfig[number];
|
|
14
|
-
}
|
package/libs/pathname.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export function arePathnamesEqual(pathnameA: string, pathnameB: string) {
|
|
2
|
-
return (
|
|
3
|
-
stripLeadingAndTrailingSlashes(pathnameA) ===
|
|
4
|
-
stripLeadingAndTrailingSlashes(pathnameB)
|
|
5
|
-
);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function stripLeadingAndTrailingSlashes(pathname: string): string {
|
|
9
|
-
return stripLeadingSlash(stripTrailingSlash(pathname));
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function stripLeadingSlash(pathname: string) {
|
|
13
|
-
if (pathname.startsWith("/")) pathname = pathname.slice(1);
|
|
14
|
-
return pathname;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function stripTrailingSlash(pathname: string) {
|
|
18
|
-
if (pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
19
|
-
return pathname;
|
|
20
|
-
}
|
package/libs/plugin.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { AstroError } from "astro/errors";
|
|
2
|
-
|
|
3
|
-
export function throwPluginError(message: string, hint?: string): never {
|
|
4
|
-
throw new AstroError(
|
|
5
|
-
message,
|
|
6
|
-
hint ??
|
|
7
|
-
`See the error report above for more informations.\n\nIf you believe this is a bug, please file an issue at https://github.com/trueberryless-org/starlight-sidebar-topics-dropdown/issues/new/choose`
|
|
8
|
-
);
|
|
9
|
-
}
|
package/libs/sidebar.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import type { StarlightRouteData } from "@astrojs/starlight/route-data";
|
|
2
|
-
|
|
3
|
-
import type { StarlightSidebarTopicsDropdownSharedConfig } from "./config";
|
|
4
|
-
import { isStarlightEntryWithTopic, type StarlightEntry } from "./content";
|
|
5
|
-
import { getLocaleFromSlug, getLocalizedSlug } from "./i18n";
|
|
6
|
-
import { arePathnamesEqual, stripLeadingAndTrailingSlashes } from "./pathname";
|
|
7
|
-
|
|
8
|
-
const absoluteLinkRegex = /^https?:\/\//;
|
|
9
|
-
|
|
10
|
-
export function getCurrentTopic(
|
|
11
|
-
config: StarlightSidebarTopicsDropdownSharedConfig,
|
|
12
|
-
sidebar: SidebarEntry[],
|
|
13
|
-
currentSlug: string,
|
|
14
|
-
entry: StarlightEntry
|
|
15
|
-
): Topic | undefined {
|
|
16
|
-
// If the current page has a topic ID, use it to find the topic.
|
|
17
|
-
const topicId = getTopicIdFromEntry(entry);
|
|
18
|
-
if (topicId) return getTopicById(config, sidebar, topicId);
|
|
19
|
-
|
|
20
|
-
// Start by checking if the current page is a topic root.
|
|
21
|
-
const topicFromSlug = getTopicFromSlug(config, sidebar, currentSlug);
|
|
22
|
-
if (topicFromSlug) return topicFromSlug;
|
|
23
|
-
|
|
24
|
-
// Otherwise, find the current topic by looking for the current page in the sidebar.
|
|
25
|
-
const currentSidebarTopic = getCurrentSidebarTopic(sidebar);
|
|
26
|
-
if (!currentSidebarTopic) return;
|
|
27
|
-
|
|
28
|
-
const currentTopicConfig =
|
|
29
|
-
config[Number.parseInt(currentSidebarTopic.label, 10)];
|
|
30
|
-
if (!currentTopicConfig) return;
|
|
31
|
-
|
|
32
|
-
const sidebarHasGroups =
|
|
33
|
-
currentSidebarTopic.entries.length !== 1 ||
|
|
34
|
-
currentSidebarTopic.entries[0]?.label !== "";
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
config: currentTopicConfig,
|
|
38
|
-
sidebar: currentSidebarTopic.entries,
|
|
39
|
-
sidebarHasGroups,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function isTopicFirstPage(
|
|
44
|
-
sidebar: SidebarEntry[],
|
|
45
|
-
currentSlug: string
|
|
46
|
-
): boolean {
|
|
47
|
-
const currentSidebarTopic = getCurrentSidebarTopic(sidebar);
|
|
48
|
-
if (!currentSidebarTopic) return false;
|
|
49
|
-
|
|
50
|
-
const firstPage = getSidebarFirstPage(currentSidebarTopic.entries);
|
|
51
|
-
if (!firstPage) return false;
|
|
52
|
-
|
|
53
|
-
return arePathnamesEqual(
|
|
54
|
-
stripLeadingAndTrailingSlashes(firstPage.href),
|
|
55
|
-
currentSlug
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function isTopicLastPage(
|
|
60
|
-
sidebar: SidebarEntry[],
|
|
61
|
-
currentSlug: string
|
|
62
|
-
): boolean {
|
|
63
|
-
const currentSidebarTopic = getCurrentSidebarTopic(sidebar);
|
|
64
|
-
if (!currentSidebarTopic) return false;
|
|
65
|
-
|
|
66
|
-
const lastPage = getSidebarLastPage(currentSidebarTopic.entries);
|
|
67
|
-
if (!lastPage) return false;
|
|
68
|
-
|
|
69
|
-
return arePathnamesEqual(
|
|
70
|
-
stripLeadingAndTrailingSlashes(lastPage.href),
|
|
71
|
-
currentSlug
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function getSidebarFirstPage(sidebar: SidebarEntry[]) {
|
|
76
|
-
const entry = sidebar[0];
|
|
77
|
-
if (!entry) return;
|
|
78
|
-
if (entry.type === "link") return entry;
|
|
79
|
-
|
|
80
|
-
return getSidebarFirstPage(entry.entries);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function getSidebarLastPage(sidebar: SidebarEntry[]) {
|
|
84
|
-
const entry = sidebar.at(-1);
|
|
85
|
-
if (!entry) return;
|
|
86
|
-
if (entry.type === "link") return entry;
|
|
87
|
-
|
|
88
|
-
return getSidebarLastPage(entry.entries);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function getTopicFromSlug(
|
|
92
|
-
config: StarlightSidebarTopicsDropdownSharedConfig,
|
|
93
|
-
sidebar: SidebarEntry[],
|
|
94
|
-
slug: string
|
|
95
|
-
): Topic | undefined {
|
|
96
|
-
let topicConfig: Topic["config"] | undefined;
|
|
97
|
-
let topicSidebar: Topic["sidebar"] | undefined;
|
|
98
|
-
let sidebarHasGroups = true;
|
|
99
|
-
|
|
100
|
-
// Start by checking if the current page is a topic homepage.
|
|
101
|
-
let groupTopicIndex = -1;
|
|
102
|
-
const slugLocale = getLocaleFromSlug(slug);
|
|
103
|
-
|
|
104
|
-
for (const topic of config) {
|
|
105
|
-
if (topic.type === "group") groupTopicIndex++;
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
!absoluteLinkRegex.test(topic.link) &&
|
|
109
|
-
arePathnamesEqual(
|
|
110
|
-
getLocalizedSlug(
|
|
111
|
-
stripLeadingAndTrailingSlashes(topic.link),
|
|
112
|
-
slugLocale
|
|
113
|
-
),
|
|
114
|
-
slug
|
|
115
|
-
) &&
|
|
116
|
-
groupTopicIndex !== -1
|
|
117
|
-
) {
|
|
118
|
-
const sidebarTopic = sidebar[groupTopicIndex];
|
|
119
|
-
|
|
120
|
-
if (sidebarTopic?.type === "group") {
|
|
121
|
-
topicConfig = topic;
|
|
122
|
-
topicSidebar = sidebarTopic.entries;
|
|
123
|
-
sidebarHasGroups =
|
|
124
|
-
sidebarTopic.entries.length !== 1 ||
|
|
125
|
-
sidebarTopic.entries[0]?.label !== "";
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!topicConfig || !topicSidebar) return;
|
|
133
|
-
|
|
134
|
-
return { config: topicConfig, sidebar: topicSidebar, sidebarHasGroups };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function getTopicById(
|
|
138
|
-
config: StarlightSidebarTopicsDropdownSharedConfig,
|
|
139
|
-
sidebar: SidebarEntry[],
|
|
140
|
-
id: string
|
|
141
|
-
): Topic | undefined {
|
|
142
|
-
let topicConfig: Topic["config"] | undefined;
|
|
143
|
-
let topicSidebar: Topic["sidebar"] | undefined;
|
|
144
|
-
let sidebarHasGroups = true;
|
|
145
|
-
let groupTopicIndex = -1;
|
|
146
|
-
for (const topic of config) {
|
|
147
|
-
if (topic.type === "group") groupTopicIndex++;
|
|
148
|
-
if (topic.type === "group" && topic.id === id) {
|
|
149
|
-
const sidebarTopic = sidebar[groupTopicIndex];
|
|
150
|
-
if (sidebarTopic?.type === "group") {
|
|
151
|
-
topicConfig = topic;
|
|
152
|
-
topicSidebar = sidebarTopic.entries;
|
|
153
|
-
sidebarHasGroups =
|
|
154
|
-
sidebarTopic.entries.length !== 1 ||
|
|
155
|
-
sidebarTopic.entries[0]?.label !== "";
|
|
156
|
-
}
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (!topicConfig || !topicSidebar) return;
|
|
161
|
-
return { config: topicConfig, sidebar: topicSidebar, sidebarHasGroups };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function getCurrentSidebarTopic(
|
|
165
|
-
sidebar: SidebarEntry[]
|
|
166
|
-
): SidebarTopic | undefined {
|
|
167
|
-
let currentSidebarTopic: SidebarTopic | undefined;
|
|
168
|
-
|
|
169
|
-
for (const topic of sidebar) {
|
|
170
|
-
if (topic.type === "link") continue;
|
|
171
|
-
|
|
172
|
-
const currentSidebarEntry = getCurrentSidebarEntry(topic.entries);
|
|
173
|
-
|
|
174
|
-
if (currentSidebarEntry) {
|
|
175
|
-
currentSidebarTopic = topic;
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return currentSidebarTopic;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function getCurrentSidebarEntry(
|
|
184
|
-
sidebar: SidebarEntry[]
|
|
185
|
-
): SidebarEntry | undefined {
|
|
186
|
-
return sidebar.find((entry) => {
|
|
187
|
-
if (entry.type === "link") {
|
|
188
|
-
return entry.isCurrent;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return getCurrentSidebarEntry(entry.entries);
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function getTopicIdFromEntry(entry: StarlightEntry): string | undefined {
|
|
196
|
-
return isStarlightEntryWithTopic(entry) ? entry.data.topic : undefined;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
type SidebarEntry = StarlightRouteData["sidebar"][number];
|
|
200
|
-
|
|
201
|
-
interface SidebarTopic {
|
|
202
|
-
label: string;
|
|
203
|
-
entries: SidebarEntry[];
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export interface Topic {
|
|
207
|
-
config: StarlightSidebarTopicsDropdownSharedConfig[number];
|
|
208
|
-
sidebar: SidebarEntry[];
|
|
209
|
-
sidebarHasGroups: boolean;
|
|
210
|
-
}
|
package/libs/starlight.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { StarlightUserConfig } from "@astrojs/starlight/types";
|
|
2
|
-
import type { AstroIntegrationLogger } from "astro";
|
|
3
|
-
|
|
4
|
-
export function overrideStarlightComponent(
|
|
5
|
-
components: StarlightUserConfig["components"],
|
|
6
|
-
logger: AstroIntegrationLogger,
|
|
7
|
-
override: keyof NonNullable<StarlightUserConfig["components"]>,
|
|
8
|
-
component: string
|
|
9
|
-
) {
|
|
10
|
-
if (components?.[override]) {
|
|
11
|
-
logger.warn(
|
|
12
|
-
`It looks like you already have a \`${override}\` component override in your Starlight configuration.`
|
|
13
|
-
);
|
|
14
|
-
logger.warn(
|
|
15
|
-
`To use \`starlight-sidebar-topics-dropdown\`, either remove your override or update it to render the content from \`starlight-sidebar-topics-dropdown/components/${component}.astro\`.`
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
[override]: `starlight-sidebar-topics-dropdown/overrides/${override}.astro`,
|
|
23
|
-
};
|
|
24
|
-
}
|
package/libs/vite.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ViteUserConfig } from "astro";
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
StarlightSidebarTopicsDropdownConfig,
|
|
5
|
-
StarlightSidebarTopicsDropdownSharedConfig,
|
|
6
|
-
} from "./config";
|
|
7
|
-
|
|
8
|
-
const moduleId = "virtual:starlight-sidebar-topics-dropdown/config";
|
|
9
|
-
|
|
10
|
-
export function vitePluginStarlightSidebarTopicsDropdown(
|
|
11
|
-
config: StarlightSidebarTopicsDropdownConfig
|
|
12
|
-
): VitePlugin {
|
|
13
|
-
const resolvedModuleId = `\0${moduleId}`;
|
|
14
|
-
|
|
15
|
-
const sharedConfig: StarlightSidebarTopicsDropdownSharedConfig = config.map(
|
|
16
|
-
(topic) => {
|
|
17
|
-
if (!("items" in topic)) return { ...topic, type: "link" };
|
|
18
|
-
const { items, ...topicWithoutItems } = topic;
|
|
19
|
-
return { ...topicWithoutItems, type: "group" };
|
|
20
|
-
}
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const moduleContent = `export default ${JSON.stringify(sharedConfig)}`;
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
name: "vite-plugin-starlight-sidebar-topics-dropdown",
|
|
27
|
-
load(id) {
|
|
28
|
-
return id === resolvedModuleId ? moduleContent : undefined;
|
|
29
|
-
},
|
|
30
|
-
resolveId(id) {
|
|
31
|
-
return id === moduleId ? resolvedModuleId : undefined;
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type VitePlugin = NonNullable<ViteUserConfig["plugins"]>[number];
|
package/middleware.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { defineRouteMiddleware } from "@astrojs/starlight/route-data";
|
|
2
|
-
import config from "virtual:starlight-sidebar-topics-dropdown/config";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
StarlightSidebarTopicsDropdownLocalsSymbol,
|
|
6
|
-
type StarlightSidebarTopicsDropdownLocals,
|
|
7
|
-
} from "./libs/locals";
|
|
8
|
-
import { throwPluginError } from "./libs/plugin";
|
|
9
|
-
import {
|
|
10
|
-
getCurrentTopic,
|
|
11
|
-
isTopicFirstPage,
|
|
12
|
-
isTopicLastPage,
|
|
13
|
-
} from "./libs/sidebar";
|
|
14
|
-
|
|
15
|
-
export const onRequest = defineRouteMiddleware((context) => {
|
|
16
|
-
const { starlightRoute } = context.locals;
|
|
17
|
-
const { entry, id, pagination, sidebar } = starlightRoute;
|
|
18
|
-
|
|
19
|
-
if (entry["data"].template !== "splash") {
|
|
20
|
-
const currentTopic = getCurrentTopic(config, sidebar, id, entry);
|
|
21
|
-
|
|
22
|
-
if (!currentTopic)
|
|
23
|
-
throwPluginError(
|
|
24
|
-
`Failed to find the topic for the \`${id}\` page.`,
|
|
25
|
-
`Either include this page in the sidebar configuration of the desired topic using the \`items\` property or to associate an unlisted page with a topic, use the \`topic\` frontmatter property and set it to the desired topic ID.
|
|
26
|
-
Learn more about unlisted pages in the ["Unlisted pages"](https://starlight-sidebar-topics-dropdown.trueberryless.org/docs/guides/unlisted-pages/) guide.`
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
starlightRoute.sidebar = currentTopic.sidebar;
|
|
30
|
-
// @ts-expect-error - See `libs/locals` for more information.
|
|
31
|
-
context.locals[StarlightSidebarTopicsDropdownLocalsSymbol] = {
|
|
32
|
-
config: currentTopic.config,
|
|
33
|
-
} satisfies StarlightSidebarTopicsDropdownLocals;
|
|
34
|
-
|
|
35
|
-
if (isTopicFirstPage(sidebar, id)) {
|
|
36
|
-
pagination.prev = undefined;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (isTopicLastPage(sidebar, id)) {
|
|
40
|
-
pagination.next = undefined;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
});
|
package/overrides/Sidebar.astro
DELETED
package/schema.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { z } from "astro/zod";
|
|
2
|
-
export const topicSchema = z.object({
|
|
3
|
-
/**
|
|
4
|
-
* ID of the topic to associate with the current page if the page is not listed in any topic sidebar configuration.
|
|
5
|
-
*
|
|
6
|
-
* @see https://starlight-sidebar-topics-dropdown.trueberryless.org/docs/guides/unlisted-pages/
|
|
7
|
-
*/
|
|
8
|
-
topic: z.string().optional(),
|
|
9
|
-
});
|
|
10
|
-
export type TopicFrontmatterSchema = z.input<typeof topicSchema>;
|
package/starlight.d.ts
DELETED