reallysimpledocs 1.0.0-beta.1 → 1.0.0-beta.2
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 +10 -0
- package/package.json +4 -3
- package/src/astro/index.js +45 -2
- package/src/components/index.js +11 -0
- package/src/css/custom.css +183 -7
- package/src/runtime/components/CommandDialog.astro +5 -5
- package/src/runtime/components/mdx/Badge.astro +15 -0
- package/src/runtime/components/mdx/Callout.astro +54 -0
- package/src/runtime/components/mdx/Card.astro +13 -0
- package/src/runtime/components/mdx/CardGrid.astro +7 -0
- package/src/runtime/components/mdx/Code.astro +19 -0
- package/src/runtime/components/mdx/CodeGroup.astro +56 -0
- package/src/runtime/components/mdx/LinkButton.astro +14 -0
- package/src/runtime/components/mdx/Preview.astro +58 -0
- package/src/runtime/components/mdx/Step.astro +8 -0
- package/src/runtime/components/mdx/Steps.astro +7 -0
- package/src/runtime/components/mdx/Tabs.astro +52 -0
- package/src/runtime/lib/docs.js +41 -6
- package/src/runtime/lib/paths.js +8 -2
- package/src/runtime/pages/doc.astro +7 -3
- package/src/runtime/scripts/copy-code.js +35 -4
package/README.md
CHANGED
|
@@ -61,6 +61,16 @@ Welcome to the docs.
|
|
|
61
61
|
|
|
62
62
|
RSD injects docs pages, Lunr search, per-page Markdown exports, `llms.txt`, and `llms-full.txt`.
|
|
63
63
|
|
|
64
|
+
Use `.md` for normal pages and `.mdx` when a page needs Astro components:
|
|
65
|
+
|
|
66
|
+
```mdx
|
|
67
|
+
import { Callout, Preview } from "reallysimpledocs/components";
|
|
68
|
+
|
|
69
|
+
# Button
|
|
70
|
+
|
|
71
|
+
<Callout type="tip">MDX is optional. Plain Markdown stays the default.</Callout>
|
|
72
|
+
```
|
|
73
|
+
|
|
64
74
|
## Use Basecoat on the rest of the site
|
|
65
75
|
|
|
66
76
|
RSD styles and scripts its generated docs pages. For custom Astro pages outside the docs route, use Basecoat directly in your own layout.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reallysimpledocs",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.js",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"check": "npm run build && npm pack --dry-run && npm run smoke:consumer"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@astrojs/mdx": "^6.0.3",
|
|
23
24
|
"@tailwindcss/vite": "4.1.17",
|
|
24
25
|
"astro": "^6.4.6",
|
|
25
26
|
"github-slugger": "^2.0.0",
|
|
@@ -29,11 +30,11 @@
|
|
|
29
30
|
"shiki": "^4.1.0"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"basecoat-css": "^1.0.0-beta.
|
|
33
|
+
"basecoat-css": "^1.0.0-beta.2",
|
|
33
34
|
"tailwindcss": ">=4.1.17 <5"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
|
-
"basecoat-css": "
|
|
37
|
+
"basecoat-css": "1.0.0-beta.2",
|
|
37
38
|
"tailwindcss": "4.1.17"
|
|
38
39
|
},
|
|
39
40
|
"overrides": {
|
package/src/astro/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import mdx from "@astrojs/mdx";
|
|
4
5
|
import tailwindcss from "@tailwindcss/vite";
|
|
5
6
|
|
|
6
7
|
const basecoatStyles = new Set(["vega", "nova", "maia", "lyra", "mira", "luma", "sera", "rhea"]);
|
|
@@ -23,6 +24,28 @@ const cssPath = (relativePath) => fileURLToPath(new URL(`../css/${relativePath}`
|
|
|
23
24
|
|
|
24
25
|
const normalizeArray = (value) => (Array.isArray(value) ? value : value ? [value] : []);
|
|
25
26
|
|
|
27
|
+
const normalizeImportPath = (filePath) => filePath.replaceAll(path.sep, "/");
|
|
28
|
+
|
|
29
|
+
function collectMdxModules(docsDir) {
|
|
30
|
+
if (!fs.existsSync(docsDir)) return {};
|
|
31
|
+
const modules = {};
|
|
32
|
+
const walk = (dir) => {
|
|
33
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
34
|
+
const fullPath = path.join(dir, entry.name);
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
walk(fullPath);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!entry.isFile() || !entry.name.endsWith(".mdx")) continue;
|
|
40
|
+
const relative = path.relative(docsDir, fullPath).replaceAll(path.sep, "/");
|
|
41
|
+
const slug = relative.replace(/\.mdx$/, "");
|
|
42
|
+
modules[slug] = normalizeImportPath(fullPath);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
walk(docsDir);
|
|
46
|
+
return modules;
|
|
47
|
+
}
|
|
48
|
+
|
|
26
49
|
export default function reallySimpleDocs(options = {}) {
|
|
27
50
|
const style = options.style || "vega";
|
|
28
51
|
if (!basecoatStyles.has(style)) {
|
|
@@ -47,13 +70,15 @@ export default function reallySimpleDocs(options = {}) {
|
|
|
47
70
|
const root = fileURLToPath(config.root);
|
|
48
71
|
const generatedDir = path.join(root, ".astro", "reallysimpledocs");
|
|
49
72
|
const generatedStyles = path.join(generatedDir, "styles.css");
|
|
73
|
+
const docsDir = path.resolve(root, normalizedOptions.docsDir);
|
|
50
74
|
const virtualConfig = {
|
|
51
75
|
...normalizedOptions,
|
|
52
76
|
root,
|
|
53
|
-
docsDir
|
|
77
|
+
docsDir,
|
|
54
78
|
siteFile: normalizedOptions.siteFile ? path.resolve(root, normalizedOptions.siteFile) : null,
|
|
55
79
|
routeBase: normalizeRouteBase(normalizedOptions.routeBase),
|
|
56
80
|
};
|
|
81
|
+
const mdxModules = collectMdxModules(docsDir);
|
|
57
82
|
const resolveUserId = (id) => (id?.startsWith(".") ? path.resolve(root, id) : id);
|
|
58
83
|
const components = {
|
|
59
84
|
SidebarHeader:
|
|
@@ -71,7 +96,7 @@ export default function reallySimpleDocs(options = {}) {
|
|
|
71
96
|
`@import ${JSON.stringify(cssPath("overrides.css"))};`,
|
|
72
97
|
...normalizedOptions.customCss.map((css) => `@import ${JSON.stringify(resolveUserId(css))};`),
|
|
73
98
|
`@source ${JSON.stringify(runtimePath("**/*.{astro,js,ts}"))};`,
|
|
74
|
-
`@source ${JSON.stringify(path.join(virtualConfig.docsDir, "**/*.md"))};`,
|
|
99
|
+
`@source ${JSON.stringify(path.join(virtualConfig.docsDir, "**/*.{md,mdx}"))};`,
|
|
75
100
|
"",
|
|
76
101
|
].join("\n"),
|
|
77
102
|
);
|
|
@@ -98,6 +123,7 @@ export default function reallySimpleDocs(options = {}) {
|
|
|
98
123
|
});
|
|
99
124
|
|
|
100
125
|
updateConfig({
|
|
126
|
+
integrations: [mdx()],
|
|
101
127
|
vite: {
|
|
102
128
|
build: {
|
|
103
129
|
cssMinify: false,
|
|
@@ -108,6 +134,7 @@ export default function reallySimpleDocs(options = {}) {
|
|
|
108
134
|
name: "reallysimpledocs-virtual-modules",
|
|
109
135
|
resolveId(id) {
|
|
110
136
|
if (id === "virtual:reallysimpledocs/config") return "\0virtual:reallysimpledocs/config";
|
|
137
|
+
if (id === "virtual:reallysimpledocs/mdx-pages") return "\0virtual:reallysimpledocs/mdx-pages";
|
|
111
138
|
if (id === "virtual:reallysimpledocs/styles.css") return generatedStyles;
|
|
112
139
|
if (id === "virtual:reallysimpledocs/components/SidebarHeader") {
|
|
113
140
|
return "\0virtual:reallysimpledocs/components/SidebarHeader";
|
|
@@ -116,6 +143,22 @@ export default function reallySimpleDocs(options = {}) {
|
|
|
116
143
|
},
|
|
117
144
|
load(id) {
|
|
118
145
|
if (id === "\0virtual:reallysimpledocs/config") return `export default ${JSON.stringify(virtualConfig)};`;
|
|
146
|
+
if (id === "\0virtual:reallysimpledocs/mdx-pages") {
|
|
147
|
+
const imports = Object.entries(mdxModules)
|
|
148
|
+
.map(([slug, filePath], index) => `import * as mdx${index} from ${JSON.stringify(filePath)};`)
|
|
149
|
+
.join("\n");
|
|
150
|
+
const moduleMap = Object.fromEntries(Object.keys(mdxModules).map((slug, index) => [slug, `mdx${index}`]));
|
|
151
|
+
return `${imports}
|
|
152
|
+
const modules = {
|
|
153
|
+
${Object.entries(moduleMap)
|
|
154
|
+
.map(([slug, name]) => ` ${JSON.stringify(slug)}: ${name},`)
|
|
155
|
+
.join("\n")}
|
|
156
|
+
};
|
|
157
|
+
export async function getMdxPage(slug) {
|
|
158
|
+
return modules[slug || "index"] || null;
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
119
162
|
if (id === "\0virtual:reallysimpledocs/components/SidebarHeader") {
|
|
120
163
|
return `export { default } from ${JSON.stringify(resolveUserId(components.SidebarHeader))};`;
|
|
121
164
|
}
|
package/src/components/index.js
CHANGED
|
@@ -2,3 +2,14 @@ export { default as DocsLayout } from "../runtime/components/DocsLayout.astro";
|
|
|
2
2
|
export { default as CommandDialog } from "../runtime/components/CommandDialog.astro";
|
|
3
3
|
export { default as DropdownMenu } from "../runtime/components/DropdownMenu.astro";
|
|
4
4
|
export { default as Sidebar } from "../runtime/components/Sidebar.astro";
|
|
5
|
+
export { default as Badge } from "../runtime/components/mdx/Badge.astro";
|
|
6
|
+
export { default as Callout } from "../runtime/components/mdx/Callout.astro";
|
|
7
|
+
export { default as Card } from "../runtime/components/mdx/Card.astro";
|
|
8
|
+
export { default as CardGrid } from "../runtime/components/mdx/CardGrid.astro";
|
|
9
|
+
export { default as Code } from "../runtime/components/mdx/Code.astro";
|
|
10
|
+
export { default as CodeGroup } from "../runtime/components/mdx/CodeGroup.astro";
|
|
11
|
+
export { default as LinkButton } from "../runtime/components/mdx/LinkButton.astro";
|
|
12
|
+
export { default as Preview } from "../runtime/components/mdx/Preview.astro";
|
|
13
|
+
export { default as Step } from "../runtime/components/mdx/Step.astro";
|
|
14
|
+
export { default as Steps } from "../runtime/components/mdx/Steps.astro";
|
|
15
|
+
export { default as Tabs } from "../runtime/components/mdx/Tabs.astro";
|
package/src/css/custom.css
CHANGED
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
> h3 {
|
|
21
21
|
@apply text-lg sm:text-xl lg:mt-8;
|
|
22
22
|
}
|
|
23
|
+
> h1:first-child {
|
|
24
|
+
@apply hidden;
|
|
25
|
+
}
|
|
23
26
|
p > a,
|
|
24
27
|
li > a,
|
|
25
28
|
dt > a,
|
|
@@ -38,10 +41,7 @@
|
|
|
38
41
|
@apply block min-w-full w-max px-4 py-3 text-[13px];
|
|
39
42
|
}
|
|
40
43
|
pre:has(> code) {
|
|
41
|
-
@apply
|
|
42
|
-
}
|
|
43
|
-
pre:has(> code) > button {
|
|
44
|
-
@apply flex items-center justify-center size-6 absolute right-2.5 top-2.5 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] rounded-md hover:bg-accent dark:hover:bg-accent/50 text-muted-foreground hover:text-foreground [&_svg]:size-4;
|
|
44
|
+
@apply max-w-full overflow-x-auto;
|
|
45
45
|
}
|
|
46
46
|
blockquote {
|
|
47
47
|
@apply border-l-4 border-border pl-4;
|
|
@@ -140,18 +140,194 @@ html.dark .shiki span {
|
|
|
140
140
|
@apply sm:max-w-2xl;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
[data-search] .command a[role='menuitem'] {
|
|
144
144
|
@apply flex-col items-start gap-1;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
[data-search] .command a[role='menuitem'] > strong {
|
|
148
148
|
@apply font-medium;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
[data-search] .command a[role='menuitem'] > span {
|
|
152
152
|
@apply text-muted-foreground line-clamp-2 text-xs font-normal leading-snug;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
.command mark {
|
|
156
156
|
@apply bg-search-highlight/50 text-foreground rounded-sm px-0.5 dark:bg-search-highlight/25;
|
|
157
157
|
}
|
|
158
|
+
|
|
159
|
+
.docs-card-link {
|
|
160
|
+
@apply transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.alert[data-variant='warning'] > svg,
|
|
164
|
+
.alert[data-variant='warning'] > h3,
|
|
165
|
+
.alert[data-variant='warning'] > section {
|
|
166
|
+
@apply text-amber-700 dark:text-amber-300;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.alert-destructive[data-variant='danger'] > svg,
|
|
170
|
+
.alert-destructive[data-variant='danger'] > h3,
|
|
171
|
+
.alert-destructive[data-variant='danger'] > section {
|
|
172
|
+
@apply text-destructive;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.code-block {
|
|
176
|
+
@apply relative my-6 max-w-full overflow-hidden rounded-lg;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.code-block > header,
|
|
180
|
+
.code-group > header,
|
|
181
|
+
.preview > header {
|
|
182
|
+
@apply flex h-10 items-center gap-2 border-b border-border/50 bg-muted px-3 text-sm font-medium dark:bg-muted/50;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.code-block > header > :first-child,
|
|
186
|
+
.code-group > header > :first-child,
|
|
187
|
+
.preview > header > :first-child {
|
|
188
|
+
@apply mr-auto;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.code-block > pre {
|
|
192
|
+
@apply my-0 max-w-full overflow-x-auto rounded-none border-0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.code-block > pre > code {
|
|
196
|
+
@apply block min-w-full w-max px-4 py-3 text-[13px];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.code-block > .shiki {
|
|
200
|
+
@apply my-0 rounded-none border-0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.code-block > button {
|
|
204
|
+
@apply absolute right-2.5 top-2.5;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.code-block button,
|
|
208
|
+
.code-group button[aria-label='Copy code'],
|
|
209
|
+
.preview button[aria-label='Copy code'] {
|
|
210
|
+
@apply flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:hover:bg-accent/50 [&_svg]:size-4;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.code-block > header > button,
|
|
214
|
+
.code-group > header > button,
|
|
215
|
+
.preview > header > button {
|
|
216
|
+
@apply ml-auto;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.code-group {
|
|
220
|
+
@apply relative my-6 max-w-full overflow-hidden rounded-lg;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.code-group > header > .tabs {
|
|
224
|
+
@apply my-0 min-w-0 max-w-full gap-0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.code-group > header > .tabs > nav {
|
|
228
|
+
@apply h-full justify-start rounded-none bg-transparent p-0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.code-group > [role='tabpanel'] {
|
|
232
|
+
@apply border-t-0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.code-group .code-block {
|
|
236
|
+
@apply my-0 min-w-0 rounded-none;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.code-group .code-block > header {
|
|
240
|
+
@apply hidden;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.preview {
|
|
244
|
+
@apply relative my-6 max-w-full overflow-hidden rounded-lg border border-border/50 bg-muted dark:bg-muted/50;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.preview > header > .tabs {
|
|
248
|
+
@apply my-0 min-w-0 max-w-full gap-0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.preview > header > .tabs > nav {
|
|
252
|
+
@apply h-full justify-start rounded-none bg-transparent p-0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.preview > [role='tabpanel'] {
|
|
256
|
+
@apply border-t-0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.preview > [data-code-panel] .code-block {
|
|
260
|
+
@apply my-0 min-w-0 rounded-none;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.preview > header > button[aria-label='Copy code'] {
|
|
264
|
+
display: none;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.preview:has(> [data-code-panel]:not([hidden])) > header > button[aria-label='Copy code'] {
|
|
268
|
+
display: flex;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.preview > [data-preview-panel] > div {
|
|
272
|
+
@apply flex min-h-64 w-full items-center justify-center bg-muted p-6 dark:bg-muted/50;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.steps {
|
|
276
|
+
@apply mb-12 mt-6 md:ml-4 md:border-l md:pl-8;
|
|
277
|
+
counter-reset: step;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.steps > section {
|
|
281
|
+
counter-increment: step;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.steps > section > h3 {
|
|
285
|
+
@apply relative mt-12 scroll-m-28 text-lg font-medium tracking-tight;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.steps > section:first-child > h3 {
|
|
289
|
+
@apply mt-0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.steps > section > h3::before {
|
|
293
|
+
@apply mr-4 inline-flex size-7 items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground;
|
|
294
|
+
content: counter(step);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@media (width >= 48rem) {
|
|
298
|
+
.steps > section > h3::before {
|
|
299
|
+
position: absolute;
|
|
300
|
+
left: 0;
|
|
301
|
+
top: 0;
|
|
302
|
+
margin-right: 0;
|
|
303
|
+
transform: translateX(-2.875rem);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.steps > section > * + * {
|
|
308
|
+
@apply mt-6;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.steps > section > h3 + * {
|
|
312
|
+
@apply mt-4;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.steps > section > * {
|
|
316
|
+
@apply leading-relaxed;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.steps > section > *:first-child {
|
|
320
|
+
@apply mt-0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.steps > section > *:last-child {
|
|
324
|
+
@apply mb-0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.prose > .alert,
|
|
328
|
+
.prose > .alert-destructive,
|
|
329
|
+
.prose > .card,
|
|
330
|
+
.prose > .grid,
|
|
331
|
+
.prose > .tabs {
|
|
332
|
+
@apply my-6;
|
|
333
|
+
}
|
|
@@ -4,7 +4,7 @@ import { renderCommandDialog } from "../lib/macros.js";
|
|
|
4
4
|
const { indexUrl } = Astro.props;
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<div data-
|
|
7
|
+
<div data-search data-index-url={indexUrl}>
|
|
8
8
|
<Fragment
|
|
9
9
|
set:html={renderCommandDialog({
|
|
10
10
|
id: "command-search",
|
|
@@ -18,7 +18,7 @@ const { indexUrl } = Astro.props;
|
|
|
18
18
|
<script>
|
|
19
19
|
import lunr from "lunr";
|
|
20
20
|
|
|
21
|
-
const root = document.querySelector("[data-
|
|
21
|
+
const root = document.querySelector("[data-search]");
|
|
22
22
|
const indexUrl = root?.getAttribute("data-index-url");
|
|
23
23
|
const dialog = document.getElementById("command-search");
|
|
24
24
|
const command = dialog?.querySelector(".command");
|
|
@@ -178,9 +178,9 @@ const { indexUrl } = Astro.props;
|
|
|
178
178
|
${items
|
|
179
179
|
.map((item, index) => {
|
|
180
180
|
const view = resultView(item, query);
|
|
181
|
-
return `<a id="command-search-result-${index}" role="menuitem" data-force
|
|
182
|
-
<
|
|
183
|
-
${view.snippet ? `<span
|
|
181
|
+
return `<a id="command-search-result-${index}" role="menuitem" data-force href="${escapeHtml(view.path)}">
|
|
182
|
+
<strong>${view.title}</strong>
|
|
183
|
+
${view.snippet ? `<span>${view.snippet}</span>` : ""}
|
|
184
184
|
</a>`;
|
|
185
185
|
})
|
|
186
186
|
.join("")}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { variant = "secondary", class: className = "" } = Astro.props;
|
|
3
|
+
const variants = {
|
|
4
|
+
primary: "badge-primary",
|
|
5
|
+
secondary: "badge-secondary",
|
|
6
|
+
destructive: "badge-destructive",
|
|
7
|
+
outline: "badge-outline",
|
|
8
|
+
ghost: "badge-ghost",
|
|
9
|
+
link: "badge-link",
|
|
10
|
+
};
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<span class:list={[variants[variant] || "badge-secondary", className]}>
|
|
14
|
+
<slot />
|
|
15
|
+
</span>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
import CircleAlert from "lucide-static/icons/circle-alert.svg?raw";
|
|
3
|
+
import Info from "lucide-static/icons/info.svg?raw";
|
|
4
|
+
import Lightbulb from "lucide-static/icons/lightbulb.svg?raw";
|
|
5
|
+
import TriangleAlert from "lucide-static/icons/triangle-alert.svg?raw";
|
|
6
|
+
import * as lucideIcons from "lucide-static";
|
|
7
|
+
import { addSvgClass } from "../../lib/html.js";
|
|
8
|
+
|
|
9
|
+
const { type, variant, title, icon: showIcon = true, class: className = "" } = Astro.props;
|
|
10
|
+
const normalizedType = String(variant || type || "note").toLowerCase();
|
|
11
|
+
const calloutType = normalizedType === "note" ? "info" : normalizedType;
|
|
12
|
+
const destructive = calloutType === "danger";
|
|
13
|
+
const labels = {
|
|
14
|
+
note: "Note",
|
|
15
|
+
info: "Info",
|
|
16
|
+
tip: "Tip",
|
|
17
|
+
warning: "Warning",
|
|
18
|
+
danger: "Danger",
|
|
19
|
+
};
|
|
20
|
+
const icons = {
|
|
21
|
+
note: Info,
|
|
22
|
+
info: Info,
|
|
23
|
+
tip: Lightbulb,
|
|
24
|
+
warning: TriangleAlert,
|
|
25
|
+
danger: CircleAlert,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function resolveNamedIcon(name) {
|
|
29
|
+
if (!name || typeof name !== "string") return "";
|
|
30
|
+
const trimmed = name.trim();
|
|
31
|
+
if (!trimmed) return "";
|
|
32
|
+
if (trimmed.startsWith("<svg")) return trimmed;
|
|
33
|
+
const pascalName = trimmed.includes("-")
|
|
34
|
+
? trimmed
|
|
35
|
+
.split("-")
|
|
36
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
37
|
+
.join("")
|
|
38
|
+
: trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
|
39
|
+
return lucideIcons[pascalName] || "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const iconSource =
|
|
43
|
+
showIcon === false ? "" : typeof showIcon === "string" ? resolveNamedIcon(showIcon) : icons[normalizedType] || Info;
|
|
44
|
+
const icon = addSvgClass(iconSource, "size-4");
|
|
45
|
+
const heading = title || labels[normalizedType] || labels.note;
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
<div class:list={[destructive ? "alert-destructive" : "alert", className]} data-variant={calloutType}>
|
|
49
|
+
{icon && <Fragment set:html={icon} />}
|
|
50
|
+
<h3>{heading}</h3>
|
|
51
|
+
<section>
|
|
52
|
+
<slot />
|
|
53
|
+
</section>
|
|
54
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { title, description, href, class: className = "" } = Astro.props;
|
|
3
|
+
const Tag = href ? "a" : "article";
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<Tag href={href} class:list={["card w-full no-underline", href && "docs-card-link", className]}>
|
|
7
|
+
{(title || description) && (
|
|
8
|
+
<header>
|
|
9
|
+
{title && <h3>{title}</h3>}
|
|
10
|
+
{description && <p>{description}</p>}
|
|
11
|
+
</header>
|
|
12
|
+
)}
|
|
13
|
+
</Tag>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { codeToHtml } from "shiki";
|
|
3
|
+
|
|
4
|
+
const { code = "", lang = "text", title, class: className = "" } = Astro.props;
|
|
5
|
+
const highlightLang = String(lang || "text").toLowerCase() === "mdx" ? "tsx" : String(lang || "text");
|
|
6
|
+
const html = await codeToHtml(String(code || ""), {
|
|
7
|
+
lang: highlightLang,
|
|
8
|
+
themes: {
|
|
9
|
+
light: "github-light",
|
|
10
|
+
dark: "github-dark",
|
|
11
|
+
},
|
|
12
|
+
defaultColor: false,
|
|
13
|
+
});
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<div class:list={["code-block", className]}>
|
|
17
|
+
{title && <header><span>{title}</span></header>}
|
|
18
|
+
<Fragment set:html={html} />
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Code from "./Code.astro";
|
|
3
|
+
|
|
4
|
+
const { tabs = [], defaultTab, class: className = "" } = Astro.props;
|
|
5
|
+
const id = `code-group-${Math.random().toString(36).slice(2)}`;
|
|
6
|
+
const normalizedTabs = tabs.map((tab, index) => ({
|
|
7
|
+
...tab,
|
|
8
|
+
value: tab.value || tab.label || String(index),
|
|
9
|
+
lang: tab.lang || "text",
|
|
10
|
+
code: tab.code || "",
|
|
11
|
+
}));
|
|
12
|
+
const defaultValue = defaultTab || normalizedTabs[0]?.value;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<div class:list={["code-group", className]}>
|
|
16
|
+
<header>
|
|
17
|
+
<div class="tabs">
|
|
18
|
+
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
|
|
19
|
+
{
|
|
20
|
+
normalizedTabs.map((tab, index) => {
|
|
21
|
+
const selected = tab.value === defaultValue;
|
|
22
|
+
return (
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
role="tab"
|
|
26
|
+
id={`${id}-tab-${index + 1}`}
|
|
27
|
+
aria-controls={`${id}-panel-${index + 1}`}
|
|
28
|
+
aria-selected={selected ? "true" : "false"}
|
|
29
|
+
tabindex={selected ? "0" : "-1"}
|
|
30
|
+
>
|
|
31
|
+
{tab.label}
|
|
32
|
+
</button>
|
|
33
|
+
);
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
</nav>
|
|
37
|
+
</div>
|
|
38
|
+
</header>
|
|
39
|
+
{
|
|
40
|
+
normalizedTabs.map((tab, index) => {
|
|
41
|
+
const selected = tab.value === defaultValue;
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
role="tabpanel"
|
|
45
|
+
id={`${id}-panel-${index + 1}`}
|
|
46
|
+
aria-labelledby={`${id}-tab-${index + 1}`}
|
|
47
|
+
tabindex="-1"
|
|
48
|
+
aria-selected={selected ? "true" : "false"}
|
|
49
|
+
hidden={!selected}
|
|
50
|
+
>
|
|
51
|
+
<Code code={tab.code} lang={tab.lang} />
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { href, variant = "default", class: className = "", target, rel } = Astro.props;
|
|
3
|
+
const variants = {
|
|
4
|
+
default: "btn",
|
|
5
|
+
secondary: "btn-secondary",
|
|
6
|
+
outline: "btn-outline",
|
|
7
|
+
ghost: "btn-ghost",
|
|
8
|
+
link: "btn-link",
|
|
9
|
+
};
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<a href={href} target={target} rel={rel} class:list={[variants[variant] || "btn", className]}>
|
|
13
|
+
<slot />
|
|
14
|
+
</a>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Code from "./Code.astro";
|
|
3
|
+
|
|
4
|
+
const { code = "", lang = "html", title = "Preview", class: className = "" } = Astro.props;
|
|
5
|
+
const id = `preview-${Math.random().toString(36).slice(2)}`;
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<section class="preview">
|
|
9
|
+
<header>
|
|
10
|
+
<div class="tabs">
|
|
11
|
+
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
|
|
12
|
+
<button
|
|
13
|
+
type="button"
|
|
14
|
+
role="tab"
|
|
15
|
+
id={`${id}-preview-tab`}
|
|
16
|
+
aria-controls={`${id}-preview-panel`}
|
|
17
|
+
aria-selected="true"
|
|
18
|
+
tabindex="0"
|
|
19
|
+
>
|
|
20
|
+
{title}
|
|
21
|
+
</button>
|
|
22
|
+
<button
|
|
23
|
+
type="button"
|
|
24
|
+
role="tab"
|
|
25
|
+
id={`${id}-code-tab`}
|
|
26
|
+
aria-controls={`${id}-code-panel`}
|
|
27
|
+
aria-selected="false"
|
|
28
|
+
tabindex="-1"
|
|
29
|
+
>
|
|
30
|
+
Code
|
|
31
|
+
</button>
|
|
32
|
+
</nav>
|
|
33
|
+
</div>
|
|
34
|
+
</header>
|
|
35
|
+
<section
|
|
36
|
+
role="tabpanel"
|
|
37
|
+
data-preview-panel
|
|
38
|
+
id={`${id}-preview-panel`}
|
|
39
|
+
aria-labelledby={`${id}-preview-tab`}
|
|
40
|
+
tabindex="-1"
|
|
41
|
+
aria-selected="true"
|
|
42
|
+
>
|
|
43
|
+
<div class={className}>
|
|
44
|
+
<slot />
|
|
45
|
+
</div>
|
|
46
|
+
</section>
|
|
47
|
+
<section
|
|
48
|
+
role="tabpanel"
|
|
49
|
+
data-code-panel
|
|
50
|
+
id={`${id}-code-panel`}
|
|
51
|
+
aria-labelledby={`${id}-code-tab`}
|
|
52
|
+
tabindex="-1"
|
|
53
|
+
aria-selected="false"
|
|
54
|
+
hidden
|
|
55
|
+
>
|
|
56
|
+
<Code code={code} lang={lang} />
|
|
57
|
+
</section>
|
|
58
|
+
</section>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { tabs = [], defaultTab, orientation = "horizontal", variant, class: className = "" } = Astro.props;
|
|
3
|
+
const id = `tabs-${Math.random().toString(36).slice(2)}`;
|
|
4
|
+
const normalizedTabs = tabs.map((tab, index) => ({
|
|
5
|
+
...tab,
|
|
6
|
+
slot: tab.slot || String(index),
|
|
7
|
+
value: tab.value || tab.slot || String(index),
|
|
8
|
+
}));
|
|
9
|
+
const defaultValue = defaultTab || normalizedTabs.find((tab) => !tab.disabled)?.value || normalizedTabs[0]?.value;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<div class:list={["tabs", className]}>
|
|
13
|
+
<nav role="tablist" aria-orientation={orientation} data-variant={variant}>
|
|
14
|
+
{
|
|
15
|
+
normalizedTabs.map((tab, index) => {
|
|
16
|
+
const selected = tab.value === defaultValue;
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
role="tab"
|
|
21
|
+
id={`${id}-tab-${index + 1}`}
|
|
22
|
+
aria-controls={`${id}-panel-${index + 1}`}
|
|
23
|
+
aria-selected={selected ? "true" : "false"}
|
|
24
|
+
tabindex={selected && !tab.disabled ? "0" : "-1"}
|
|
25
|
+
disabled={tab.disabled}
|
|
26
|
+
>
|
|
27
|
+
{tab.label}
|
|
28
|
+
</button>
|
|
29
|
+
);
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
</nav>
|
|
33
|
+
{
|
|
34
|
+
await Promise.all(
|
|
35
|
+
normalizedTabs.map(async (tab, index) => {
|
|
36
|
+
const selected = tab.value === defaultValue;
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
role="tabpanel"
|
|
40
|
+
id={`${id}-panel-${index + 1}`}
|
|
41
|
+
aria-labelledby={`${id}-tab-${index + 1}`}
|
|
42
|
+
tabindex="-1"
|
|
43
|
+
aria-selected={selected ? "true" : "false"}
|
|
44
|
+
hidden={!selected}
|
|
45
|
+
>
|
|
46
|
+
<Fragment set:html={await Astro.slots.render(tab.slot)} />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}),
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
</div>
|
package/src/runtime/lib/docs.js
CHANGED
|
@@ -19,7 +19,16 @@ export const fallbackLabelFromSlug = (slug) => {
|
|
|
19
19
|
return base.replace(/-/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
export const slugToFile = (slug) => path.join(...String(slug).split("/")) +
|
|
22
|
+
export const slugToFile = (slug, extension = ".md") => path.join(...String(slug).split("/")) + extension;
|
|
23
|
+
|
|
24
|
+
export function resolveDocFile(config, slug) {
|
|
25
|
+
for (const extension of [".md", ".mdx"]) {
|
|
26
|
+
const file = slugToFile(slug, extension);
|
|
27
|
+
const filePath = path.join(config.docsDir, file);
|
|
28
|
+
if (fs.existsSync(filePath)) return { file, filePath, extension };
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Missing docs page for slug "${slug}". Expected .md or .mdx in ${config.docsDir}.`);
|
|
31
|
+
}
|
|
23
32
|
|
|
24
33
|
export function flattenMenuSlugs(menu) {
|
|
25
34
|
const out = [];
|
|
@@ -44,13 +53,13 @@ export function flattenMenuSlugs(menu) {
|
|
|
44
53
|
export function getPages(config) {
|
|
45
54
|
const manifest = getManifest(config);
|
|
46
55
|
return flattenMenuSlugs(manifest.menu || []).map((slug) => {
|
|
47
|
-
const file =
|
|
48
|
-
const filePath = path.join(config.docsDir, file);
|
|
56
|
+
const { file, filePath, extension } = resolveDocFile(config, slug);
|
|
49
57
|
const source = fs.readFileSync(filePath, "utf8");
|
|
50
58
|
const parsed = parseDocMarkdown(source, fallbackLabelFromSlug(slug));
|
|
51
59
|
return {
|
|
52
60
|
slug,
|
|
53
61
|
file,
|
|
62
|
+
extension,
|
|
54
63
|
path: routePath(config.routeBase, slug),
|
|
55
64
|
markdownPath: markdownPath(config.routeBase, slug),
|
|
56
65
|
title: parsed.title,
|
|
@@ -113,6 +122,20 @@ export async function renderDoc(config, page) {
|
|
|
113
122
|
return { page, html, headings };
|
|
114
123
|
}
|
|
115
124
|
|
|
125
|
+
export function getDocHeadings(config, page) {
|
|
126
|
+
const parsed = parseDocMarkdown(getDocMarkdown(config, page), page.title);
|
|
127
|
+
const headings = [];
|
|
128
|
+
const slugger = new GithubSlugger();
|
|
129
|
+
const tokens = marked.lexer(parsed.body);
|
|
130
|
+
tokens.forEach((token) => {
|
|
131
|
+
if (token.type !== "heading" || token.depth <= 1 || token.depth >= 4) return;
|
|
132
|
+
const text = plainText(token.tokens || []).trim();
|
|
133
|
+
if (!text) return;
|
|
134
|
+
headings.push({ depth: token.depth, id: slugger.slug(text), text });
|
|
135
|
+
});
|
|
136
|
+
return headings;
|
|
137
|
+
}
|
|
138
|
+
|
|
116
139
|
export function renderNunjucksCompat(content) {
|
|
117
140
|
return String(content).replace(
|
|
118
141
|
/\{%\s*lucide\s+["']([^"']+)["'](?:\s*,\s*[^%]+)?\s*%\}/g,
|
|
@@ -156,7 +179,7 @@ export function buildMenus(config, currentSlug) {
|
|
|
156
179
|
type: "item",
|
|
157
180
|
icon,
|
|
158
181
|
url: page.path,
|
|
159
|
-
label: page.title,
|
|
182
|
+
label: item.label || page.title,
|
|
160
183
|
current: page.slug === currentSlug,
|
|
161
184
|
keywords: page.title,
|
|
162
185
|
};
|
|
@@ -231,7 +254,7 @@ export function getPageItemSlug(item) {
|
|
|
231
254
|
}
|
|
232
255
|
|
|
233
256
|
function parseDocMarkdown(source, fallbackTitle) {
|
|
234
|
-
const content =
|
|
257
|
+
const content = normalizeDocSource(source);
|
|
235
258
|
const tokens = marked.lexer(content);
|
|
236
259
|
const first = tokens[0];
|
|
237
260
|
|
|
@@ -249,7 +272,18 @@ function parseDocMarkdown(source, fallbackTitle) {
|
|
|
249
272
|
}
|
|
250
273
|
|
|
251
274
|
function markdownPlainText(markdown) {
|
|
252
|
-
return tokensText(marked.lexer(renderNunjucksCompat(markdown)));
|
|
275
|
+
return tokensText(marked.lexer(renderNunjucksCompat(normalizeDocSource(markdown))));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function normalizeDocSource(source) {
|
|
279
|
+
const withoutFrontmatter = String(source || "")
|
|
280
|
+
.trimStart()
|
|
281
|
+
.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "");
|
|
282
|
+
const firstHeading = withoutFrontmatter.search(/^#\s+/m);
|
|
283
|
+
if (firstHeading > 0) return withoutFrontmatter.slice(firstHeading).trimStart();
|
|
284
|
+
return withoutFrontmatter
|
|
285
|
+
.replace(/^(\s*(?:import|export)\s+[^\n]*\n)+/m, "")
|
|
286
|
+
.trimStart();
|
|
253
287
|
}
|
|
254
288
|
|
|
255
289
|
async function highlightCode(tokens) {
|
|
@@ -296,6 +330,7 @@ async function highlightToken(token) {
|
|
|
296
330
|
function normalizeLanguage(lang) {
|
|
297
331
|
const value = lang?.trim().split(/\s+/)[0].toLowerCase() || "text";
|
|
298
332
|
if (value === "njk") return "html";
|
|
333
|
+
if (value === "mdx") return "tsx";
|
|
299
334
|
if (value === "sh" || value === "shell") return "bash";
|
|
300
335
|
return value;
|
|
301
336
|
}
|
package/src/runtime/lib/paths.js
CHANGED
|
@@ -2,12 +2,18 @@ export const stripSlashes = (value) => String(value || "").replace(/^\/+|\/+$/g,
|
|
|
2
2
|
|
|
3
3
|
export const routePath = (routeBase, slug) => {
|
|
4
4
|
const base = stripSlashes(routeBase);
|
|
5
|
-
const
|
|
5
|
+
const strippedSlug = stripSlashes(slug);
|
|
6
|
+
const normalizedSlug =
|
|
7
|
+
strippedSlug === "index" ? "" : strippedSlug.endsWith("/index") ? strippedSlug.slice(0, -"/index".length) : strippedSlug;
|
|
6
8
|
const parts = [base, normalizedSlug].filter(Boolean);
|
|
7
9
|
return parts.length ? `/${parts.join("/")}/` : "/";
|
|
8
10
|
};
|
|
9
11
|
|
|
10
|
-
export const routeSlug = (slug) =>
|
|
12
|
+
export const routeSlug = (slug) => {
|
|
13
|
+
const strippedSlug = stripSlashes(slug);
|
|
14
|
+
if (strippedSlug === "index") return undefined;
|
|
15
|
+
return strippedSlug.endsWith("/index") ? strippedSlug.slice(0, -"/index".length) : strippedSlug;
|
|
16
|
+
};
|
|
11
17
|
|
|
12
18
|
export const markdownPath = (routeBase, slug) => {
|
|
13
19
|
const base = stripSlashes(routeBase);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
import config from "virtual:reallysimpledocs/config";
|
|
3
|
+
import { getMdxPage } from "virtual:reallysimpledocs/mdx-pages";
|
|
3
4
|
import DocsLayout from "../components/DocsLayout.astro";
|
|
4
|
-
import { getPages, renderDoc } from "../lib/docs.js";
|
|
5
|
+
import { getDocHeadings, getPages, renderDoc } from "../lib/docs.js";
|
|
5
6
|
import { routeSlug } from "../lib/paths.js";
|
|
6
7
|
|
|
7
8
|
export function getStaticPaths() {
|
|
@@ -12,9 +13,12 @@ export function getStaticPaths() {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const { page } = Astro.props;
|
|
15
|
-
const
|
|
16
|
+
const isMdx = page.extension === ".mdx";
|
|
17
|
+
const mdxModule = isMdx ? await getMdxPage(page.slug) : null;
|
|
18
|
+
const MdxContent = mdxModule?.Content || mdxModule?.default;
|
|
19
|
+
const doc = isMdx ? { page, headings: getDocHeadings(config, page), html: "" } : await renderDoc(config, page);
|
|
16
20
|
---
|
|
17
21
|
|
|
18
22
|
<DocsLayout page={page} headings={doc.headings}>
|
|
19
|
-
<Fragment set:html={doc.html} />
|
|
23
|
+
{MdxContent ? <MdxContent /> : <Fragment set:html={doc.html} />}
|
|
20
24
|
</DocsLayout>
|
|
@@ -19,18 +19,49 @@ const copyText = async (text) => {
|
|
|
19
19
|
if (!ok) throw new Error("Copy failed");
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const activePanelCode = (root) => root.querySelector(':scope > [role="tabpanel"]:not([hidden]) code');
|
|
23
|
+
|
|
22
24
|
const initCopyCode = (codeBlock) => {
|
|
23
25
|
const codeEl = codeBlock.querySelector(":scope > code");
|
|
24
26
|
if (!codeEl) return;
|
|
25
27
|
|
|
28
|
+
let target = null;
|
|
29
|
+
let frame = codeBlock.closest(".code-group");
|
|
30
|
+
let getCode = frame ? () => activePanelCode(frame) || codeEl : () => codeEl;
|
|
31
|
+
if (frame) target = frame.querySelector(":scope > header");
|
|
32
|
+
|
|
33
|
+
const preview = codeBlock.closest(".preview");
|
|
34
|
+
const previewCodePanel = preview?.querySelector(":scope > [data-code-panel]");
|
|
35
|
+
if (!frame && previewCodePanel?.contains(codeBlock)) {
|
|
36
|
+
frame = preview;
|
|
37
|
+
target = frame.querySelector(":scope > header");
|
|
38
|
+
getCode = () => codeEl;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!frame) {
|
|
42
|
+
frame = codeBlock.parentElement?.classList.contains("code-block") ? codeBlock.parentElement : null;
|
|
43
|
+
if (!frame) {
|
|
44
|
+
frame = document.createElement("div");
|
|
45
|
+
frame.className = "code-block";
|
|
46
|
+
codeBlock.parentNode.insertBefore(frame, codeBlock);
|
|
47
|
+
frame.appendChild(codeBlock);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
target ||= frame.querySelector(":scope > header") || frame;
|
|
52
|
+
|
|
53
|
+
if (target.querySelector(":scope > button[aria-label='Copy code']")) {
|
|
54
|
+
codeBlock.dataset.copyCodeInitialized = true;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
26
58
|
const button = document.createElement("button");
|
|
27
59
|
button.type = "button";
|
|
28
|
-
button.className = "code-block-copy";
|
|
29
60
|
button.setAttribute("aria-label", "Copy code");
|
|
30
61
|
button.innerHTML = Copy;
|
|
31
62
|
|
|
32
63
|
button.addEventListener("click", async () => {
|
|
33
|
-
const text = (
|
|
64
|
+
const text = (getCode()?.textContent || "").replace(/\n$/, "");
|
|
34
65
|
if (!text) return;
|
|
35
66
|
|
|
36
67
|
button.disabled = true;
|
|
@@ -47,8 +78,8 @@ const initCopyCode = (codeBlock) => {
|
|
|
47
78
|
}, 1200);
|
|
48
79
|
});
|
|
49
80
|
|
|
50
|
-
|
|
81
|
+
target.appendChild(button);
|
|
51
82
|
codeBlock.dataset.copyCodeInitialized = true;
|
|
52
83
|
};
|
|
53
84
|
|
|
54
|
-
window.basecoat?.register("copy-code", "pre:not([data-copy-code-initialized])", initCopyCode);
|
|
85
|
+
window.basecoat?.register("copy-code", "pre:has(> code):not([data-copy-code-initialized])", initCopyCode);
|