@openlaboratory/open-doc 0.1.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/README.md +91 -0
- package/app/.astro/collections/docs.schema.json +24 -0
- package/app/.astro/content-assets.mjs +1 -0
- package/app/.astro/content-modules.mjs +4 -0
- package/app/.astro/content.d.ts +218 -0
- package/app/.astro/data-store.json +1 -0
- package/app/.astro/settings.json +5 -0
- package/app/.astro/types.d.ts +2 -0
- package/app/astro.config.mjs +43 -0
- package/app/node_modules/.astro/data-store.json +1 -0
- package/app/node_modules/.vite/deps/@astrojs_react_client__js.js +163 -0
- package/app/node_modules/.vite/deps/@astrojs_react_client__js.js.map +7 -0
- package/app/node_modules/.vite/deps/_metadata.json +67 -0
- package/app/node_modules/.vite/deps/astro___aria-query.js +6776 -0
- package/app/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
- package/app/node_modules/.vite/deps/astro___axobject-query.js +3754 -0
- package/app/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
- package/app/node_modules/.vite/deps/astro___cssesc.js +99 -0
- package/app/node_modules/.vite/deps/astro___cssesc.js.map +7 -0
- package/app/node_modules/.vite/deps/chunk-55ZOATU5.js +305 -0
- package/app/node_modules/.vite/deps/chunk-55ZOATU5.js.map +7 -0
- package/app/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
- package/app/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
- package/app/node_modules/.vite/deps/chunk-FEZZJEG2.js +6935 -0
- package/app/node_modules/.vite/deps/chunk-FEZZJEG2.js.map +7 -0
- package/app/node_modules/.vite/deps/package.json +3 -0
- package/app/node_modules/.vite/deps/react-dom.js +6 -0
- package/app/node_modules/.vite/deps/react-dom.js.map +7 -0
- package/app/node_modules/.vite/deps/react.js +5 -0
- package/app/node_modules/.vite/deps/react.js.map +7 -0
- package/app/node_modules/.vite/deps/react_jsx-dev-runtime.js +39 -0
- package/app/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/app/node_modules/.vite/deps/react_jsx-runtime.js +57 -0
- package/app/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
- package/app/src/components/DocsMobileNav.tsx +124 -0
- package/app/src/components/DocsSearch.tsx +315 -0
- package/app/src/components/DocsSidebar.astro +46 -0
- package/app/src/components/DocsTableOfContents.tsx +92 -0
- package/app/src/components/Navbar.astro +39 -0
- package/app/src/components/SocialIcon.astro +54 -0
- package/app/src/components/ThemeToggle.tsx +62 -0
- package/app/src/content.config.ts +17 -0
- package/app/src/env.d.ts +7 -0
- package/app/src/integrations/open-doc-config.mjs +65 -0
- package/app/src/layouts/DocsLayout.astro +369 -0
- package/app/src/lib/config.ts +36 -0
- package/app/src/lib/navigation.ts +68 -0
- package/app/src/lib/withBase.ts +11 -0
- package/app/src/pages/404.astro +24 -0
- package/app/src/pages/[...slug].astro +34 -0
- package/app/src/pages/index.astro +107 -0
- package/app/src/styles/global.css +324 -0
- package/app/tailwind.config.mjs +53 -0
- package/app/tsconfig.json +11 -0
- package/bin/open-doc.js +2 -0
- package/dist/chunk-BRUM67K7.js +30 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +268 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +8 -0
- package/package.json +77 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from 'astro:content'
|
|
3
|
+
import config from 'virtual:open-doc-config'
|
|
4
|
+
import Navbar from '../components/Navbar.astro'
|
|
5
|
+
import DocsSidebar from '../components/DocsSidebar.astro'
|
|
6
|
+
import { DocsTableOfContents } from '../components/DocsTableOfContents'
|
|
7
|
+
import { DocsSearch } from '../components/DocsSearch'
|
|
8
|
+
import { DocsMobileNav } from '../components/DocsMobileNav'
|
|
9
|
+
import { buildNavigation, getPrevNext, getSectionForSlug, type DocSection } from '../lib/navigation'
|
|
10
|
+
import { withBase } from '../lib/withBase'
|
|
11
|
+
import '../styles/global.css'
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
title: string
|
|
15
|
+
description?: string
|
|
16
|
+
currentSlug: string
|
|
17
|
+
headings?: { depth: number; slug: string; text: string }[]
|
|
18
|
+
showPrevNext?: boolean
|
|
19
|
+
editUrl?: string
|
|
20
|
+
navigation?: DocSection[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
title,
|
|
25
|
+
description = config.description,
|
|
26
|
+
currentSlug,
|
|
27
|
+
headings = [],
|
|
28
|
+
showPrevNext = true,
|
|
29
|
+
editUrl,
|
|
30
|
+
navigation: navProp,
|
|
31
|
+
} = Astro.props
|
|
32
|
+
|
|
33
|
+
const navigation: DocSection[] = navProp ?? buildNavigation(await getCollection('docs'), config.nav)
|
|
34
|
+
|
|
35
|
+
const { prev, next } =
|
|
36
|
+
showPrevNext && currentSlug ? getPrevNext(navigation, currentSlug) : { prev: null, next: null }
|
|
37
|
+
const section = currentSlug ? getSectionForSlug(navigation, currentSlug) : null
|
|
38
|
+
|
|
39
|
+
const pageTitle = title === config.title ? title : `${title} — ${config.title}`
|
|
40
|
+
|
|
41
|
+
// SEO: canonical + OpenGraph URLs require a configured `site`.
|
|
42
|
+
const canonical = Astro.site ? new URL(Astro.url.pathname, Astro.site).href : undefined
|
|
43
|
+
|
|
44
|
+
// Favicon: a configured path, otherwise an inlined default (works regardless of
|
|
45
|
+
// whether the consumer ships a public directory).
|
|
46
|
+
const defaultFaviconSvg =
|
|
47
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>'
|
|
48
|
+
const favicon = config.favicon
|
|
49
|
+
? withBase(config.favicon)
|
|
50
|
+
: `data:image/svg+xml,${encodeURIComponent(defaultFaviconSvg)}`
|
|
51
|
+
|
|
52
|
+
// Optional per-mode color overrides from the user config.
|
|
53
|
+
const toVars = (obj?: Record<string, string>) =>
|
|
54
|
+
Object.entries(obj ?? {})
|
|
55
|
+
.map(([k, v]) => `--${k}: ${v};`)
|
|
56
|
+
.join(' ')
|
|
57
|
+
const lightVars = toVars(config.theme.light)
|
|
58
|
+
const darkVars = toVars(config.theme.dark)
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
<!doctype html>
|
|
62
|
+
<html lang={config.lang}>
|
|
63
|
+
<head>
|
|
64
|
+
<meta charset="UTF-8" />
|
|
65
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
66
|
+
<meta name="generator" content="open-doc" />
|
|
67
|
+
{description && <meta name="description" content={description} />}
|
|
68
|
+
<title>{pageTitle}</title>
|
|
69
|
+
{canonical && <link rel="canonical" href={canonical} />}
|
|
70
|
+
|
|
71
|
+
<!-- OpenGraph / Twitter -->
|
|
72
|
+
<meta property="og:type" content="website" />
|
|
73
|
+
<meta property="og:site_name" content={config.title} />
|
|
74
|
+
<meta property="og:title" content={pageTitle} />
|
|
75
|
+
{description && <meta property="og:description" content={description} />}
|
|
76
|
+
{canonical && <meta property="og:url" content={canonical} />}
|
|
77
|
+
<meta name="twitter:card" content="summary" />
|
|
78
|
+
<meta name="twitter:title" content={pageTitle} />
|
|
79
|
+
{description && <meta name="twitter:description" content={description} />}
|
|
80
|
+
|
|
81
|
+
<meta name="theme-color" content="#0a0a0a" media="(prefers-color-scheme: dark)" />
|
|
82
|
+
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
|
|
83
|
+
|
|
84
|
+
<link rel="icon" type="image/svg+xml" href={favicon} />
|
|
85
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
86
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
87
|
+
<link
|
|
88
|
+
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&family=Red+Hat+Mono:wght@400;500&display=swap"
|
|
89
|
+
rel="stylesheet"
|
|
90
|
+
/>
|
|
91
|
+
{(lightVars || darkVars) && <style set:html={`:root{${lightVars}} .dark{${darkVars}}`} />}
|
|
92
|
+
<script is:inline define:vars={{ defaultTheme: config.defaultTheme }}>
|
|
93
|
+
;(function () {
|
|
94
|
+
try {
|
|
95
|
+
var stored = localStorage.getItem('open-doc-theme')
|
|
96
|
+
var theme = stored
|
|
97
|
+
if (!theme) {
|
|
98
|
+
theme =
|
|
99
|
+
defaultTheme === 'auto'
|
|
100
|
+
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
101
|
+
? 'dark'
|
|
102
|
+
: 'light'
|
|
103
|
+
: defaultTheme
|
|
104
|
+
}
|
|
105
|
+
document.documentElement.classList.toggle('dark', theme === 'dark')
|
|
106
|
+
if (defaultTheme === 'auto') {
|
|
107
|
+
window
|
|
108
|
+
.matchMedia('(prefers-color-scheme: dark)')
|
|
109
|
+
.addEventListener('change', function (e) {
|
|
110
|
+
if (!localStorage.getItem('open-doc-theme')) {
|
|
111
|
+
document.documentElement.classList.toggle('dark', e.matches)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
116
|
+
document.documentElement.classList.add('dark')
|
|
117
|
+
}
|
|
118
|
+
})()
|
|
119
|
+
</script>
|
|
120
|
+
</head>
|
|
121
|
+
<body class="font-sans">
|
|
122
|
+
<Navbar />
|
|
123
|
+
|
|
124
|
+
<div class="flex min-h-[calc(100vh-57px)]">
|
|
125
|
+
<!-- Left sidebar (desktop) -->
|
|
126
|
+
<aside
|
|
127
|
+
class="sticky top-[57px] hidden h-[calc(100vh-57px)] w-60 shrink-0 flex-col overflow-y-auto border-r border-foreground/[0.08] bg-surface-sidebar hide-scrollbar lg:flex xl:w-64"
|
|
128
|
+
aria-label="Documentation sidebar"
|
|
129
|
+
>
|
|
130
|
+
<div class="border-b border-foreground/[0.08] px-4 py-3">
|
|
131
|
+
<DocsSearch navigation={navigation} client:load />
|
|
132
|
+
</div>
|
|
133
|
+
<div class="flex-1 overflow-y-auto hide-scrollbar">
|
|
134
|
+
<DocsSidebar navigation={navigation} currentSlug={currentSlug} />
|
|
135
|
+
</div>
|
|
136
|
+
</aside>
|
|
137
|
+
|
|
138
|
+
<!-- Main content -->
|
|
139
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
140
|
+
<DocsMobileNav navigation={navigation} currentSlug={currentSlug} client:load />
|
|
141
|
+
|
|
142
|
+
<!-- Mobile search (the sidebar search is desktop-only) -->
|
|
143
|
+
<div class="border-b border-foreground/[0.08] bg-surface-sidebar px-4 py-2.5 lg:hidden">
|
|
144
|
+
<DocsSearch navigation={navigation} client:load />
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div class="flex min-w-0 flex-1">
|
|
148
|
+
<main class="min-w-0 flex-1 bg-surface-content px-6 py-10 sm:px-10 lg:py-12">
|
|
149
|
+
<div class="mx-auto max-w-2xl xl:max-w-3xl">
|
|
150
|
+
{
|
|
151
|
+
section && (
|
|
152
|
+
<nav
|
|
153
|
+
class="mb-6 flex items-center gap-2 text-sm text-foreground/40"
|
|
154
|
+
aria-label="Breadcrumb"
|
|
155
|
+
>
|
|
156
|
+
<a href={withBase('/')} class="transition-colors hover:text-foreground/60">
|
|
157
|
+
Docs
|
|
158
|
+
</a>
|
|
159
|
+
<svg
|
|
160
|
+
class="h-3 w-3"
|
|
161
|
+
fill="none"
|
|
162
|
+
viewBox="0 0 24 24"
|
|
163
|
+
stroke="currentColor"
|
|
164
|
+
stroke-width="2"
|
|
165
|
+
>
|
|
166
|
+
<path
|
|
167
|
+
stroke-linecap="round"
|
|
168
|
+
stroke-linejoin="round"
|
|
169
|
+
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
170
|
+
/>
|
|
171
|
+
</svg>
|
|
172
|
+
<span class="text-foreground/55">{section.label}</span>
|
|
173
|
+
<svg
|
|
174
|
+
class="h-3 w-3"
|
|
175
|
+
fill="none"
|
|
176
|
+
viewBox="0 0 24 24"
|
|
177
|
+
stroke="currentColor"
|
|
178
|
+
stroke-width="2"
|
|
179
|
+
>
|
|
180
|
+
<path
|
|
181
|
+
stroke-linecap="round"
|
|
182
|
+
stroke-linejoin="round"
|
|
183
|
+
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
184
|
+
/>
|
|
185
|
+
</svg>
|
|
186
|
+
<span class="text-foreground/80">{title}</span>
|
|
187
|
+
</nav>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
<slot />
|
|
192
|
+
|
|
193
|
+
{
|
|
194
|
+
editUrl && (
|
|
195
|
+
<div class="mt-10 text-sm">
|
|
196
|
+
<a
|
|
197
|
+
href={editUrl}
|
|
198
|
+
target="_blank"
|
|
199
|
+
rel="noopener noreferrer"
|
|
200
|
+
class="inline-flex items-center gap-1.5 text-foreground/45 transition-colors hover:text-foreground/75"
|
|
201
|
+
>
|
|
202
|
+
<svg
|
|
203
|
+
class="h-3.5 w-3.5"
|
|
204
|
+
fill="none"
|
|
205
|
+
viewBox="0 0 24 24"
|
|
206
|
+
stroke="currentColor"
|
|
207
|
+
stroke-width="2"
|
|
208
|
+
>
|
|
209
|
+
<path
|
|
210
|
+
stroke-linecap="round"
|
|
211
|
+
stroke-linejoin="round"
|
|
212
|
+
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z"
|
|
213
|
+
/>
|
|
214
|
+
</svg>
|
|
215
|
+
Edit this page
|
|
216
|
+
</a>
|
|
217
|
+
</div>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
{
|
|
222
|
+
showPrevNext && (prev || next) && (
|
|
223
|
+
<nav
|
|
224
|
+
class="mt-12 flex flex-col gap-3 border-t border-foreground/[0.1] pt-8 sm:flex-row sm:justify-between"
|
|
225
|
+
aria-label="Previous and next pages"
|
|
226
|
+
>
|
|
227
|
+
{prev ? (
|
|
228
|
+
<a
|
|
229
|
+
href={withBase(prev.slug)}
|
|
230
|
+
class="group flex min-w-0 items-center gap-3 rounded-lg border border-foreground/[0.08] bg-foreground/[0.02] px-4 py-3 transition-all hover:border-foreground/[0.16] hover:bg-foreground/[0.05] sm:max-w-[45%]"
|
|
231
|
+
>
|
|
232
|
+
<svg
|
|
233
|
+
class="h-4 w-4 shrink-0 text-foreground/35 transition-colors group-hover:text-foreground/60"
|
|
234
|
+
fill="none"
|
|
235
|
+
viewBox="0 0 24 24"
|
|
236
|
+
stroke="currentColor"
|
|
237
|
+
stroke-width="2"
|
|
238
|
+
>
|
|
239
|
+
<path
|
|
240
|
+
stroke-linecap="round"
|
|
241
|
+
stroke-linejoin="round"
|
|
242
|
+
d="M15.75 19.5L8.25 12l7.5-7.5"
|
|
243
|
+
/>
|
|
244
|
+
</svg>
|
|
245
|
+
<div class="min-w-0">
|
|
246
|
+
<div class="text-[11px] text-foreground/30">Previous</div>
|
|
247
|
+
<div class="truncate text-sm font-medium text-foreground/75 transition-colors group-hover:text-foreground/90">
|
|
248
|
+
{prev.title}
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</a>
|
|
252
|
+
) : (
|
|
253
|
+
<div />
|
|
254
|
+
)}
|
|
255
|
+
|
|
256
|
+
{next ? (
|
|
257
|
+
<a
|
|
258
|
+
href={withBase(next.slug)}
|
|
259
|
+
class="group flex min-w-0 items-center justify-between gap-3 rounded-lg border border-foreground/[0.08] bg-foreground/[0.02] px-4 py-3 transition-all hover:border-foreground/[0.16] hover:bg-foreground/[0.05] sm:ml-auto sm:max-w-[45%]"
|
|
260
|
+
>
|
|
261
|
+
<div class="min-w-0 text-right">
|
|
262
|
+
<div class="text-[11px] text-foreground/30">Next</div>
|
|
263
|
+
<div class="truncate text-sm font-medium text-foreground/75 transition-colors group-hover:text-foreground/90">
|
|
264
|
+
{next.title}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<svg
|
|
268
|
+
class="h-4 w-4 shrink-0 text-foreground/35 transition-colors group-hover:text-foreground/60"
|
|
269
|
+
fill="none"
|
|
270
|
+
viewBox="0 0 24 24"
|
|
271
|
+
stroke="currentColor"
|
|
272
|
+
stroke-width="2"
|
|
273
|
+
>
|
|
274
|
+
<path
|
|
275
|
+
stroke-linecap="round"
|
|
276
|
+
stroke-linejoin="round"
|
|
277
|
+
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
278
|
+
/>
|
|
279
|
+
</svg>
|
|
280
|
+
</a>
|
|
281
|
+
) : (
|
|
282
|
+
<div />
|
|
283
|
+
)}
|
|
284
|
+
</nav>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
</div>
|
|
288
|
+
</main>
|
|
289
|
+
|
|
290
|
+
<!-- Right table of contents -->
|
|
291
|
+
<aside
|
|
292
|
+
class="sticky top-[57px] hidden h-[calc(100vh-57px)] w-56 shrink-0 overflow-y-auto border-l border-foreground/[0.08] bg-surface-content px-5 py-8 xl:block"
|
|
293
|
+
aria-label="Table of contents"
|
|
294
|
+
>
|
|
295
|
+
<DocsTableOfContents headings={headings} client:load />
|
|
296
|
+
</aside>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<script>
|
|
302
|
+
const prefersReducedMotion = () =>
|
|
303
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
304
|
+
|
|
305
|
+
function scrollActiveSidebarIntoView() {
|
|
306
|
+
const active = document.querySelector(
|
|
307
|
+
'aside[aria-label="Documentation sidebar"] a[aria-current="page"]',
|
|
308
|
+
)
|
|
309
|
+
if (active) active.scrollIntoView({ block: 'nearest' })
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function addHeadingAnchors() {
|
|
313
|
+
const prose = document.querySelector('.docs-prose')
|
|
314
|
+
if (!prose) return
|
|
315
|
+
prose.querySelectorAll('h1[id], h2[id], h3[id], h4[id]').forEach((heading) => {
|
|
316
|
+
if (heading.querySelector('.heading-anchor')) return
|
|
317
|
+
const anchor = document.createElement('a')
|
|
318
|
+
anchor.href = `#${heading.id}`
|
|
319
|
+
anchor.className = 'heading-anchor'
|
|
320
|
+
anchor.textContent = '#'
|
|
321
|
+
anchor.setAttribute('aria-hidden', 'true')
|
|
322
|
+
anchor.addEventListener('click', (e) => {
|
|
323
|
+
e.preventDefault()
|
|
324
|
+
history.pushState(null, '', `#${heading.id}`)
|
|
325
|
+
heading.scrollIntoView({ behavior: prefersReducedMotion() ? 'auto' : 'smooth' })
|
|
326
|
+
})
|
|
327
|
+
heading.appendChild(anchor)
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const clipboardIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>`
|
|
332
|
+
const checkIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>`
|
|
333
|
+
|
|
334
|
+
function addCopyButtons() {
|
|
335
|
+
const prose = document.querySelector('.docs-prose')
|
|
336
|
+
if (!prose) return
|
|
337
|
+
prose.querySelectorAll('pre').forEach((pre) => {
|
|
338
|
+
if (pre.querySelector('.code-copy-btn')) return
|
|
339
|
+
const btn = document.createElement('button')
|
|
340
|
+
btn.className = 'code-copy-btn'
|
|
341
|
+
btn.setAttribute('aria-label', 'Copy code')
|
|
342
|
+
btn.innerHTML = clipboardIcon
|
|
343
|
+
btn.addEventListener('click', () => {
|
|
344
|
+
const code = pre.querySelector('code')
|
|
345
|
+
const text = code ? code.innerText : pre.innerText
|
|
346
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
347
|
+
btn.innerHTML = checkIcon
|
|
348
|
+
btn.classList.add('copied')
|
|
349
|
+
setTimeout(() => {
|
|
350
|
+
btn.innerHTML = clipboardIcon
|
|
351
|
+
btn.classList.remove('copied')
|
|
352
|
+
}, 2000)
|
|
353
|
+
})
|
|
354
|
+
})
|
|
355
|
+
pre.appendChild(btn)
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function enhance() {
|
|
360
|
+
addHeadingAnchors()
|
|
361
|
+
addCopyButtons()
|
|
362
|
+
scrollActiveSidebarIntoView()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
enhance()
|
|
366
|
+
document.addEventListener('astro:page-load', enhance)
|
|
367
|
+
</script>
|
|
368
|
+
</body>
|
|
369
|
+
</html>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Resolved config shape consumed by the app. This mirrors `ResolvedConfig` from
|
|
2
|
+
// the `open-doc` package (the CLI produces it via `normalizeConfig`). Kept as a
|
|
3
|
+
// local copy so the Astro app stays self-contained and needs no cross-package
|
|
4
|
+
// type import.
|
|
5
|
+
|
|
6
|
+
export type ThemeMode = 'dark' | 'light' | 'auto'
|
|
7
|
+
|
|
8
|
+
export interface SocialLink {
|
|
9
|
+
icon?: string
|
|
10
|
+
label?: string
|
|
11
|
+
href: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface NavSection {
|
|
15
|
+
label: string
|
|
16
|
+
dir?: string
|
|
17
|
+
pages: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ThemeColors = Record<string, string>
|
|
21
|
+
|
|
22
|
+
export interface ResolvedConfig {
|
|
23
|
+
title: string
|
|
24
|
+
description: string
|
|
25
|
+
base: string
|
|
26
|
+
site?: string
|
|
27
|
+
lang: string
|
|
28
|
+
favicon?: string
|
|
29
|
+
logo: { text: string; src?: string; href: string }
|
|
30
|
+
social: SocialLink[]
|
|
31
|
+
defaultTheme: ThemeMode
|
|
32
|
+
themeToggle: boolean
|
|
33
|
+
theme: { light?: ThemeColors; dark?: ThemeColors }
|
|
34
|
+
editLinkBase?: string
|
|
35
|
+
nav: NavSection[]
|
|
36
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { NavSection } from './config'
|
|
2
|
+
|
|
3
|
+
export interface DocPage {
|
|
4
|
+
slug: string
|
|
5
|
+
title: string
|
|
6
|
+
description?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface DocSection {
|
|
10
|
+
label: string
|
|
11
|
+
pages: DocPage[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface EntryLike {
|
|
15
|
+
id: string
|
|
16
|
+
data: { title: string; description?: string }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toSlug(section: NavSection, page: string): string {
|
|
20
|
+
const dir = section.dir?.replace(/^\/|\/$/g, '')
|
|
21
|
+
const p = page.replace(/^\/|\/$/g, '')
|
|
22
|
+
if (!dir) return p
|
|
23
|
+
return p.startsWith(`${dir}/`) ? p : `${dir}/${p}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Merge the explicit `nav` config with frontmatter from the content collection.
|
|
28
|
+
* Page titles/descriptions come from each file's frontmatter. Pages whose file
|
|
29
|
+
* is missing on disk are skipped silently, so drafts can be left out of nav.
|
|
30
|
+
*/
|
|
31
|
+
export function buildNavigation(entries: EntryLike[], nav: NavSection[]): DocSection[] {
|
|
32
|
+
const frontmatter = new Map(entries.map((e) => [e.id.replace(/\.mdx?$/, ''), e.data]))
|
|
33
|
+
const sections: DocSection[] = []
|
|
34
|
+
|
|
35
|
+
for (const section of nav) {
|
|
36
|
+
const pages: DocPage[] = []
|
|
37
|
+
for (const page of section.pages) {
|
|
38
|
+
const slug = toSlug(section, page)
|
|
39
|
+
const data = frontmatter.get(slug)
|
|
40
|
+
if (!data) continue
|
|
41
|
+
pages.push({ slug, title: data.title, description: data.description })
|
|
42
|
+
}
|
|
43
|
+
if (pages.length > 0) sections.push({ label: section.label, pages })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return sections
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function flattenNavigation(nav: DocSection[]): DocPage[] {
|
|
50
|
+
return nav.flatMap((s) => s.pages)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getPrevNext(
|
|
54
|
+
nav: DocSection[],
|
|
55
|
+
currentSlug: string,
|
|
56
|
+
): { prev: DocPage | null; next: DocPage | null } {
|
|
57
|
+
const flat = flattenNavigation(nav)
|
|
58
|
+
const i = flat.findIndex((p) => p.slug === currentSlug)
|
|
59
|
+
if (i === -1) return { prev: null, next: null }
|
|
60
|
+
return {
|
|
61
|
+
prev: i > 0 ? flat[i - 1] : null,
|
|
62
|
+
next: i < flat.length - 1 ? flat[i + 1] : null,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getSectionForSlug(nav: DocSection[], slug: string): DocSection | null {
|
|
67
|
+
return nav.find((s) => s.pages.some((p) => p.slug === slug)) ?? null
|
|
68
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefix an internal path with the site's configured base path so links work
|
|
3
|
+
* under sub-path hosting (e.g. `base: '/docs'`). Use for every internal href.
|
|
4
|
+
*/
|
|
5
|
+
export function withBase(path: string): string {
|
|
6
|
+
const base = import.meta.env.BASE_URL // '/' or '/docs/'
|
|
7
|
+
const b = base.endsWith('/') ? base.slice(0, -1) : base
|
|
8
|
+
const p = path.startsWith('/') ? path : `/${path}`
|
|
9
|
+
// For base '/', `b` is '' and `withBase('/')` correctly yields '/'.
|
|
10
|
+
return `${b}${p}`
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:open-doc-config'
|
|
3
|
+
import DocsLayout from '../layouts/DocsLayout.astro'
|
|
4
|
+
import { withBase } from '../lib/withBase'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<DocsLayout title="Page not found" currentSlug="" showPrevNext={false}>
|
|
8
|
+
<div class="flex flex-col items-center justify-center py-20 text-center">
|
|
9
|
+
<p class="text-6xl font-bold tracking-tight text-foreground/20">404</p>
|
|
10
|
+
<h1 class="mt-4 text-2xl font-bold text-foreground/90">Page not found</h1>
|
|
11
|
+
<p class="mt-2 max-w-sm text-sm text-foreground/55">
|
|
12
|
+
The page you're looking for doesn't exist or may have moved.
|
|
13
|
+
</p>
|
|
14
|
+
<a
|
|
15
|
+
href={withBase('/')}
|
|
16
|
+
class="mt-6 inline-flex items-center gap-2 rounded-lg border border-foreground/[0.12] bg-foreground/[0.04] px-4 py-2 text-sm font-medium text-foreground/80 transition-all hover:border-foreground/[0.2] hover:bg-foreground/[0.07] hover:text-foreground"
|
|
17
|
+
>
|
|
18
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
19
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"></path>
|
|
20
|
+
</svg>
|
|
21
|
+
Back to {config.title}
|
|
22
|
+
</a>
|
|
23
|
+
</div>
|
|
24
|
+
</DocsLayout>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection, render } from 'astro:content'
|
|
3
|
+
import config from 'virtual:open-doc-config'
|
|
4
|
+
import DocsLayout from '../layouts/DocsLayout.astro'
|
|
5
|
+
|
|
6
|
+
export async function getStaticPaths() {
|
|
7
|
+
const entries = await getCollection('docs')
|
|
8
|
+
return entries.map((entry) => ({
|
|
9
|
+
params: { slug: entry.id.replace(/\.mdx?$/, '') },
|
|
10
|
+
props: { entry },
|
|
11
|
+
}))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { entry } = Astro.props
|
|
15
|
+
const { Content, headings } = await render(entry)
|
|
16
|
+
const slug = entry.id.replace(/\.mdx?$/, '')
|
|
17
|
+
|
|
18
|
+
const ext = entry.filePath?.endsWith('.mdx') ? '.mdx' : '.md'
|
|
19
|
+
const editUrl = config.editLinkBase
|
|
20
|
+
? `${config.editLinkBase.replace(/\/$/, '')}/${slug}${ext}`
|
|
21
|
+
: undefined
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<DocsLayout
|
|
25
|
+
title={entry.data.title}
|
|
26
|
+
description={entry.data.description}
|
|
27
|
+
currentSlug={slug}
|
|
28
|
+
headings={headings}
|
|
29
|
+
editUrl={editUrl}
|
|
30
|
+
>
|
|
31
|
+
<article class="docs-prose prose max-w-none" data-pagefind-body>
|
|
32
|
+
<Content />
|
|
33
|
+
</article>
|
|
34
|
+
</DocsLayout>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from 'astro:content'
|
|
3
|
+
import config from 'virtual:open-doc-config'
|
|
4
|
+
import DocsLayout from '../layouts/DocsLayout.astro'
|
|
5
|
+
import { buildNavigation, flattenNavigation } from '../lib/navigation'
|
|
6
|
+
import { withBase } from '../lib/withBase'
|
|
7
|
+
|
|
8
|
+
const navigation = buildNavigation(await getCollection('docs'), config.nav)
|
|
9
|
+
const firstPage = flattenNavigation(navigation)[0]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<DocsLayout title={config.title} currentSlug="" showPrevNext={false} navigation={navigation}>
|
|
13
|
+
<div class="mb-10">
|
|
14
|
+
<h1 class="text-3xl font-bold tracking-tight text-foreground/95 sm:text-4xl">
|
|
15
|
+
{config.title}
|
|
16
|
+
</h1>
|
|
17
|
+
{
|
|
18
|
+
config.description && (
|
|
19
|
+
<p class="mt-3 text-base leading-relaxed text-foreground/55">{config.description}</p>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
firstPage && (
|
|
26
|
+
<a
|
|
27
|
+
href={withBase(firstPage.slug)}
|
|
28
|
+
class="group mb-8 flex items-center gap-4 rounded-xl border border-foreground/[0.1] bg-foreground/[0.03] p-5 transition-all hover:border-foreground/[0.18] hover:bg-foreground/[0.05]"
|
|
29
|
+
>
|
|
30
|
+
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-foreground/[0.08]">
|
|
31
|
+
<svg
|
|
32
|
+
class="h-5 w-5 text-foreground/70"
|
|
33
|
+
fill="none"
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
stroke="currentColor"
|
|
36
|
+
stroke-width="1.5"
|
|
37
|
+
>
|
|
38
|
+
<path
|
|
39
|
+
stroke-linecap="round"
|
|
40
|
+
stroke-linejoin="round"
|
|
41
|
+
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"
|
|
42
|
+
/>
|
|
43
|
+
</svg>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="min-w-0 flex-1">
|
|
46
|
+
<div class="font-semibold text-foreground/90">Get started</div>
|
|
47
|
+
<div class="mt-0.5 text-sm text-foreground/45">Jump straight into {firstPage.title}.</div>
|
|
48
|
+
</div>
|
|
49
|
+
<svg
|
|
50
|
+
class="h-4 w-4 shrink-0 text-foreground/30 transition-transform group-hover:translate-x-0.5"
|
|
51
|
+
fill="none"
|
|
52
|
+
viewBox="0 0 24 24"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
stroke-width="2"
|
|
55
|
+
>
|
|
56
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
|
57
|
+
</svg>
|
|
58
|
+
</a>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
<div class="grid gap-4 sm:grid-cols-2">
|
|
63
|
+
{
|
|
64
|
+
navigation.map((section) => (
|
|
65
|
+
<div class="flex flex-col rounded-xl border border-foreground/[0.08] bg-foreground/[0.02] p-5">
|
|
66
|
+
<h2 class="mb-3 text-xs font-semibold uppercase tracking-widest text-foreground/40">
|
|
67
|
+
{section.label}
|
|
68
|
+
</h2>
|
|
69
|
+
<ul class="flex flex-col gap-1">
|
|
70
|
+
{section.pages.map((page) => (
|
|
71
|
+
<li>
|
|
72
|
+
<a
|
|
73
|
+
href={withBase(page.slug)}
|
|
74
|
+
class="group flex items-start gap-2 rounded-md px-2 py-1.5 transition-colors hover:bg-foreground/[0.05]"
|
|
75
|
+
>
|
|
76
|
+
<svg
|
|
77
|
+
class="mt-0.5 h-3.5 w-3.5 shrink-0 text-foreground/25 transition-colors group-hover:text-foreground/50"
|
|
78
|
+
fill="none"
|
|
79
|
+
viewBox="0 0 24 24"
|
|
80
|
+
stroke="currentColor"
|
|
81
|
+
stroke-width="2"
|
|
82
|
+
>
|
|
83
|
+
<path
|
|
84
|
+
stroke-linecap="round"
|
|
85
|
+
stroke-linejoin="round"
|
|
86
|
+
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
87
|
+
/>
|
|
88
|
+
</svg>
|
|
89
|
+
<div>
|
|
90
|
+
<div class="text-sm font-medium leading-snug text-foreground/80 transition-colors group-hover:text-foreground/95">
|
|
91
|
+
{page.title}
|
|
92
|
+
</div>
|
|
93
|
+
{page.description && (
|
|
94
|
+
<div class="mt-0.5 text-xs leading-snug text-foreground/40">
|
|
95
|
+
{page.description}
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</a>
|
|
100
|
+
</li>
|
|
101
|
+
))}
|
|
102
|
+
</ul>
|
|
103
|
+
</div>
|
|
104
|
+
))
|
|
105
|
+
}
|
|
106
|
+
</div>
|
|
107
|
+
</DocsLayout>
|