@rimelight/ui 0.0.23 → 0.0.25
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/package.json +8 -3
- package/src/components/Head.astro +199 -156
- package/src/components/astro/RLAAccordion.astro +22 -0
- package/src/components/astro/RLAButton.astro +104 -85
- package/src/components/astro/RLAFooter.astro +14 -40
- package/src/components/astro/RLAHeader.astro +18 -78
- package/src/components/astro/RLAIcon.astro +11 -36
- package/src/components/astro/RLALogo.astro +9 -44
- package/src/components/astro/RLAPlaceholder.astro +8 -33
- package/src/components/astro/RLAQuote.astro +15 -0
- package/src/components/astro/RLAScrollToTop.astro +171 -221
- package/src/components/astro/RLASection.astro +181 -0
- package/src/components/vue/RLVButton.vue +125 -50
- package/src/components/vue/RLVFooter.vue +12 -49
- package/src/components/vue/RLVHeader.vue +15 -98
- package/src/components/vue/RLVIcon.vue +11 -37
- package/src/components/vue/RLVLogo.vue +8 -56
- package/src/components/vue/RLVPlaceholder.vue +11 -33
- package/src/components/vue/RLVScrollToTop.vue +17 -88
- package/src/components/vue/RLVSection.vue +98 -0
- package/src/composables/index.ts +0 -1
- package/src/env.d.ts +5 -0
- package/src/integrations/ui.ts +2 -6
- package/src/nuxt-types.ts +3 -0
- package/src/plugins/index.ts +5 -2
- package/src/plugins/starlightAddons/index.ts +3 -64
- package/src/plugins/starlightDocsApi/components/ApiEmits.astro +46 -0
- package/src/plugins/starlightDocsApi/components/ApiProps.astro +47 -0
- package/src/plugins/starlightDocsApi/components/ApiSlots.astro +50 -0
- package/src/plugins/starlightDocsApi/components/ApiTheme.astro +27 -0
- package/src/plugins/starlightDocsApi/extract-vue.ts +286 -0
- package/src/plugins/starlightDocsApi/index.ts +265 -0
- package/src/plugins/starlightDocsApi/types.ts +40 -0
- package/src/plugins/starlightDocsApi/utils.ts +45 -0
- package/src/themes/button.theme.ts +179 -23
- package/src/themes/footer.theme.ts +4 -6
- package/src/themes/header.theme.ts +4 -28
- package/src/themes/icon.theme.ts +4 -2
- package/src/themes/index.ts +15 -20
- package/src/themes/logo.theme.ts +4 -2
- package/src/themes/placeholder.theme.ts +4 -6
- package/src/themes/scroll-to-top.theme.ts +4 -6
- package/src/themes/section.theme.ts +185 -0
- package/src/types/componentVariant.ts +10 -0
- package/src/types/index.ts +1 -0
- package/src/utils/defineTheme.ts +11 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/resolveClasses.ts +21 -0
- package/src/utils/useUi.ts +19 -0
- package/src/composables/useUi.ts +0 -27
- package/src/plugins/starlightAddons/Sidebar.astro +0 -89
- package/src/plugins/starlightAddons/data.ts +0 -39
- package/src/plugins/starlightAddons/libs/config.ts +0 -107
- package/src/plugins/starlightAddons/libs/content.ts +0 -20
- package/src/plugins/starlightAddons/libs/i18n.ts +0 -51
- package/src/plugins/starlightAddons/libs/locals.ts +0 -12
- package/src/plugins/starlightAddons/libs/pathname.ts +0 -22
- package/src/plugins/starlightAddons/libs/plugin.ts +0 -9
- package/src/plugins/starlightAddons/libs/sidebar.ts +0 -183
- package/src/plugins/starlightAddons/libs/vite.ts +0 -46
- package/src/plugins/starlightAddons/locals.d.ts +0 -14
- package/src/plugins/starlightAddons/middleware.ts +0 -132
- package/src/plugins/starlightAddons/overrides/Sidebar.astro +0 -8
- package/src/plugins/starlightAddons/schema.ts +0 -13
- package/src/plugins/starlightAddons/virtual.d.ts +0 -13
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cx } from "css-variants"
|
|
2
|
+
|
|
3
|
+
export function resolveClasses<T extends (...args: never[]) => Record<string, string>>(
|
|
4
|
+
themeFn: T,
|
|
5
|
+
variants: Parameters<T>[0],
|
|
6
|
+
mergedUI: Record<string, string | undefined>,
|
|
7
|
+
rootClass?: string
|
|
8
|
+
): Record<string, string> {
|
|
9
|
+
const resolved = themeFn(variants)
|
|
10
|
+
const result: Record<string, string> = {}
|
|
11
|
+
|
|
12
|
+
for (const [slot, value] of Object.entries(resolved)) {
|
|
13
|
+
result[slot] = cx(value, mergedUI[slot])
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (rootClass) {
|
|
17
|
+
result.root = cx(result.root ?? "", rootClass)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return result
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import defu from "defu"
|
|
2
|
+
import { getUIConfig } from "virtual:rimelight-ui"
|
|
3
|
+
import type { DefaultUIConfig } from "../themes"
|
|
4
|
+
|
|
5
|
+
export function useUi(
|
|
6
|
+
componentName: keyof DefaultUIConfig,
|
|
7
|
+
uiProp?: Record<string, string | undefined>
|
|
8
|
+
): Record<string, string | undefined> {
|
|
9
|
+
const globalConfig = getUIConfig()
|
|
10
|
+
const defaults = globalConfig[componentName] ?? {}
|
|
11
|
+
const merged = defu(uiProp ?? {}, defaults)
|
|
12
|
+
|
|
13
|
+
return Object.fromEntries(
|
|
14
|
+
Object.entries(merged).map(([key, value]) => [
|
|
15
|
+
key,
|
|
16
|
+
typeof value === "string" ? value : undefined
|
|
17
|
+
])
|
|
18
|
+
)
|
|
19
|
+
}
|
package/src/composables/useUi.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { computed, type ComputedRef } from "vue"
|
|
2
|
-
import defu from "defu"
|
|
3
|
-
import { getUIConfig } from "virtual:rimelight-ui"
|
|
4
|
-
import type { DefaultUIConfig } from "@/themes"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Reactive UI config merge composable for Vue components.
|
|
8
|
-
*
|
|
9
|
-
* Merges (highest priority first): 1. Local `ui` prop override 2. Global config from integration
|
|
10
|
-
* (virtual:rimelight-ui)
|
|
11
|
-
*
|
|
12
|
-
* @param componentName - The component key in the config (e.g. 'button')
|
|
13
|
-
* @param uiProp - The local `ui` prop from defineProps
|
|
14
|
-
* @returns ComputedRef of merged UI overrides
|
|
15
|
-
*/
|
|
16
|
-
export function useUi(
|
|
17
|
-
componentName: keyof DefaultUIConfig,
|
|
18
|
-
uiProp: Record<string, unknown> | undefined
|
|
19
|
-
): ComputedRef<Record<string, unknown>> {
|
|
20
|
-
const globalConfig = getUIConfig()
|
|
21
|
-
|
|
22
|
-
return computed(() => {
|
|
23
|
-
const componentConfig = globalConfig[componentName]
|
|
24
|
-
|
|
25
|
-
return defu(uiProp ?? {}, componentConfig ?? {})
|
|
26
|
-
})
|
|
27
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { Badge, Icon } from '@astrojs/starlight/components'
|
|
3
|
-
|
|
4
|
-
const { hasSidebar } = Astro.locals.starlightRoute
|
|
5
|
-
const { isPageWithTopic, topics } = Astro.locals.starlightSidebarTopics
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
{hasSidebar && isPageWithTopic && (
|
|
9
|
-
<ul class="starlight-sidebar-topics">
|
|
10
|
-
{topics.map((topic, index) => (
|
|
11
|
-
<li key={index}>
|
|
12
|
-
<a href={topic.link} class:list={{ 'starlight-sidebar-topics-current': topic.isCurrent }}>
|
|
13
|
-
{topic.icon && (
|
|
14
|
-
<div class="starlight-sidebar-topics-icon">
|
|
15
|
-
<Icon name={topic.icon} />
|
|
16
|
-
</div>
|
|
17
|
-
)}
|
|
18
|
-
<div>
|
|
19
|
-
{topic.label}
|
|
20
|
-
{topic.badge && (
|
|
21
|
-
<Badge class="starlight-sidebar-topics-badge" text={topic.badge.text} variant={topic.badge.variant} />
|
|
22
|
-
)}
|
|
23
|
-
</div>
|
|
24
|
-
</a>
|
|
25
|
-
</li>
|
|
26
|
-
))}
|
|
27
|
-
</ul>
|
|
28
|
-
)}
|
|
29
|
-
<style>
|
|
30
|
-
ul {
|
|
31
|
-
list-style: none;
|
|
32
|
-
padding: 0;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
ul::after {
|
|
36
|
-
content: '';
|
|
37
|
-
display: block;
|
|
38
|
-
margin-top: 1rem;
|
|
39
|
-
height: 1px;
|
|
40
|
-
border-top: 1px solid var(--sl-color-hairline-light);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
li {
|
|
44
|
-
overflow-wrap: anywhere;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
li + li {
|
|
48
|
-
margin-top: 0.25rem;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
a {
|
|
52
|
-
align-items: center;
|
|
53
|
-
color: var(--sl-color-white);
|
|
54
|
-
display: flex;
|
|
55
|
-
font-size: var(--sl-text-base);
|
|
56
|
-
font-weight: 600;
|
|
57
|
-
gap: 0.5rem;
|
|
58
|
-
line-height: 1.5;
|
|
59
|
-
padding: 0.3em 0.5rem;
|
|
60
|
-
text-decoration: none;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
a:is(.starlight-sidebar-topics-current, :hover, :focus-visible) {
|
|
64
|
-
color: var(--sl-color-accent-high);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
:global([data-theme='light']) a.starlight-sidebar-topics-current {
|
|
68
|
-
color: var(--sl-color-accent);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.starlight-sidebar-topics-icon {
|
|
72
|
-
align-items: center;
|
|
73
|
-
border-radius: 0.25rem;
|
|
74
|
-
border: 1px solid var(--sl-color-gray-4);
|
|
75
|
-
display: flex;
|
|
76
|
-
justify-content: center;
|
|
77
|
-
padding: 0.25rem;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
a:is(.starlight-sidebar-topics-current, :hover, :focus-visible) .starlight-sidebar-topics-icon {
|
|
81
|
-
background-color: var(--sl-color-text-accent);
|
|
82
|
-
border-color: var(--sl-color-text-accent);
|
|
83
|
-
color: var(--sl-color-text-invert);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.starlight-sidebar-topics-badge {
|
|
87
|
-
margin-inline-start: 0.25em;
|
|
88
|
-
}
|
|
89
|
-
</style>
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { StarlightIcon } from "@astrojs/starlight/types"
|
|
2
|
-
|
|
3
|
-
import type { SidebarTopicBadge } from "./libs/config"
|
|
4
|
-
|
|
5
|
-
export interface StarlightSidebarTopicsRouteData {
|
|
6
|
-
/**
|
|
7
|
-
* Indicates if the current page is associated with a topic or not.
|
|
8
|
-
*/
|
|
9
|
-
isPageWithTopic: boolean
|
|
10
|
-
/**
|
|
11
|
-
* A list of all configured topics.
|
|
12
|
-
*/
|
|
13
|
-
topics: {
|
|
14
|
-
/**
|
|
15
|
-
* The optional badge associated with the topic.
|
|
16
|
-
*/
|
|
17
|
-
badge?: {
|
|
18
|
-
text: string
|
|
19
|
-
variant: SidebarTopicBadge["variant"]
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* The name of an optional icon associated with the topic set to one of Starlight’s built-in
|
|
23
|
-
* icons.
|
|
24
|
-
*/
|
|
25
|
-
icon?: StarlightIcon
|
|
26
|
-
/**
|
|
27
|
-
* Indicates if the current page is part of the topic.
|
|
28
|
-
*/
|
|
29
|
-
isCurrent: boolean
|
|
30
|
-
/**
|
|
31
|
-
* The label of the topic.
|
|
32
|
-
*/
|
|
33
|
-
label: string
|
|
34
|
-
/**
|
|
35
|
-
* The link to the topic’s content.
|
|
36
|
-
*/
|
|
37
|
-
link: string
|
|
38
|
-
}[]
|
|
39
|
-
}
|
|
@@ -1,107 +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(), z.string())]),
|
|
6
|
-
variant: z.enum(["note", "danger", "success", "caution", "tip", "default"]).default("default")
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
const sidebarTopicBaseSchema = z.object({
|
|
10
|
-
/**
|
|
11
|
-
* An optional badge to display next to the topic label.
|
|
12
|
-
*
|
|
13
|
-
* This option accepts the same configuration as the Starlight badge sidebar item configuration.
|
|
14
|
-
*
|
|
15
|
-
* @see https://starlight.astro.build/guides/sidebar/#badges
|
|
16
|
-
*/
|
|
17
|
-
badge: z
|
|
18
|
-
.union([z.string(), sidebarTopicBadgeSchema])
|
|
19
|
-
.transform((badge) =>
|
|
20
|
-
typeof badge === "string" ? { variant: "default" as const, text: badge } : badge
|
|
21
|
-
)
|
|
22
|
-
.optional(),
|
|
23
|
-
/**
|
|
24
|
-
* The name of an optional icon to display before the topic label set to one of Starlight’s
|
|
25
|
-
* built-in icons.
|
|
26
|
-
*
|
|
27
|
-
* @see https://starlight.astro.build/reference/icons/#all-icons
|
|
28
|
-
*/
|
|
29
|
-
icon: z.string().optional(),
|
|
30
|
-
/**
|
|
31
|
-
* The topic label visible at the top of the sidebar.
|
|
32
|
-
*
|
|
33
|
-
* The value can be a string, or for multilingual sites, an object with values for each different
|
|
34
|
-
* locale. When using the object form, the keys must be BCP-47 tags (e.g. en, fr, or zh-CN).
|
|
35
|
-
*/
|
|
36
|
-
label: z.union([z.string(), z.record(z.string(), z.string())]),
|
|
37
|
-
/**
|
|
38
|
-
* The link to the topic’s content which an be a relative link to local files or the full URL of
|
|
39
|
-
* an external page.
|
|
40
|
-
*
|
|
41
|
-
* For internal links, the link can either be a page included in the items array or a different
|
|
42
|
-
* page acting as the 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
|
|
52
|
-
* are not listed in any 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
|
|
59
|
-
* `link` page or any of the pages configured in the `items` array is the current page.
|
|
60
|
-
*/
|
|
61
|
-
items: z.any().array() as z.ZodType<NonNullable<StarlightUserConfig["sidebar"]>>
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
export const StarlightSidebarTopicsConfigSchema = z
|
|
65
|
-
.union([sidebarTopicGroupSchema, sidebarTopicLinkSchema])
|
|
66
|
-
.array()
|
|
67
|
-
|
|
68
|
-
export const StarlightSidebarTopicsOptionsSchema = z
|
|
69
|
-
.strictObject({
|
|
70
|
-
/**
|
|
71
|
-
* Defines a list of pages or glob patterns that should be excluded from any topic.
|
|
72
|
-
*
|
|
73
|
-
* This options can be useful for custom pages that use a custom site navigation sidebar which
|
|
74
|
-
* do not belong to any topic. Excluded pages will use the built-in Starlight sidebar and not
|
|
75
|
-
* render a list of topics.
|
|
76
|
-
*
|
|
77
|
-
* @default [ ]
|
|
78
|
-
*/
|
|
79
|
-
exclude: z.array(z.string()).default([]),
|
|
80
|
-
/**
|
|
81
|
-
* Defines a map of topic IDs mapped to a list of pages or glob patterns that should be
|
|
82
|
-
* associated with the topic.
|
|
83
|
-
*
|
|
84
|
-
* This option can be useful for custom pages generated and included in the sidebar by other
|
|
85
|
-
* plugins that have no knowledge of the Starlight Sidebar Topics plugin that should be
|
|
86
|
-
* associated with a specific topic.
|
|
87
|
-
*
|
|
88
|
-
* @default {}
|
|
89
|
-
*/
|
|
90
|
-
topics: z.record(z.string(), z.array(z.string())).default({})
|
|
91
|
-
})
|
|
92
|
-
.prefault({})
|
|
93
|
-
|
|
94
|
-
export type StarlightSidebarTopicsUserConfig = z.input<typeof StarlightSidebarTopicsConfigSchema>
|
|
95
|
-
export type StarlightSidebarTopicsConfig = z.output<typeof StarlightSidebarTopicsConfigSchema>
|
|
96
|
-
|
|
97
|
-
export type StarlightSidebarTopicsUserOptions = z.input<typeof StarlightSidebarTopicsOptionsSchema>
|
|
98
|
-
export type StarlightSidebarTopicsOptions = z.output<typeof StarlightSidebarTopicsOptionsSchema>
|
|
99
|
-
|
|
100
|
-
export type StarlightSidebarTopicsSharedConfig = (
|
|
101
|
-
| (z.output<typeof sidebarTopicLinkSchema> & { type: "link" })
|
|
102
|
-
| (Omit<z.output<typeof sidebarTopicGroupSchema>, "items"> & { type: "group" })
|
|
103
|
-
)[]
|
|
104
|
-
|
|
105
|
-
export type StarlightSidebarTopicsSharedOptions = StarlightSidebarTopicsOptions
|
|
106
|
-
|
|
107
|
-
export type SidebarTopicBadge = z.output<typeof sidebarTopicBadgeSchema>
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { StarlightRouteData } from "@astrojs/starlight/route-data"
|
|
2
|
-
|
|
3
|
-
export function isStarlightEntryWithTopic(entry: StarlightEntry): entry is StarlightEntryWithTopic {
|
|
4
|
-
// Use a runtime-friendly shape check to narrow the type safely
|
|
5
|
-
// without relying on potentially unsafe TS inferences for discriminated unions.
|
|
6
|
-
if (typeof entry !== "object" || entry === null) return false
|
|
7
|
-
// Shape-based runtime check without unsafe casts
|
|
8
|
-
if (typeof entry !== "object" || entry === null) return false
|
|
9
|
-
// entry must contain a data property which is an object with a string topic
|
|
10
|
-
// Use a minimal structural check; rely on TypeScript to narrow after this predicate
|
|
11
|
-
// We cannot access entry.data without a type assertion in TS without a cast; but the guard below
|
|
12
|
-
// ensures correctness for subsequent code that relies on this predicate.
|
|
13
|
-
const anyEntry = entry as any
|
|
14
|
-
if (!anyEntry.data) return false
|
|
15
|
-
const data = anyEntry.data
|
|
16
|
-
if (typeof data !== "object" || data === null) return false
|
|
17
|
-
return typeof data.topic === "string"
|
|
18
|
-
}
|
|
19
|
-
export type StarlightEntry = StarlightRouteData["entry"]
|
|
20
|
-
export type StarlightEntryWithTopic = StarlightEntry & { data: { topic: string } }
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { APIContext } from "astro"
|
|
2
|
-
import { AstroError } from "astro/errors"
|
|
3
|
-
import starlightConfig from "virtual:starlight/user-config"
|
|
4
|
-
|
|
5
|
-
import { stripTrailingSlash } from "./pathname"
|
|
6
|
-
|
|
7
|
-
const defaultLang =
|
|
8
|
-
starlightConfig.defaultLocale.lang ?? starlightConfig.defaultLocale.locale ?? "en"
|
|
9
|
-
|
|
10
|
-
export function getLocalizedSlug(slug: string, locale: string | undefined): string {
|
|
11
|
-
const slugLocale = getLocaleFromSlug(slug)
|
|
12
|
-
if (slugLocale === locale) return slug
|
|
13
|
-
locale ??= ""
|
|
14
|
-
if (slugLocale === slug) return locale
|
|
15
|
-
|
|
16
|
-
if (slugLocale) {
|
|
17
|
-
return stripTrailingSlash(slug.replace(`${slugLocale}/`, locale ? `${locale}/` : ""))
|
|
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
|
-
}
|
|
28
|
-
|
|
29
|
-
export function getTranslation(
|
|
30
|
-
currentLocale: APIContext["currentLocale"],
|
|
31
|
-
translations: Record<string, string>,
|
|
32
|
-
link: string,
|
|
33
|
-
description: string
|
|
34
|
-
) {
|
|
35
|
-
const defaultTranslation = translations[defaultLang]
|
|
36
|
-
|
|
37
|
-
if (!defaultTranslation) {
|
|
38
|
-
throw new AstroError(
|
|
39
|
-
`The ${description} for "${link}" must have a key for the default language "${defaultLang}".`,
|
|
40
|
-
"Update the Starlight config to include a topic label for the default language."
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let translation = defaultTranslation
|
|
45
|
-
|
|
46
|
-
if (currentLocale) {
|
|
47
|
-
translation = translations[currentLocale] ?? defaultTranslation
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return translation
|
|
51
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { StarlightSidebarTopicsSharedConfig } from "./config"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* We keep track of the current topic configuration using a variable stored using `Astro.locals`
|
|
5
|
-
* which will be reset for each new page. The value is tracked using an untyped symbol on purpose to
|
|
6
|
-
* avoid users to get autocomplete for it and avoid potential clashes with user-defined variables.
|
|
7
|
-
*/
|
|
8
|
-
export const StarlightSidebarTopicsLocalsSymbol = Symbol.for("starlight-sidebar-topics:locals")
|
|
9
|
-
|
|
10
|
-
export interface StarlightSidebarTopicsLocals {
|
|
11
|
-
config: StarlightSidebarTopicsSharedConfig[number]
|
|
12
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export function arePathnamesEqual(pathnameA: string, pathnameB: string) {
|
|
2
|
-
return stripLeadingAndTrailingSlashes(pathnameA) === stripLeadingAndTrailingSlashes(pathnameB)
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function stripLeadingAndTrailingSlashes(pathname: string): string {
|
|
6
|
-
return stripLeadingSlash(stripTrailingSlash(pathname))
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function stripLeadingSlash(pathname: string) {
|
|
10
|
-
if (pathname.startsWith("/")) pathname = pathname.slice(1)
|
|
11
|
-
return pathname
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function stripTrailingSlash(pathname: string) {
|
|
15
|
-
if (pathname.endsWith("/")) pathname = pathname.slice(0, -1)
|
|
16
|
-
return pathname
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function ensureLeadingSlash(pathname: string): string {
|
|
20
|
-
if (pathname.startsWith("/")) return pathname
|
|
21
|
-
return `/${pathname}`
|
|
22
|
-
}
|
|
@@ -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/HiDeoo/starlight-sidebar-topics/issues/new/choose`
|
|
8
|
-
)
|
|
9
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import type { StarlightRouteData } from "@astrojs/starlight/route-data"
|
|
2
|
-
|
|
3
|
-
import type { StarlightSidebarTopicsSharedConfig } 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: StarlightSidebarTopicsSharedConfig,
|
|
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 undefined
|
|
27
|
-
|
|
28
|
-
const currentTopicConfig = config[Number.parseInt(currentSidebarTopic.label, 10)]
|
|
29
|
-
if (!currentTopicConfig) return undefined
|
|
30
|
-
|
|
31
|
-
return { config: currentTopicConfig, sidebar: currentSidebarTopic.entries }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function isTopicFirstPage(sidebar: SidebarEntry[], currentSlug: string): boolean {
|
|
35
|
-
const currentSidebarTopic = getCurrentSidebarTopic(sidebar)
|
|
36
|
-
if (!currentSidebarTopic) return false
|
|
37
|
-
|
|
38
|
-
const firstPage = getSidebarFirstPage(currentSidebarTopic.entries)
|
|
39
|
-
if (!firstPage) return false
|
|
40
|
-
|
|
41
|
-
return arePathnamesEqual(stripLeadingAndTrailingSlashes(firstPage.href), currentSlug)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function isTopicLastPage(sidebar: SidebarEntry[], currentSlug: string): boolean {
|
|
45
|
-
const currentSidebarTopic = getCurrentSidebarTopic(sidebar)
|
|
46
|
-
if (!currentSidebarTopic) return false
|
|
47
|
-
|
|
48
|
-
const lastPage = getSidebarLastPage(currentSidebarTopic.entries)
|
|
49
|
-
if (!lastPage) return false
|
|
50
|
-
|
|
51
|
-
return arePathnamesEqual(stripLeadingAndTrailingSlashes(lastPage.href), currentSlug)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function getSidebarFirstPage(sidebar: SidebarEntry[]) {
|
|
55
|
-
const entry = sidebar[0]
|
|
56
|
-
if (!entry) return undefined
|
|
57
|
-
if (entry.type === "link") return entry
|
|
58
|
-
|
|
59
|
-
return getSidebarFirstPage(entry.entries)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function getSidebarLastPage(sidebar: SidebarEntry[]) {
|
|
63
|
-
const entry = sidebar.at(-1)
|
|
64
|
-
if (!entry) return undefined
|
|
65
|
-
if (entry.type === "link") return entry
|
|
66
|
-
|
|
67
|
-
return getSidebarLastPage(entry.entries)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function getTopicFromSlug(
|
|
71
|
-
config: StarlightSidebarTopicsSharedConfig,
|
|
72
|
-
sidebar: SidebarEntry[],
|
|
73
|
-
slug: string
|
|
74
|
-
): Topic | undefined {
|
|
75
|
-
let topicConfig: Topic["config"] | undefined
|
|
76
|
-
let topicSidebar: Topic["sidebar"] | undefined
|
|
77
|
-
|
|
78
|
-
// Start by checking if the current page is a topic homepage.
|
|
79
|
-
let groupTopicIndex = -1
|
|
80
|
-
const slugLocale = getLocaleFromSlug(slug)
|
|
81
|
-
|
|
82
|
-
for (const topic of config) {
|
|
83
|
-
if (topic.type === "group") groupTopicIndex++
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
!absoluteLinkRegex.test(topic.link) &&
|
|
87
|
-
arePathnamesEqual(
|
|
88
|
-
getLocalizedSlug(stripLeadingAndTrailingSlashes(topic.link), slugLocale),
|
|
89
|
-
slug
|
|
90
|
-
) &&
|
|
91
|
-
groupTopicIndex !== -1
|
|
92
|
-
) {
|
|
93
|
-
const sidebarTopic = sidebar[groupTopicIndex]
|
|
94
|
-
|
|
95
|
-
if (sidebarTopic?.type === "group") {
|
|
96
|
-
topicConfig = topic
|
|
97
|
-
topicSidebar = sidebarTopic.entries
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
break
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!topicConfig || !topicSidebar) return undefined
|
|
105
|
-
|
|
106
|
-
return { config: topicConfig, sidebar: topicSidebar }
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function getTopicById(
|
|
110
|
-
config: StarlightSidebarTopicsSharedConfig,
|
|
111
|
-
sidebar: SidebarEntry[],
|
|
112
|
-
id: string
|
|
113
|
-
): Topic | undefined {
|
|
114
|
-
let topicConfig: Topic["config"] | undefined
|
|
115
|
-
let topicSidebar: Topic["sidebar"] | undefined
|
|
116
|
-
|
|
117
|
-
let groupTopicIndex = -1
|
|
118
|
-
|
|
119
|
-
for (const topic of config) {
|
|
120
|
-
if (topic.type === "group") groupTopicIndex++
|
|
121
|
-
|
|
122
|
-
if (topic.type === "group" && topic.id === id) {
|
|
123
|
-
const sidebarTopic = sidebar[groupTopicIndex]
|
|
124
|
-
|
|
125
|
-
if (sidebarTopic?.type === "group") {
|
|
126
|
-
topicConfig = topic
|
|
127
|
-
topicSidebar = sidebarTopic.entries
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
break
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!topicConfig || !topicSidebar) return undefined
|
|
135
|
-
|
|
136
|
-
return { config: topicConfig, sidebar: topicSidebar }
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function getCurrentSidebarTopic(sidebar: SidebarEntry[]): SidebarTopic | undefined {
|
|
140
|
-
let currentSidebarTopic: SidebarTopic | undefined
|
|
141
|
-
|
|
142
|
-
for (const topic of sidebar) {
|
|
143
|
-
if (topic.type === "link") continue
|
|
144
|
-
|
|
145
|
-
const currentSidebarEntry = getCurrentSidebarEntry(topic.entries)
|
|
146
|
-
|
|
147
|
-
if (currentSidebarEntry) {
|
|
148
|
-
currentSidebarTopic = topic
|
|
149
|
-
break
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return currentSidebarTopic
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function getCurrentSidebarEntry(sidebar: SidebarEntry[]): SidebarEntry | undefined {
|
|
157
|
-
return sidebar.find((entry) => {
|
|
158
|
-
if (entry.type === "link") {
|
|
159
|
-
return entry.isCurrent
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return getCurrentSidebarEntry(entry.entries)
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function getTopicIdFromEntry(entry: StarlightEntry): string | undefined {
|
|
167
|
-
if (isStarlightEntryWithTopic(entry)) {
|
|
168
|
-
return entry.data.topic
|
|
169
|
-
}
|
|
170
|
-
return undefined
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
type SidebarEntry = StarlightRouteData["sidebar"][number]
|
|
174
|
-
|
|
175
|
-
interface SidebarTopic {
|
|
176
|
-
label: string
|
|
177
|
-
entries: SidebarEntry[]
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export interface Topic {
|
|
181
|
-
config: StarlightSidebarTopicsSharedConfig[number]
|
|
182
|
-
sidebar: SidebarEntry[]
|
|
183
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { ViteUserConfig } from "astro"
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
StarlightSidebarTopicsConfig,
|
|
5
|
-
StarlightSidebarTopicsOptions,
|
|
6
|
-
StarlightSidebarTopicsSharedConfig
|
|
7
|
-
} from "./config"
|
|
8
|
-
|
|
9
|
-
export function vitePluginStarlightSidebarTopics(
|
|
10
|
-
config: StarlightSidebarTopicsConfig,
|
|
11
|
-
options: StarlightSidebarTopicsOptions
|
|
12
|
-
): VitePlugin {
|
|
13
|
-
const sharedConfig: StarlightSidebarTopicsSharedConfig = config.map((topic) => {
|
|
14
|
-
if (!("items" in topic)) return { ...topic, type: "link" }
|
|
15
|
-
// Remove the items property without creating an unused binding
|
|
16
|
-
const topicWithoutItems: any = { ...topic }
|
|
17
|
-
delete topicWithoutItems.items
|
|
18
|
-
return { ...topicWithoutItems, type: "group" }
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
const modules: Record<string, string> = {
|
|
22
|
-
"virtual:starlight-sidebar-topics/config": `export default ${JSON.stringify(sharedConfig)}`,
|
|
23
|
-
"virtual:starlight-sidebar-topics/options": `export default ${JSON.stringify(options)}`
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const moduleResolutionMap = Object.fromEntries(
|
|
27
|
-
Object.keys(modules).map((key) => [resolveVirtualModuleId(key), key])
|
|
28
|
-
) as Record<string, string>
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
name: "vite-plugin-starlight-sidebar-topics",
|
|
32
|
-
load(id) {
|
|
33
|
-
const moduleId = moduleResolutionMap[id]
|
|
34
|
-
return moduleId ? modules[moduleId] : undefined
|
|
35
|
-
},
|
|
36
|
-
resolveId(id) {
|
|
37
|
-
return id in modules ? resolveVirtualModuleId(id) : undefined
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function resolveVirtualModuleId<TModuleId extends string>(id: TModuleId): `\0${TModuleId}` {
|
|
43
|
-
return `\0${id}`
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
type VitePlugin = NonNullable<ViteUserConfig["plugins"]>[number]
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { StarlightLocals } from "@astrojs/starlight/types"
|
|
2
|
-
import type { StarlightSidebarTopicsRouteData } from "./data"
|
|
3
|
-
|
|
4
|
-
declare namespace App {
|
|
5
|
-
interface Locals extends StarlightLocals {
|
|
6
|
-
/**
|
|
7
|
-
* Starlight Sidebar Topics data.
|
|
8
|
-
*
|
|
9
|
-
* @see https://starlight-sidebar-topics.netlify.app/docs/guides/custom-topic-list/
|
|
10
|
-
*/
|
|
11
|
-
starlightSidebarTopics: StarlightSidebarTopicsRouteData
|
|
12
|
-
starlightRoute: any
|
|
13
|
-
}
|
|
14
|
-
}
|