@rimelight/ui 0.0.15 → 0.0.16
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 +3 -1
- package/src/env.d.ts +3 -0
- package/src/integrations/sri.ts +92 -92
- package/src/middleware/security.ts +9 -2
- package/src/middleware/sri.ts +78 -78
- package/src/plugins/starlightAddons/Sidebar.astro +89 -0
- package/src/plugins/starlightAddons/data.ts +39 -0
- package/src/plugins/starlightAddons/index.ts +132 -63
- package/src/plugins/starlightAddons/libs/config.ts +107 -0
- package/src/plugins/starlightAddons/libs/content.ts +20 -0
- package/src/plugins/starlightAddons/libs/i18n.ts +51 -0
- package/src/plugins/starlightAddons/libs/locals.ts +12 -0
- package/src/plugins/starlightAddons/libs/pathname.ts +22 -0
- package/src/plugins/starlightAddons/libs/plugin.ts +9 -0
- package/src/plugins/starlightAddons/libs/sidebar.ts +183 -0
- package/src/plugins/starlightAddons/libs/vite.ts +46 -0
- package/src/plugins/starlightAddons/locals.d.ts +14 -0
- package/src/plugins/starlightAddons/middleware.ts +132 -0
- package/src/plugins/starlightAddons/overrides/Sidebar.astro +8 -0
- package/src/plugins/starlightAddons/schema.ts +13 -0
- package/src/plugins/starlightAddons/starlight.d.ts +6 -0
- package/src/plugins/starlightAddons/virtual.d.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment UI Library.",
|
|
6
6
|
"homepage": "https://rimelight.com/docs",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"css-variants": "2.3.5",
|
|
58
58
|
"defu": "6.1.6",
|
|
59
59
|
"embla-carousel": "8.6.0",
|
|
60
|
+
"picomatch": "^4.0.4",
|
|
60
61
|
"tailwind-merge": "^3.3.1",
|
|
61
62
|
"tailwindcss": "^4.2.3",
|
|
62
63
|
"turndown": "^7.2.4",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"@astrojs/ts-plugin": "1.10.7",
|
|
70
71
|
"@e18e/eslint-plugin": "0.3.0",
|
|
71
72
|
"@types/node": "25.5.2",
|
|
73
|
+
"@types/picomatch": "^4.0.3",
|
|
72
74
|
"@unocss/eslint-plugin": "66.6.8",
|
|
73
75
|
"astro": "6.1.8",
|
|
74
76
|
"eslint-plugin-regexp": "3.1.0",
|
package/src/env.d.ts
CHANGED
|
@@ -38,5 +38,8 @@ declare module "turndown-plugin-gfm" {
|
|
|
38
38
|
declare namespace App {
|
|
39
39
|
interface Locals {
|
|
40
40
|
cfContext?: Record<string, unknown>
|
|
41
|
+
// Extended locals used by Starlight Addons for richer type safety
|
|
42
|
+
starlightSidebarTopics?: any
|
|
43
|
+
starlightRoute?: any
|
|
41
44
|
}
|
|
42
45
|
}
|
package/src/integrations/sri.ts
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import type { AstroIntegration } from "astro"
|
|
2
|
-
import { createHash } from "node:crypto"
|
|
3
|
-
|
|
4
|
-
interface SRIConfig {
|
|
5
|
-
security?: {
|
|
6
|
-
csp?: {
|
|
7
|
-
scriptDirective?: {
|
|
8
|
-
resources?: string[]
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function assertSRIConfig(obj: unknown): asserts obj is SRIConfig {
|
|
15
|
-
if (!obj || typeof obj !== "object") throw new Error("Expected config")
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Astro SRI Integration Fetches external scripts at build-time and generates hashes for the SSR
|
|
20
|
-
* middleware.
|
|
21
|
-
*/
|
|
22
|
-
export function sri(): AstroIntegration {
|
|
23
|
-
const sriManifest: Record<string, string> = {}
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
name: "sri",
|
|
27
|
-
hooks: {
|
|
28
|
-
"astro:config:setup": async ({ updateConfig, config, logger }) => {
|
|
29
|
-
logger.info("Initializing SRI Discovery...")
|
|
30
|
-
|
|
31
|
-
// 1. Identify External Scripts from CSP config
|
|
32
|
-
// We look into the custom 'security' block in astro.config.mjs
|
|
33
|
-
assertSRIConfig(config)
|
|
34
|
-
|
|
35
|
-
const security = config.security
|
|
36
|
-
const externalUrls: string[] = []
|
|
37
|
-
|
|
38
|
-
if (security?.csp?.scriptDirective?.resources) {
|
|
39
|
-
for (const resource of security.csp.scriptDirective.resources) {
|
|
40
|
-
if (resource.startsWith("https://")) {
|
|
41
|
-
externalUrls.push(resource)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 2. Fetch and Hash External Scripts
|
|
47
|
-
await Promise.all(
|
|
48
|
-
externalUrls.map(async (url) => {
|
|
49
|
-
try {
|
|
50
|
-
logger.info(`Fetching ${url} for SRI hashing...`)
|
|
51
|
-
const response = await fetch(url)
|
|
52
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
53
|
-
|
|
54
|
-
const buffer = await response.arrayBuffer()
|
|
55
|
-
const hash = createHash("sha384").update(Buffer.from(buffer)).digest("base64")
|
|
56
|
-
|
|
57
|
-
sriManifest[url] = `sha384-${hash}`
|
|
58
|
-
logger.info(`Successfully hashed ${url}`)
|
|
59
|
-
} catch (err: unknown) {
|
|
60
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
61
|
-
logger.error(`Failed to fetch SRI for ${url}: ${message}`)
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
// 3. Provide hashes to the runtime via Vite's virtual module
|
|
67
|
-
const virtualModuleId = "virtual:sri-manifest"
|
|
68
|
-
const resolvedVirtualModuleId = "\0" + virtualModuleId
|
|
69
|
-
|
|
70
|
-
updateConfig({
|
|
71
|
-
vite: {
|
|
72
|
-
plugins: [
|
|
73
|
-
{
|
|
74
|
-
name: "vite-plugin-sri-manifest",
|
|
75
|
-
resolveId(id: string) {
|
|
76
|
-
if (id === virtualModuleId) return resolvedVirtualModuleId
|
|
77
|
-
return null
|
|
78
|
-
},
|
|
79
|
-
load(id: string) {
|
|
80
|
-
if (id === resolvedVirtualModuleId) {
|
|
81
|
-
return `export const manifest = ${JSON.stringify(sriManifest)};`
|
|
82
|
-
}
|
|
83
|
-
return null
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
1
|
+
import type { AstroIntegration } from "astro"
|
|
2
|
+
import { createHash } from "node:crypto"
|
|
3
|
+
|
|
4
|
+
interface SRIConfig {
|
|
5
|
+
security?: {
|
|
6
|
+
csp?: {
|
|
7
|
+
scriptDirective?: {
|
|
8
|
+
resources?: string[]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function assertSRIConfig(obj: unknown): asserts obj is SRIConfig {
|
|
15
|
+
if (!obj || typeof obj !== "object") throw new Error("Expected config")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Astro SRI Integration Fetches external scripts at build-time and generates hashes for the SSR
|
|
20
|
+
* middleware.
|
|
21
|
+
*/
|
|
22
|
+
export function sri(): AstroIntegration {
|
|
23
|
+
const sriManifest: Record<string, string> = {}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
name: "sri",
|
|
27
|
+
hooks: {
|
|
28
|
+
"astro:config:setup": async ({ updateConfig, config, logger }) => {
|
|
29
|
+
logger.info("Initializing SRI Discovery...")
|
|
30
|
+
|
|
31
|
+
// 1. Identify External Scripts from CSP config
|
|
32
|
+
// We look into the custom 'security' block in astro.config.mjs
|
|
33
|
+
assertSRIConfig(config)
|
|
34
|
+
|
|
35
|
+
const security = config.security
|
|
36
|
+
const externalUrls: string[] = []
|
|
37
|
+
|
|
38
|
+
if (security?.csp?.scriptDirective?.resources) {
|
|
39
|
+
for (const resource of security.csp.scriptDirective.resources) {
|
|
40
|
+
if (resource.startsWith("https://")) {
|
|
41
|
+
externalUrls.push(resource)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Fetch and Hash External Scripts
|
|
47
|
+
await Promise.all(
|
|
48
|
+
externalUrls.map(async (url) => {
|
|
49
|
+
try {
|
|
50
|
+
logger.info(`Fetching ${url} for SRI hashing...`)
|
|
51
|
+
const response = await fetch(url)
|
|
52
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
53
|
+
|
|
54
|
+
const buffer = await response.arrayBuffer()
|
|
55
|
+
const hash = createHash("sha384").update(Buffer.from(buffer)).digest("base64")
|
|
56
|
+
|
|
57
|
+
sriManifest[url] = `sha384-${hash}`
|
|
58
|
+
logger.info(`Successfully hashed ${url}`)
|
|
59
|
+
} catch (err: unknown) {
|
|
60
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
61
|
+
logger.error(`Failed to fetch SRI for ${url}: ${message}`)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// 3. Provide hashes to the runtime via Vite's virtual module
|
|
67
|
+
const virtualModuleId = "virtual:sri-manifest"
|
|
68
|
+
const resolvedVirtualModuleId = "\0" + virtualModuleId
|
|
69
|
+
|
|
70
|
+
updateConfig({
|
|
71
|
+
vite: {
|
|
72
|
+
plugins: [
|
|
73
|
+
{
|
|
74
|
+
name: "vite-plugin-sri-manifest",
|
|
75
|
+
resolveId(id: string) {
|
|
76
|
+
if (id === virtualModuleId) return resolvedVirtualModuleId
|
|
77
|
+
return null
|
|
78
|
+
},
|
|
79
|
+
load(id: string) {
|
|
80
|
+
if (id === resolvedVirtualModuleId) {
|
|
81
|
+
return `export const manifest = ${JSON.stringify(sriManifest)};`
|
|
82
|
+
}
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { defineMiddleware } from "astro:middleware"
|
|
2
2
|
|
|
3
|
-
export const security = defineMiddleware(async (
|
|
3
|
+
export const security = defineMiddleware(async (context, next) => {
|
|
4
|
+
// HTTP to HTTPS redirect
|
|
5
|
+
if (context.request.url.startsWith("http://")) {
|
|
6
|
+
const httpsUrl = context.request.url.replace(/^http:/, "https:")
|
|
7
|
+
return Response.redirect(httpsUrl, 301)
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
const response = await next()
|
|
5
11
|
|
|
6
12
|
const newResponse = new Response(response.body, response)
|
|
@@ -10,6 +16,7 @@ export const security = defineMiddleware(async (_, next) => {
|
|
|
10
16
|
newResponse.headers.set("X-Content-Type-Options", "nosniff")
|
|
11
17
|
newResponse.headers.set("X-Frame-Options", "DENY")
|
|
12
18
|
newResponse.headers.set("Cross-Origin-Resource-Policy", "same-origin")
|
|
19
|
+
newResponse.headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
|
13
20
|
|
|
14
21
|
return newResponse
|
|
15
|
-
})
|
|
22
|
+
})
|
package/src/middleware/sri.ts
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
import { defineMiddleware } from "astro:middleware"
|
|
2
|
-
// @ts-ignore - virtual module generated by the SRI integration
|
|
3
|
-
import { manifest } from "virtual:sri-manifest"
|
|
4
|
-
|
|
5
|
-
declare const HTMLRewriter: any
|
|
6
|
-
|
|
7
|
-
function assertManifest(obj: unknown): asserts obj is Record<string, string> {
|
|
8
|
-
if (!obj || typeof obj !== "object") throw new Error("Expected manifest")
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* SRI Middleware Injects integrity attributes into script tags based on the pre-generated manifest.
|
|
13
|
-
* Handles both SSR streaming (via HTMLRewriter) and post-rendering fallback.
|
|
14
|
-
*/
|
|
15
|
-
export const sri = defineMiddleware(async (_, next) => {
|
|
16
|
-
const response = await next()
|
|
17
|
-
const contentType = response.headers.get("content-type") || ""
|
|
18
|
-
|
|
19
|
-
// Only process HTML responses
|
|
20
|
-
if (!contentType.includes("text/html")) {
|
|
21
|
-
return response
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Use Cloudflare's HTMLRewriter if available (SSR Production/Preview)
|
|
25
|
-
if (typeof HTMLRewriter !== "undefined") {
|
|
26
|
-
return new HTMLRewriter()
|
|
27
|
-
.on("script[src]", {
|
|
28
|
-
element(el: any) {
|
|
29
|
-
const src = el.getAttribute("src")
|
|
30
|
-
if (src && manifest[src]) {
|
|
31
|
-
el.setAttribute("integrity", manifest[src])
|
|
32
|
-
el.setAttribute("crossorigin", "anonymous")
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
.on('link[rel="stylesheet"][href]', {
|
|
37
|
-
element(el: any) {
|
|
38
|
-
const href = el.getAttribute("href")
|
|
39
|
-
if (href && manifest[href]) {
|
|
40
|
-
el.setAttribute("integrity", manifest[href])
|
|
41
|
-
el.setAttribute("crossorigin", "anonymous")
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
.transform(response)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Fallback for Dev
|
|
49
|
-
let modifiedHtml = await response.text()
|
|
50
|
-
|
|
51
|
-
// Simple RegEx replacement for Dev inspection
|
|
52
|
-
assertManifest(manifest)
|
|
53
|
-
|
|
54
|
-
const entries = Object.entries(manifest)
|
|
55
|
-
for (const [url, hash] of entries) {
|
|
56
|
-
if (!hash) continue
|
|
57
|
-
|
|
58
|
-
// Look for script tags with the specific URL
|
|
59
|
-
const scriptRegex = new RegExp(`(<script[^>]+src=["']${url}["'][^>]*)(/?>)`, "g")
|
|
60
|
-
modifiedHtml = modifiedHtml.replace(
|
|
61
|
-
scriptRegex,
|
|
62
|
-
`$1 integrity="${hash}" crossorigin="anonymous"$2`
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
// Look for link tags with the specific URL
|
|
66
|
-
const linkRegex = new RegExp(`(<link[^>]+href=["']${url}["'][^>]*)(/?>)`, "g")
|
|
67
|
-
modifiedHtml = modifiedHtml.replace(
|
|
68
|
-
linkRegex,
|
|
69
|
-
`$1 integrity="${hash}" crossorigin="anonymous"$2`
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return new Response(modifiedHtml, {
|
|
74
|
-
status: response.status,
|
|
75
|
-
statusText: response.statusText,
|
|
76
|
-
headers: response.headers
|
|
77
|
-
})
|
|
78
|
-
})
|
|
1
|
+
import { defineMiddleware } from "astro:middleware"
|
|
2
|
+
// @ts-ignore - virtual module generated by the SRI integration
|
|
3
|
+
import { manifest } from "virtual:sri-manifest"
|
|
4
|
+
|
|
5
|
+
declare const HTMLRewriter: any
|
|
6
|
+
|
|
7
|
+
function assertManifest(obj: unknown): asserts obj is Record<string, string> {
|
|
8
|
+
if (!obj || typeof obj !== "object") throw new Error("Expected manifest")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SRI Middleware Injects integrity attributes into script tags based on the pre-generated manifest.
|
|
13
|
+
* Handles both SSR streaming (via HTMLRewriter) and post-rendering fallback.
|
|
14
|
+
*/
|
|
15
|
+
export const sri = defineMiddleware(async (_, next) => {
|
|
16
|
+
const response = await next()
|
|
17
|
+
const contentType = response.headers.get("content-type") || ""
|
|
18
|
+
|
|
19
|
+
// Only process HTML responses
|
|
20
|
+
if (!contentType.includes("text/html")) {
|
|
21
|
+
return response
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Use Cloudflare's HTMLRewriter if available (SSR Production/Preview)
|
|
25
|
+
if (typeof HTMLRewriter !== "undefined") {
|
|
26
|
+
return new HTMLRewriter()
|
|
27
|
+
.on("script[src]", {
|
|
28
|
+
element(el: any) {
|
|
29
|
+
const src = el.getAttribute("src")
|
|
30
|
+
if (src && manifest[src]) {
|
|
31
|
+
el.setAttribute("integrity", manifest[src])
|
|
32
|
+
el.setAttribute("crossorigin", "anonymous")
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
.on('link[rel="stylesheet"][href]', {
|
|
37
|
+
element(el: any) {
|
|
38
|
+
const href = el.getAttribute("href")
|
|
39
|
+
if (href && manifest[href]) {
|
|
40
|
+
el.setAttribute("integrity", manifest[href])
|
|
41
|
+
el.setAttribute("crossorigin", "anonymous")
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.transform(response)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fallback for Dev
|
|
49
|
+
let modifiedHtml = await response.text()
|
|
50
|
+
|
|
51
|
+
// Simple RegEx replacement for Dev inspection
|
|
52
|
+
assertManifest(manifest)
|
|
53
|
+
|
|
54
|
+
const entries = Object.entries(manifest)
|
|
55
|
+
for (const [url, hash] of entries) {
|
|
56
|
+
if (!hash) continue
|
|
57
|
+
|
|
58
|
+
// Look for script tags with the specific URL
|
|
59
|
+
const scriptRegex = new RegExp(`(<script[^>]+src=["']${url}["'][^>]*)(/?>)`, "g")
|
|
60
|
+
modifiedHtml = modifiedHtml.replace(
|
|
61
|
+
scriptRegex,
|
|
62
|
+
`$1 integrity="${hash}" crossorigin="anonymous"$2`
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Look for link tags with the specific URL
|
|
66
|
+
const linkRegex = new RegExp(`(<link[^>]+href=["']${url}["'][^>]*)(/?>)`, "g")
|
|
67
|
+
modifiedHtml = modifiedHtml.replace(
|
|
68
|
+
linkRegex,
|
|
69
|
+
`$1 integrity="${hash}" crossorigin="anonymous"$2`
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return new Response(modifiedHtml, {
|
|
74
|
+
status: response.status,
|
|
75
|
+
statusText: response.statusText,
|
|
76
|
+
headers: response.headers
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
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>
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
}
|