@farming-labs/astro-theme 0.0.2-beta.15
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 +75 -0
- package/src/components/DocsContent.astro +39 -0
- package/src/components/DocsLayout.astro +321 -0
- package/src/components/DocsPage.astro +178 -0
- package/src/components/FloatingAIChat.astro +468 -0
- package/src/components/SearchDialog.astro +315 -0
- package/src/components/ThemeToggle.astro +46 -0
- package/src/index.d.ts +10 -0
- package/src/index.js +10 -0
- package/src/lib/renderMarkdown.js +110 -0
- package/src/themes/darksharp.d.ts +4 -0
- package/src/themes/darksharp.js +42 -0
- package/src/themes/default.d.ts +4 -0
- package/src/themes/default.js +42 -0
- package/src/themes/pixel-border.d.ts +4 -0
- package/src/themes/pixel-border.js +38 -0
- package/styles/darksharp-bundle.css +6 -0
- package/styles/darksharp.css +206 -0
- package/styles/docs.css +2176 -0
- package/styles/pixel-border-bundle.css +6 -0
- package/styles/pixel-border.css +606 -0
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@farming-labs/astro-theme",
|
|
3
|
+
"version": "0.0.2-beta.15",
|
|
4
|
+
"description": "Astro UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"import": "./src/index.js",
|
|
10
|
+
"default": "./src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./src/components/*": "./src/components/*",
|
|
13
|
+
"./fumadocs": {
|
|
14
|
+
"types": "./src/themes/default.d.ts",
|
|
15
|
+
"import": "./src/themes/default.js",
|
|
16
|
+
"default": "./src/themes/default.js"
|
|
17
|
+
},
|
|
18
|
+
"./pixel-border": {
|
|
19
|
+
"types": "./src/themes/pixel-border.d.ts",
|
|
20
|
+
"import": "./src/themes/pixel-border.js",
|
|
21
|
+
"default": "./src/themes/pixel-border.js"
|
|
22
|
+
},
|
|
23
|
+
"./darksharp": {
|
|
24
|
+
"types": "./src/themes/darksharp.d.ts",
|
|
25
|
+
"import": "./src/themes/darksharp.js",
|
|
26
|
+
"default": "./src/themes/darksharp.js"
|
|
27
|
+
},
|
|
28
|
+
"./css": "./styles/docs.css",
|
|
29
|
+
"./fumadocs/css": "./styles/docs.css",
|
|
30
|
+
"./styles/pixel-border.css": "./styles/pixel-border.css",
|
|
31
|
+
"./styles/darksharp.css": "./styles/darksharp.css",
|
|
32
|
+
"./pixel-border/css": "./styles/pixel-border-bundle.css",
|
|
33
|
+
"./darksharp/css": "./styles/darksharp-bundle.css"
|
|
34
|
+
},
|
|
35
|
+
"typesVersions": {
|
|
36
|
+
"*": {
|
|
37
|
+
"pixel-border": [
|
|
38
|
+
"./src/themes/pixel-border.d.ts"
|
|
39
|
+
],
|
|
40
|
+
"darksharp": [
|
|
41
|
+
"./src/themes/darksharp.d.ts"
|
|
42
|
+
],
|
|
43
|
+
"fumadocs": [
|
|
44
|
+
"./src/themes/default.d.ts"
|
|
45
|
+
],
|
|
46
|
+
".": [
|
|
47
|
+
"./src/index.d.ts"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"src",
|
|
53
|
+
"styles"
|
|
54
|
+
],
|
|
55
|
+
"keywords": [
|
|
56
|
+
"docs",
|
|
57
|
+
"astro",
|
|
58
|
+
"theme",
|
|
59
|
+
"documentation"
|
|
60
|
+
],
|
|
61
|
+
"author": "Farming Labs",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"sugar-high": "^0.9.5",
|
|
65
|
+
"@farming-labs/astro": "0.0.2-beta.15",
|
|
66
|
+
"@farming-labs/docs": "0.0.2-beta.15"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"astro": ">=4.0.0"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "echo 'Astro components are shipped as source'",
|
|
73
|
+
"typecheck": "echo 'ok'"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
import DocsPage from "./DocsPage.astro";
|
|
3
|
+
|
|
4
|
+
const { data, config = null } = Astro.props;
|
|
5
|
+
|
|
6
|
+
const titleSuffix = config?.metadata?.titleTemplate
|
|
7
|
+
? config.metadata.titleTemplate.replace("%s", "")
|
|
8
|
+
: " – Docs";
|
|
9
|
+
|
|
10
|
+
const tocEnabled = config?.theme?.ui?.layout?.toc?.enabled ?? true;
|
|
11
|
+
|
|
12
|
+
const breadcrumbEnabled = (() => {
|
|
13
|
+
const bc = config?.breadcrumb;
|
|
14
|
+
if (bc === undefined || bc === true) return true;
|
|
15
|
+
if (bc === false) return false;
|
|
16
|
+
if (typeof bc === "object") return bc.enabled !== false;
|
|
17
|
+
return true;
|
|
18
|
+
})();
|
|
19
|
+
|
|
20
|
+
const showEditOnGithub = !!config?.github && !!data.editOnGithub;
|
|
21
|
+
const showLastModified = !!data.lastModified;
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<head>
|
|
25
|
+
<title>{data.title}{titleSuffix}</title>
|
|
26
|
+
{data.description && <meta name="description" content={data.description} />}
|
|
27
|
+
</head>
|
|
28
|
+
|
|
29
|
+
<DocsPage
|
|
30
|
+
entry={config?.entry ?? "docs"}
|
|
31
|
+
tocEnabled={tocEnabled}
|
|
32
|
+
breadcrumbEnabled={breadcrumbEnabled}
|
|
33
|
+
previousPage={data.previousPage}
|
|
34
|
+
nextPage={data.nextPage}
|
|
35
|
+
editOnGithub={showEditOnGithub ? data.editOnGithub : null}
|
|
36
|
+
lastModified={showLastModified ? data.lastModified : null}
|
|
37
|
+
>
|
|
38
|
+
<Fragment set:html={data.html} />
|
|
39
|
+
</DocsPage>
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
---
|
|
2
|
+
import ThemeToggle from "./ThemeToggle.astro";
|
|
3
|
+
|
|
4
|
+
const { tree, config = null, title, titleUrl } = Astro.props;
|
|
5
|
+
|
|
6
|
+
const resolvedTitle = title ?? config?.nav?.title ?? "Docs";
|
|
7
|
+
const resolvedTitleUrl = titleUrl ?? config?.nav?.url ?? "/docs";
|
|
8
|
+
|
|
9
|
+
const showThemeToggle = (() => {
|
|
10
|
+
const toggle = config?.themeToggle;
|
|
11
|
+
if (toggle === undefined || toggle === true) return true;
|
|
12
|
+
if (toggle === false) return false;
|
|
13
|
+
if (typeof toggle === "object") return toggle.enabled !== false;
|
|
14
|
+
return true;
|
|
15
|
+
})();
|
|
16
|
+
|
|
17
|
+
const forcedTheme = (() => {
|
|
18
|
+
const toggle = config?.themeToggle;
|
|
19
|
+
if (typeof toggle === "object" && toggle.enabled === false && toggle.default && toggle.default !== "system") {
|
|
20
|
+
return toggle.default;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
})();
|
|
24
|
+
|
|
25
|
+
const themeInitScript = forcedTheme
|
|
26
|
+
? `document.documentElement.classList.remove('light','dark');document.documentElement.classList.add('${forcedTheme}')`
|
|
27
|
+
: [
|
|
28
|
+
"(function(){",
|
|
29
|
+
"var m=document.cookie.match(/(?:^|;\\s*)theme=(\\w+)/);",
|
|
30
|
+
"var t=m?m[1]:(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light');",
|
|
31
|
+
"document.documentElement.classList.remove('light','dark');",
|
|
32
|
+
"document.documentElement.classList.add(t);",
|
|
33
|
+
"})()",
|
|
34
|
+
].join("");
|
|
35
|
+
|
|
36
|
+
const COLOR_MAP: Record<string, string> = {
|
|
37
|
+
primary: "--color-fd-primary",
|
|
38
|
+
primaryForeground: "--color-fd-primary-foreground",
|
|
39
|
+
background: "--color-fd-background",
|
|
40
|
+
foreground: "--color-fd-foreground",
|
|
41
|
+
muted: "--color-fd-muted",
|
|
42
|
+
mutedForeground: "--color-fd-muted-foreground",
|
|
43
|
+
border: "--color-fd-border",
|
|
44
|
+
card: "--color-fd-card",
|
|
45
|
+
cardForeground: "--color-fd-card-foreground",
|
|
46
|
+
accent: "--color-fd-accent",
|
|
47
|
+
accentForeground: "--color-fd-accent-foreground",
|
|
48
|
+
popover: "--color-fd-popover",
|
|
49
|
+
popoverForeground: "--color-fd-popover-foreground",
|
|
50
|
+
secondary: "--color-fd-secondary",
|
|
51
|
+
secondaryForeground: "--color-fd-secondary-foreground",
|
|
52
|
+
ring: "--color-fd-ring",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function buildColorsCSS(colors?: Record<string, string | undefined>): string {
|
|
56
|
+
if (!colors) return "";
|
|
57
|
+
const vars: string[] = [];
|
|
58
|
+
for (const [key, value] of Object.entries(colors)) {
|
|
59
|
+
if (!value || !COLOR_MAP[key]) continue;
|
|
60
|
+
vars.push(`${COLOR_MAP[key]}: ${value};`);
|
|
61
|
+
}
|
|
62
|
+
if (vars.length === 0) return "";
|
|
63
|
+
return `:root, .dark {\n ${vars.join("\n ")}\n}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildFontStyleVars(prefix: string, style?: { size?: string; weight?: string | number; lineHeight?: string; letterSpacing?: string }): string {
|
|
67
|
+
if (!style) return "";
|
|
68
|
+
const parts: string[] = [];
|
|
69
|
+
if (style.size) parts.push(`${prefix}-size: ${style.size};`);
|
|
70
|
+
if (style.weight != null) parts.push(`${prefix}-weight: ${style.weight};`);
|
|
71
|
+
if (style.lineHeight) parts.push(`${prefix}-line-height: ${style.lineHeight};`);
|
|
72
|
+
if (style.letterSpacing) parts.push(`${prefix}-letter-spacing: ${style.letterSpacing};`);
|
|
73
|
+
return parts.join("\n ");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildTypographyCSS(typo?: any): string {
|
|
77
|
+
if (!typo?.font) return "";
|
|
78
|
+
const vars: string[] = [];
|
|
79
|
+
const fontStyle = typo.font.style;
|
|
80
|
+
if (fontStyle?.sans) vars.push(`--fd-font-sans: ${fontStyle.sans};`);
|
|
81
|
+
if (fontStyle?.mono) vars.push(`--fd-font-mono: ${fontStyle.mono};`);
|
|
82
|
+
const elements = ["h1", "h2", "h3", "h4", "body", "small"] as const;
|
|
83
|
+
for (const el of elements) {
|
|
84
|
+
const elStyle = typo.font[el];
|
|
85
|
+
if (elStyle) {
|
|
86
|
+
const elVars = buildFontStyleVars(`--fd-${el}`, elStyle);
|
|
87
|
+
if (elVars) vars.push(elVars);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (vars.length === 0) return "";
|
|
91
|
+
return `:root {\n ${vars.join("\n ")}\n}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const colorOverrides = config?.theme?._userColorOverrides as Record<string, string | undefined> | undefined;
|
|
95
|
+
const typography = config?.theme?.ui?.typography;
|
|
96
|
+
const overrideCSS = [buildColorsCSS(colorOverrides), buildTypographyCSS(typography)].filter(Boolean).join("\n");
|
|
97
|
+
|
|
98
|
+
const currentPath = Astro.url.pathname;
|
|
99
|
+
|
|
100
|
+
function isActive(url) {
|
|
101
|
+
const normalised = url.replace(/\/$/, '') || '/';
|
|
102
|
+
const currentNorm = currentPath.replace(/\/$/, '') || '/';
|
|
103
|
+
return normalised === currentNorm;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const ICON_MAP = {
|
|
107
|
+
book: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>`,
|
|
108
|
+
terminal: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`,
|
|
109
|
+
rocket: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>`,
|
|
110
|
+
settings: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
|
|
111
|
+
file: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
|
|
112
|
+
code: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function getIcon(iconKey) {
|
|
116
|
+
if (!iconKey) return null;
|
|
117
|
+
return ICON_MAP[iconKey] || null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const aiConfig = config?.ai;
|
|
121
|
+
const showFloatingAI = aiConfig?.mode === "floating" && aiConfig?.enabled;
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
<script is:inline set:html={themeInitScript}></script>
|
|
125
|
+
{overrideCSS && <style set:html={overrideCSS} />}
|
|
126
|
+
|
|
127
|
+
<div class="fd-layout">
|
|
128
|
+
<header class="fd-header">
|
|
129
|
+
<button class="fd-menu-btn" id="fd-menu-toggle" aria-label="Toggle sidebar">
|
|
130
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
131
|
+
<line x1="3" y1="6" x2="21" y2="6" />
|
|
132
|
+
<line x1="3" y1="12" x2="21" y2="12" />
|
|
133
|
+
<line x1="3" y1="18" x2="21" y2="18" />
|
|
134
|
+
</svg>
|
|
135
|
+
</button>
|
|
136
|
+
<a href={resolvedTitleUrl} class="fd-header-title">{resolvedTitle}</a>
|
|
137
|
+
<button class="fd-search-trigger-mobile" id="fd-search-open-mobile" aria-label="Search">
|
|
138
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
139
|
+
<circle cx="11" cy="11" r="8" />
|
|
140
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
141
|
+
</svg>
|
|
142
|
+
</button>
|
|
143
|
+
</header>
|
|
144
|
+
|
|
145
|
+
<div class="fd-sidebar-overlay" id="fd-sidebar-overlay" style="display:none"></div>
|
|
146
|
+
|
|
147
|
+
<aside class="fd-sidebar" id="fd-sidebar">
|
|
148
|
+
<div class="fd-sidebar-header">
|
|
149
|
+
<a href={resolvedTitleUrl} class="fd-sidebar-title">
|
|
150
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
151
|
+
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
|
152
|
+
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
|
153
|
+
</svg>
|
|
154
|
+
{resolvedTitle}
|
|
155
|
+
</a>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div class="fd-sidebar-search">
|
|
159
|
+
<button class="fd-sidebar-search-btn" id="fd-search-open">
|
|
160
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
161
|
+
<circle cx="11" cy="11" r="8" />
|
|
162
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
163
|
+
</svg>
|
|
164
|
+
<span>Search</span>
|
|
165
|
+
<kbd>⌘</kbd><kbd>K</kbd>
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<nav class="fd-sidebar-nav">
|
|
170
|
+
{tree?.children?.map((node, i) => {
|
|
171
|
+
if (node.type === "page") {
|
|
172
|
+
const icon = getIcon(node.icon);
|
|
173
|
+
return (
|
|
174
|
+
<a
|
|
175
|
+
href={node.url}
|
|
176
|
+
class={`fd-sidebar-link fd-sidebar-top-link ${isActive(node.url) ? 'fd-sidebar-link-active' : ''} ${i === 0 ? 'fd-sidebar-first-item' : ''}`}
|
|
177
|
+
data-active={isActive(node.url) || undefined}
|
|
178
|
+
>
|
|
179
|
+
{icon && <span class="fd-sidebar-icon" set:html={icon} />}
|
|
180
|
+
{node.name}
|
|
181
|
+
</a>
|
|
182
|
+
);
|
|
183
|
+
} else if (node.type === "folder") {
|
|
184
|
+
const folderIcon = getIcon(node.icon);
|
|
185
|
+
return (
|
|
186
|
+
<details class={`fd-sidebar-folder ${i === 0 ? 'fd-sidebar-first-item' : ''}`} open>
|
|
187
|
+
<summary class="fd-sidebar-folder-trigger">
|
|
188
|
+
<span class="fd-sidebar-folder-label">
|
|
189
|
+
{folderIcon && <span class="fd-sidebar-icon" set:html={folderIcon} />}
|
|
190
|
+
{node.name}
|
|
191
|
+
</span>
|
|
192
|
+
<svg class="fd-sidebar-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
193
|
+
<polyline points="6 9 12 15 18 9" />
|
|
194
|
+
</svg>
|
|
195
|
+
</summary>
|
|
196
|
+
<div class="fd-sidebar-folder-content">
|
|
197
|
+
{node.index && (
|
|
198
|
+
<a
|
|
199
|
+
href={node.index.url}
|
|
200
|
+
class={`fd-sidebar-link fd-sidebar-child-link ${isActive(node.index.url) ? 'fd-sidebar-link-active' : ''}`}
|
|
201
|
+
data-active={isActive(node.index.url) || undefined}
|
|
202
|
+
>
|
|
203
|
+
{node.index.name}
|
|
204
|
+
</a>
|
|
205
|
+
)}
|
|
206
|
+
{node.children?.map(child => {
|
|
207
|
+
if (child.type === "page") {
|
|
208
|
+
return (
|
|
209
|
+
<a
|
|
210
|
+
href={child.url}
|
|
211
|
+
class={`fd-sidebar-link fd-sidebar-child-link ${isActive(child.url) ? 'fd-sidebar-link-active' : ''}`}
|
|
212
|
+
data-active={isActive(child.url) || undefined}
|
|
213
|
+
>
|
|
214
|
+
{child.name}
|
|
215
|
+
</a>
|
|
216
|
+
);
|
|
217
|
+
} else if (child.type === "folder") {
|
|
218
|
+
return (
|
|
219
|
+
<details class="fd-sidebar-folder fd-sidebar-nested-folder" open>
|
|
220
|
+
<summary class="fd-sidebar-folder-trigger">
|
|
221
|
+
<span class="fd-sidebar-folder-label">{child.name}</span>
|
|
222
|
+
<svg class="fd-sidebar-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
223
|
+
<polyline points="6 9 12 15 18 9" />
|
|
224
|
+
</svg>
|
|
225
|
+
</summary>
|
|
226
|
+
<div class="fd-sidebar-folder-content">
|
|
227
|
+
{child.index && (
|
|
228
|
+
<a
|
|
229
|
+
href={child.index.url}
|
|
230
|
+
class={`fd-sidebar-link fd-sidebar-child-link ${isActive(child.index.url) ? 'fd-sidebar-link-active' : ''}`}
|
|
231
|
+
data-active={isActive(child.index.url) || undefined}
|
|
232
|
+
>
|
|
233
|
+
{child.index.name}
|
|
234
|
+
</a>
|
|
235
|
+
)}
|
|
236
|
+
{child.children?.map(grandchild => {
|
|
237
|
+
if (grandchild.type === "page") {
|
|
238
|
+
return (
|
|
239
|
+
<a
|
|
240
|
+
href={grandchild.url}
|
|
241
|
+
class={`fd-sidebar-link fd-sidebar-child-link ${isActive(grandchild.url) ? 'fd-sidebar-link-active' : ''}`}
|
|
242
|
+
data-active={isActive(grandchild.url) || undefined}
|
|
243
|
+
>
|
|
244
|
+
{grandchild.name}
|
|
245
|
+
</a>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
})}
|
|
249
|
+
</div>
|
|
250
|
+
</details>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
})}
|
|
254
|
+
</div>
|
|
255
|
+
</details>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
})}
|
|
259
|
+
</nav>
|
|
260
|
+
|
|
261
|
+
{showThemeToggle && (
|
|
262
|
+
<div class="fd-sidebar-footer">
|
|
263
|
+
<ThemeToggle />
|
|
264
|
+
</div>
|
|
265
|
+
)}
|
|
266
|
+
</aside>
|
|
267
|
+
|
|
268
|
+
<main class="fd-main">
|
|
269
|
+
<slot />
|
|
270
|
+
</main>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<script>
|
|
274
|
+
// Sidebar toggle
|
|
275
|
+
const menuBtn = document.getElementById('fd-menu-toggle');
|
|
276
|
+
const sidebar = document.getElementById('fd-sidebar');
|
|
277
|
+
const overlay = document.getElementById('fd-sidebar-overlay');
|
|
278
|
+
|
|
279
|
+
menuBtn?.addEventListener('click', () => {
|
|
280
|
+
sidebar?.classList.toggle('fd-sidebar-open');
|
|
281
|
+
overlay!.style.display = sidebar?.classList.contains('fd-sidebar-open') ? 'block' : 'none';
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
overlay?.addEventListener('click', () => {
|
|
285
|
+
sidebar?.classList.remove('fd-sidebar-open');
|
|
286
|
+
overlay!.style.display = 'none';
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Search Cmd+K
|
|
290
|
+
document.addEventListener('keydown', (e) => {
|
|
291
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
const dialog = document.getElementById('fd-search-dialog');
|
|
294
|
+
if (dialog) dialog.style.display = dialog.style.display === 'none' ? 'flex' : 'none';
|
|
295
|
+
}
|
|
296
|
+
if (e.key === 'Escape') {
|
|
297
|
+
sidebar?.classList.remove('fd-sidebar-open');
|
|
298
|
+
overlay!.style.display = 'none';
|
|
299
|
+
const dialog = document.getElementById('fd-search-dialog');
|
|
300
|
+
if (dialog) dialog.style.display = 'none';
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
document.getElementById('fd-search-open')?.addEventListener('click', () => {
|
|
305
|
+
const dialog = document.getElementById('fd-search-dialog');
|
|
306
|
+
if (dialog) dialog.style.display = 'flex';
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
document.getElementById('fd-search-open-mobile')?.addEventListener('click', () => {
|
|
310
|
+
const dialog = document.getElementById('fd-search-dialog');
|
|
311
|
+
if (dialog) dialog.style.display = 'flex';
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Close sidebar on link click (mobile)
|
|
315
|
+
sidebar?.querySelectorAll('a').forEach(link => {
|
|
316
|
+
link.addEventListener('click', () => {
|
|
317
|
+
sidebar?.classList.remove('fd-sidebar-open');
|
|
318
|
+
overlay!.style.display = 'none';
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
</script>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
const {
|
|
3
|
+
tocEnabled = true,
|
|
4
|
+
breadcrumbEnabled = true,
|
|
5
|
+
entry = "docs",
|
|
6
|
+
previousPage = null,
|
|
7
|
+
nextPage = null,
|
|
8
|
+
editOnGithub = null,
|
|
9
|
+
lastModified = null,
|
|
10
|
+
} = Astro.props;
|
|
11
|
+
|
|
12
|
+
const pathname = Astro.url.pathname;
|
|
13
|
+
const segments = pathname.split("/").filter(Boolean).filter(s => s.toLowerCase() !== entry.toLowerCase());
|
|
14
|
+
const parentLabel = segments.length >= 2 ? segments[segments.length - 2].replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase()) : "";
|
|
15
|
+
const currentLabel = segments.length >= 2 ? segments[segments.length - 1].replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase()) : "";
|
|
16
|
+
const parentUrl = (() => {
|
|
17
|
+
if (segments.length < 2) return "";
|
|
18
|
+
const all = pathname.split("/").filter(Boolean);
|
|
19
|
+
const parentSegment = segments[segments.length - 2];
|
|
20
|
+
const parentIndex = all.indexOf(parentSegment);
|
|
21
|
+
return "/" + all.slice(0, parentIndex + 1).join("/");
|
|
22
|
+
})();
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<div class="fd-page">
|
|
26
|
+
<article class="fd-page-article" id="nd-page">
|
|
27
|
+
{breadcrumbEnabled && segments.length >= 2 && (
|
|
28
|
+
<nav class="fd-breadcrumb" aria-label="Breadcrumb">
|
|
29
|
+
<span class="fd-breadcrumb-item">
|
|
30
|
+
<a href={parentUrl} class="fd-breadcrumb-parent fd-breadcrumb-link">{parentLabel}</a>
|
|
31
|
+
</span>
|
|
32
|
+
<span class="fd-breadcrumb-item">
|
|
33
|
+
<span class="fd-breadcrumb-sep">/</span>
|
|
34
|
+
<span class="fd-breadcrumb-current">{currentLabel}</span>
|
|
35
|
+
</span>
|
|
36
|
+
</nav>
|
|
37
|
+
)}
|
|
38
|
+
|
|
39
|
+
<div class="fd-page-body">
|
|
40
|
+
<slot />
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<footer class="fd-page-footer">
|
|
44
|
+
{(editOnGithub || lastModified) && (
|
|
45
|
+
<div class="fd-edit-on-github">
|
|
46
|
+
{editOnGithub && (
|
|
47
|
+
<a href={editOnGithub} target="_blank" rel="noopener noreferrer">
|
|
48
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
49
|
+
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7" />
|
|
50
|
+
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
51
|
+
</svg>
|
|
52
|
+
Edit on GitHub
|
|
53
|
+
</a>
|
|
54
|
+
)}
|
|
55
|
+
{lastModified && (
|
|
56
|
+
<span class="fd-last-modified">Last updated: {lastModified}</span>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{(previousPage || nextPage) && (
|
|
62
|
+
<nav class="fd-page-nav" aria-label="Page navigation">
|
|
63
|
+
{previousPage ? (
|
|
64
|
+
<a href={previousPage.url} class="fd-page-nav-card fd-page-nav-prev">
|
|
65
|
+
<span class="fd-page-nav-label">
|
|
66
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
67
|
+
<polyline points="15 18 9 12 15 6" />
|
|
68
|
+
</svg>
|
|
69
|
+
Previous
|
|
70
|
+
</span>
|
|
71
|
+
<span class="fd-page-nav-title">{previousPage.name}</span>
|
|
72
|
+
</a>
|
|
73
|
+
) : <div />}
|
|
74
|
+
{nextPage ? (
|
|
75
|
+
<a href={nextPage.url} class="fd-page-nav-card fd-page-nav-next">
|
|
76
|
+
<span class="fd-page-nav-label">
|
|
77
|
+
Next
|
|
78
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
79
|
+
<polyline points="9 18 15 12 9 6" />
|
|
80
|
+
</svg>
|
|
81
|
+
</span>
|
|
82
|
+
<span class="fd-page-nav-title">{nextPage.name}</span>
|
|
83
|
+
</a>
|
|
84
|
+
) : <div />}
|
|
85
|
+
</nav>
|
|
86
|
+
)}
|
|
87
|
+
</footer>
|
|
88
|
+
</article>
|
|
89
|
+
|
|
90
|
+
{tocEnabled && (
|
|
91
|
+
<aside class="fd-toc">
|
|
92
|
+
<div class="fd-toc-inner">
|
|
93
|
+
<h3 class="fd-toc-title">
|
|
94
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
95
|
+
<line x1="3" y1="6" x2="21" y2="6" />
|
|
96
|
+
<line x1="3" y1="12" x2="15" y2="12" />
|
|
97
|
+
<line x1="3" y1="18" x2="18" y2="18" />
|
|
98
|
+
</svg>
|
|
99
|
+
On this page
|
|
100
|
+
</h3>
|
|
101
|
+
<ul class="fd-toc-list" id="fd-toc-list"></ul>
|
|
102
|
+
</div>
|
|
103
|
+
</aside>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<script>
|
|
108
|
+
function initDocsPage() {
|
|
109
|
+
// Scan headings for TOC
|
|
110
|
+
const container = document.querySelector('.fd-page-body');
|
|
111
|
+
const tocList = document.getElementById('fd-toc-list');
|
|
112
|
+
if (container && tocList) {
|
|
113
|
+
const headings = container.querySelectorAll('h2[id], h3[id], h4[id]');
|
|
114
|
+
tocList.innerHTML = '';
|
|
115
|
+
headings.forEach(el => {
|
|
116
|
+
const depth = parseInt(el.tagName[1], 10);
|
|
117
|
+
const li = document.createElement('li');
|
|
118
|
+
li.className = 'fd-toc-item';
|
|
119
|
+
const a = document.createElement('a');
|
|
120
|
+
a.className = 'fd-toc-link';
|
|
121
|
+
a.href = `#${el.id}`;
|
|
122
|
+
a.textContent = el.textContent?.replace(/^#\s*/, '') || '';
|
|
123
|
+
a.style.paddingLeft = `${12 + (depth - 2) * 12}px`;
|
|
124
|
+
li.appendChild(a);
|
|
125
|
+
tocList.appendChild(li);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Intersection observer for active heading
|
|
129
|
+
const observer = new IntersectionObserver((entries) => {
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
if (entry.isIntersecting) {
|
|
132
|
+
tocList.querySelectorAll('.fd-toc-link').forEach(link => {
|
|
133
|
+
link.classList.toggle('fd-toc-link-active', link.getAttribute('href') === `#${entry.target.id}`);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}, { rootMargin: '-80px 0px -80% 0px' });
|
|
138
|
+
|
|
139
|
+
headings.forEach(el => observer.observe(el));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Copy buttons
|
|
143
|
+
document.querySelectorAll('.fd-copy-btn').forEach(btn => {
|
|
144
|
+
btn.addEventListener('click', () => {
|
|
145
|
+
const code = btn.getAttribute('data-code')
|
|
146
|
+
?.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
147
|
+
if (!code) return;
|
|
148
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
149
|
+
btn.classList.add('fd-copy-btn-copied');
|
|
150
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
151
|
+
setTimeout(() => {
|
|
152
|
+
btn.classList.remove('fd-copy-btn-copied');
|
|
153
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
|
|
154
|
+
}, 2000);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Tabs
|
|
160
|
+
document.querySelectorAll('[data-tabs]').forEach(tabs => {
|
|
161
|
+
tabs.querySelectorAll('.fd-tab-trigger').forEach(trigger => {
|
|
162
|
+
trigger.addEventListener('click', () => {
|
|
163
|
+
const val = trigger.getAttribute('data-tab-value');
|
|
164
|
+
tabs.querySelectorAll('.fd-tab-trigger').forEach(t => {
|
|
165
|
+
t.classList.toggle('fd-tab-active', t.getAttribute('data-tab-value') === val);
|
|
166
|
+
t.setAttribute('aria-selected', String(t.getAttribute('data-tab-value') === val));
|
|
167
|
+
});
|
|
168
|
+
tabs.querySelectorAll('.fd-tab-panel').forEach(p => {
|
|
169
|
+
p.classList.toggle('fd-tab-panel-active', p.getAttribute('data-tab-panel') === val);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
initDocsPage();
|
|
177
|
+
document.addEventListener('astro:after-swap', initDocsPage);
|
|
178
|
+
</script>
|