reallysimpledocs 1.0.0-beta.4 → 1.0.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.
Files changed (53) hide show
  1. package/README.md +59 -39
  2. package/package.json +11 -11
  3. package/src/astro/index.js +64 -98
  4. package/src/astro/preview-scripts.js +140 -0
  5. package/src/astro/preview-source.js +150 -0
  6. package/src/astro/rehype-basecoat-tables.js +62 -0
  7. package/src/components/index.js +1 -0
  8. package/src/css/custom.css +197 -58
  9. package/src/css/styles/luma.css +9 -0
  10. package/src/css/styles/lyra.css +9 -0
  11. package/src/css/styles/maia.css +9 -0
  12. package/src/css/styles/mira.css +9 -0
  13. package/src/css/styles/nova.css +9 -0
  14. package/src/css/styles/rhea.css +9 -0
  15. package/src/css/styles/sera.css +9 -0
  16. package/src/css/styles/vega.css +9 -0
  17. package/src/runtime/components/BaseLayout.astro +22 -28
  18. package/src/runtime/components/CommandDialog.astro +52 -35
  19. package/src/runtime/components/DefaultContentHeader.astro +0 -17
  20. package/src/runtime/components/DefaultHead.astro +110 -0
  21. package/src/runtime/components/DefaultHeadContent.astro +2 -0
  22. package/src/runtime/components/DefaultSidebarHeader.astro +1 -1
  23. package/src/runtime/components/DocsLayout.astro +99 -166
  24. package/src/runtime/components/DropdownMenu.astro +1 -1
  25. package/src/runtime/components/ThemeToggle.astro +21 -0
  26. package/src/runtime/components/mdx/Callout.astro +2 -32
  27. package/src/runtime/components/mdx/Card.astro +3 -8
  28. package/src/runtime/components/mdx/CardGrid.astro +1 -1
  29. package/src/runtime/components/mdx/Code.astro +21 -27
  30. package/src/runtime/components/mdx/CodeGroup.astro +114 -24
  31. package/src/runtime/components/mdx/Preview.astro +7 -13
  32. package/src/runtime/components/mdx/Steps.astro +1 -5
  33. package/src/runtime/components/mdx/Tab.astro +6 -0
  34. package/src/runtime/components/mdx/Tabs.astro +7 -7
  35. package/src/runtime/lib/code-meta.js +8 -0
  36. package/src/runtime/lib/docs.js +141 -119
  37. package/src/runtime/lib/highlight.js +1 -14
  38. package/src/runtime/lib/html.js +9 -0
  39. package/src/runtime/lib/macros.js +10 -5
  40. package/src/runtime/lib/markdown-export.js +240 -169
  41. package/src/runtime/pages/doc.astro +1 -3
  42. package/src/runtime/pages/llms-full.txt.ts +2 -2
  43. package/src/runtime/pages/llms.txt.ts +2 -2
  44. package/src/runtime/pages/markdown.ts +2 -2
  45. package/src/runtime/pages/search-index.ts +12 -3
  46. package/CHANGELOG.md +0 -35
  47. package/src/css/overrides.css +0 -1
  48. package/src/css/sources.css +0 -1
  49. package/src/runtime/lib/markdown-exporters/index.js +0 -144
  50. package/src/runtime/lib/mdx-source.js +0 -19
  51. package/src/runtime/lib/preview-source-plugin.js +0 -33
  52. package/src/runtime/scripts/copy-code.js +0 -130
  53. package/src/site/ContentHeader.astro +0 -33
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # ReallySimpleDocs
2
2
 
3
- A simple documentation system for Astro, built with **Basecoat** and **Tailwind CSS**.
3
+ A simple documentation system for Astro, built with Basecoat and Tailwind CSS.
4
4
 
5
- ReallySimpleDocs gives you a shadcn/ui-compatible docs template without a React runtime. It uses Basecoat for the same design language and CSS-variable model, while staying close to plain Astro, Markdown, HTML, CSS, and a small amount of vanilla JavaScript.
5
+ ReallySimpleDocs keeps docs portable: content lives in `docs/`, navigation lives in `docs/docs.json`, and the Astro integration owns the docs UI, search, Markdown exports, and LLM files.
6
6
 
7
- Docs stay portable: content lives in `docs/`, navigation lives in `docs/docs.json`, and the Astro integration owns the docs UI.
7
+ ## Install
8
8
 
9
- ## Add to an Astro site
9
+ Add ReallySimpleDocs to an Astro site:
10
10
 
11
11
  ```bash
12
- npm install reallysimpledocs@beta basecoat-css@beta tailwindcss
12
+ npm install reallysimpledocs basecoat-css@beta tailwindcss
13
13
  ```
14
14
 
15
- Add the integration:
15
+ Configure the integration:
16
16
 
17
17
  ```js
18
18
  // astro.config.mjs
@@ -25,17 +25,16 @@ export default defineConfig({
25
25
  docsDir: "./docs",
26
26
  routeBase: "/docs",
27
27
  style: "vega",
28
- customCss: ["./src/docs.css"],
29
28
  site: {
30
- title: "ReallySimpleDocs",
31
- description: "A simple documentation system for Astro.",
29
+ title: "Acme Docs",
30
+ description: "Documentation for Acme.",
32
31
  },
33
32
  }),
34
33
  ],
35
34
  });
36
35
  ```
37
36
 
38
- Create docs:
37
+ Create your first docs page:
39
38
 
40
39
  ```text
41
40
  docs/
@@ -61,68 +60,89 @@ docs/
61
60
  Welcome to the docs.
62
61
  ```
63
62
 
64
- ReallySimpleDocs injects docs pages, Lunr search, per-page Markdown exports, `llms.txt`, and `llms-full.txt`.
63
+ ## What it provides
65
64
 
66
- Use `.mdx` when a page needs ReallySimpleDocs components. Common ReallySimpleDocs components are available by default, so you do not need to import them in each page:
65
+ - Static docs routes from Markdown and MDX files.
66
+ - Sidebar navigation from `docs/docs.json`.
67
+ - Lunr search with a built-in command dialog.
68
+ - Syntax-highlighted code blocks with copy buttons.
69
+ - `llms.txt`, `llms-full.txt`, and per-page Markdown exports.
70
+ - Basecoat styling with style-specific RSD CSS for docs surfaces.
71
+ - Component slots for additive head content, sidebar chrome, and header controls.
67
72
 
68
- ```mdx
69
- # Button
73
+ ## Customization
70
74
 
71
- <Callout title="MDX works">MDX is optional. Plain Markdown stays the default.</Callout>
72
- ```
75
+ Use `components.ContentHeader` for controls between search and the built-in theme toggle:
73
76
 
74
- ## Use Basecoat on the rest of the site
77
+ ```js
78
+ reallySimpleDocs({
79
+ components: {
80
+ ContentHeader: "./src/components/ContentHeader.astro",
81
+ },
82
+ });
83
+ ```
75
84
 
76
- ReallySimpleDocs styles and scripts its generated docs pages. For custom Astro pages outside the docs route, such as a landing page or app page, use Basecoat directly in your own layout.
85
+ Use `components.Head` to add tags to `<head>`. RSD's built-in metadata, theme setup, Basecoat JavaScript, copy-code behavior, and search behavior still render.
77
86
 
78
- Install stays the same:
87
+ Use `customCss` for project CSS imported after Basecoat and RSD styles:
79
88
 
80
- ```bash
81
- npm install reallysimpledocs@beta basecoat-css@beta tailwindcss
89
+ ```js
90
+ reallySimpleDocs({
91
+ style: "nova",
92
+ customCss: ["./src/docs.css"],
93
+ });
82
94
  ```
83
95
 
84
- Create a site stylesheet:
96
+ Use `css: false` only when you provide the full Tailwind/Basecoat/RSD stylesheet pipeline yourself. RSD exports its own CSS for that case:
85
97
 
86
98
  ```css
87
- /* src/styles/site.css */
88
- @import "tailwindcss";
89
- @import "basecoat-css/base";
90
- @import "basecoat-css/styles/vega";
91
-
92
- @source "../src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}";
99
+ @import "reallysimpledocs/css";
100
+ @import "reallysimpledocs/css/styles/vega";
93
101
  ```
94
102
 
95
- Load that stylesheet and Basecoat's JavaScript in your custom layout:
103
+ ## Search outside docs
104
+
105
+ Render the exported `CommandDialog` on any Astro page where you want docs search:
96
106
 
97
107
  ```astro
98
108
  ---
99
- import "../styles/site.css";
100
- import "basecoat-css/all.min";
109
+ import { CommandDialog } from "reallysimpledocs/components";
101
110
  ---
102
111
 
103
- <slot />
104
- ```
112
+ <CommandDialog />
105
113
 
106
- Use the same Basecoat `style` in your ReallySimpleDocs config and your site stylesheet if you want the docs and custom pages to match.
114
+ <button type="button" class="btn" onclick="window.rsdOpenCommandSearch?.()">
115
+ Search docs
116
+ </button>
117
+ ```
107
118
 
108
- ## Local development (this repo)
119
+ ## Local development
109
120
 
110
121
  ```bash
111
122
  npm install
112
123
  npm run dev
113
124
  ```
114
125
 
126
+ Useful checks before publishing:
127
+
128
+ ```bash
129
+ npm run build
130
+ npm pack --dry-run
131
+ npm run smoke:consumer
132
+ ```
133
+
134
+ `npm run check` runs all three.
135
+
115
136
  ## Documentation
116
137
 
117
- Go to [ReallySimpleDocs.com](https://reallysimpledocs.com).
138
+ Go to [reallysimpledocs.com](https://reallysimpledocs.com).
118
139
 
119
- ## Support the project
140
+ ## Support
120
141
 
121
- - [Contribute code](https://github.com/hunvreus/reallysimpledocs/blob/main/CONTRIBUTING.md)
122
142
  - [Report issues](https://github.com/hunvreus/reallysimpledocs/issues)
123
143
  - [Sponsor me](https://github.com/sponsors/hunvreus)
124
144
  - [Star the project on GitHub](https://github.com/hunvreus/reallysimpledocs)
125
145
 
126
146
  ## License
127
147
 
128
- [MIT](https://github.com/hunvreus/reallysimpledocs/blob/main/LICENSE.md)
148
+ [MIT](LICENSE.md)
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "reallysimpledocs",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.js",
7
7
  "./astro": "./src/astro/index.js",
8
- "./components": "./src/components/index.js"
8
+ "./components": "./src/components/index.js",
9
+ "./css": "./src/css/custom.css",
10
+ "./css/styles/*": "./src/css/styles/*.css",
11
+ "./css/styles/*.css": "./src/css/styles/*.css"
9
12
  },
10
13
  "files": [
11
14
  "src",
12
15
  "README.md",
13
- "CHANGELOG.md",
14
16
  "LICENSE.md"
15
17
  ],
16
18
  "scripts": {
@@ -24,25 +26,23 @@
24
26
  "@astrojs/markdown-remark": "^7.2.0",
25
27
  "@astrojs/mdx": "^6.0.3",
26
28
  "@tailwindcss/vite": "4.1.17",
27
- "astro": "^6.4.6",
28
29
  "github-slugger": "^2.0.0",
29
30
  "lucide-static": "^0.552.0",
30
31
  "lunr": "^2.3.9",
31
32
  "marked": "^16.4.1",
32
- "mdast-util-gfm": "^3.1.0",
33
- "mdast-util-to-markdown": "^2.1.2",
34
- "remark-gfm": "^4.0.1",
35
- "remark-mdx": "^3.1.1",
36
- "remark-parse": "^11.0.0",
33
+ "rehype-parse": "^9.0.1",
34
+ "rehype-stringify": "^10.0.1",
37
35
  "shiki": "^4.1.0",
38
36
  "unified": "^11.0.5"
39
37
  },
40
38
  "peerDependencies": {
41
- "basecoat-css": "^1.0.0-beta.2",
39
+ "astro": "^6.4.6",
40
+ "basecoat-css": "^1.0.0-beta.6",
42
41
  "tailwindcss": ">=4.1.17 <5"
43
42
  },
44
43
  "devDependencies": {
45
- "basecoat-css": "1.0.0-beta.2",
44
+ "astro": "^6.4.6",
45
+ "basecoat-css": "^1.0.0-beta.6",
46
46
  "tailwindcss": "4.1.17"
47
47
  },
48
48
  "overrides": {
@@ -1,10 +1,12 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import { unified as astroMarkdown } from "@astrojs/markdown-remark";
5
4
  import mdx from "@astrojs/mdx";
5
+ import { unified } from "@astrojs/markdown-remark";
6
6
  import tailwindcss from "@tailwindcss/vite";
7
- import { previewSourceRemarkPlugin } from "../runtime/lib/preview-source-plugin.js";
7
+ import { rehypeBasecoatTables } from "./rehype-basecoat-tables.js";
8
+ import { remarkPreviewSource } from "./preview-source.js";
9
+ import { transformPreviewScripts } from "./preview-scripts.js";
8
10
 
9
11
  const basecoatStyles = new Set(["vega", "nova", "maia", "lyra", "mira", "luma", "sera", "rhea"]);
10
12
 
@@ -28,12 +30,7 @@ const normalizeArray = (value) => (Array.isArray(value) ? value : value ? [value
28
30
 
29
31
  const normalizeImportPath = (filePath) => filePath.replaceAll(path.sep, "/");
30
32
 
31
- const normalizeRecord = (value) => (value && typeof value === "object" && !Array.isArray(value) ? value : {});
32
-
33
- const isInsideDir = (dir, file) => {
34
- const relative = path.relative(dir, file);
35
- return relative && !relative.startsWith("..") && !path.isAbsolute(relative);
36
- };
33
+ const normalizeBoolean = (value, fallback = true) => (value === undefined ? fallback : Boolean(value));
37
34
 
38
35
  const shikiConfig = {
39
36
  themes: {
@@ -63,18 +60,6 @@ function collectMdxModules(docsDir) {
63
60
  return modules;
64
61
  }
65
62
 
66
- function componentModuleSource(modules) {
67
- const entries = Object.entries(modules);
68
- const imports = entries
69
- .map(([name, moduleId], index) => `import component${index} from ${JSON.stringify(moduleId)};`)
70
- .join("\n");
71
- return `${imports}
72
- export default {
73
- ${entries.map(([name], index) => ` ${JSON.stringify(name)}: component${index},`).join("\n")}
74
- };
75
- `;
76
- }
77
-
78
63
  export default function reallySimpleDocs(options = {}) {
79
64
  const style = options.style || "vega";
80
65
  if (!basecoatStyles.has(style)) {
@@ -87,9 +72,9 @@ export default function reallySimpleDocs(options = {}) {
87
72
  siteFile: options.siteFile || null,
88
73
  style,
89
74
  customCss: normalizeArray(options.customCss),
75
+ css: normalizeBoolean(options.css),
76
+ bodyAttrs: options.bodyAttrs || {},
90
77
  components: options.components || {},
91
- mdxComponents: normalizeRecord(options.mdxComponents),
92
- markdownSerializers: normalizeRecord(options.markdownSerializers),
93
78
  routeBase: options.routeBase ?? "/docs",
94
79
  assetsBase: options.assetsBase || "/assets",
95
80
  };
@@ -97,57 +82,51 @@ export default function reallySimpleDocs(options = {}) {
97
82
  return {
98
83
  name: "reallysimpledocs",
99
84
  hooks: {
100
- "astro:config:setup": ({ injectRoute, updateConfig, config }) => {
85
+ "astro:config:setup": ({ injectRoute, injectScript, updateConfig, config }) => {
101
86
  const root = fileURLToPath(config.root);
102
87
  const generatedDir = path.join(root, ".astro", "reallysimpledocs");
103
88
  const generatedStyles = path.join(generatedDir, "styles.css");
104
89
  const docsDir = path.resolve(root, normalizedOptions.docsDir);
105
- const resolveUserId = (id) => (id?.startsWith(".") ? path.resolve(root, id) : id);
106
- const markdownSerializers = Object.fromEntries(
107
- Object.entries(normalizedOptions.markdownSerializers).map(([name, moduleId]) => [
108
- name,
109
- normalizeImportPath(resolveUserId(moduleId)),
110
- ]),
111
- );
112
- const mdxComponents = Object.fromEntries(
113
- Object.entries(normalizedOptions.mdxComponents).map(([name, moduleId]) => [
114
- name,
115
- normalizeImportPath(resolveUserId(moduleId)),
116
- ]),
117
- );
118
90
  const virtualConfig = {
119
91
  ...normalizedOptions,
120
92
  root,
121
93
  docsDir,
122
94
  siteFile: normalizedOptions.siteFile ? path.resolve(root, normalizedOptions.siteFile) : null,
123
- mdxComponents,
124
- markdownSerializers,
125
95
  routeBase: normalizeRouteBase(normalizedOptions.routeBase),
126
96
  };
127
- const components = {
128
- ContentHeader:
129
- normalizedOptions.components.ContentHeader || runtimePath("components/DefaultContentHeader.astro"),
130
- SidebarHeader:
131
- normalizedOptions.components.SidebarHeader || runtimePath("components/DefaultSidebarHeader.astro"),
132
- SidebarFooter:
133
- normalizedOptions.components.SidebarFooter || runtimePath("components/DefaultSidebarFooter.astro"),
97
+ const mdxModules = collectMdxModules(docsDir);
98
+ const resolveUserId = (id) => (id?.startsWith(".") ? path.resolve(root, id) : id);
99
+ const componentDefaults = {
100
+ Head: runtimePath("components/DefaultHeadContent.astro"),
101
+ SidebarHeader: runtimePath("components/DefaultSidebarHeader.astro"),
102
+ SidebarFooter: runtimePath("components/DefaultSidebarFooter.astro"),
103
+ ContentHeader: runtimePath("components/DefaultContentHeader.astro"),
134
104
  };
135
- fs.mkdirSync(generatedDir, { recursive: true });
136
- fs.writeFileSync(
137
- generatedStyles,
138
- [
139
- '@import "tailwindcss";',
140
- '@import "basecoat-css/base";',
141
- `@import "basecoat-css/styles/${style}";`,
142
- `@import ${JSON.stringify(cssPath("sources.css"))};`,
143
- `@import ${JSON.stringify(cssPath("custom.css"))};`,
144
- `@import ${JSON.stringify(cssPath("overrides.css"))};`,
145
- ...normalizedOptions.customCss.map((css) => `@import ${JSON.stringify(resolveUserId(css))};`),
146
- `@source ${JSON.stringify(runtimePath("**/*.{astro,js,ts}"))};`,
147
- `@source ${JSON.stringify(path.join(virtualConfig.docsDir, "**/*.{md,mdx}"))};`,
148
- "",
149
- ].join("\n"),
105
+ const components = Object.fromEntries(
106
+ Object.entries(componentDefaults).map(([name, defaultPath]) => [
107
+ name,
108
+ normalizedOptions.components[name] || defaultPath,
109
+ ]),
150
110
  );
111
+ fs.mkdirSync(generatedDir, { recursive: true });
112
+ const styleSource = normalizedOptions.css
113
+ ? [
114
+ '@import "tailwindcss";',
115
+ '@import "basecoat-css/base";',
116
+ `@import "basecoat-css/styles/${style}";`,
117
+ `@import ${JSON.stringify(cssPath("custom.css"))};`,
118
+ `@import ${JSON.stringify(cssPath(`styles/${style}.css`))};`,
119
+ ...normalizedOptions.customCss.map((css) => `@import ${JSON.stringify(resolveUserId(css))};`),
120
+ `@source ${JSON.stringify(path.join(root, "src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"))};`,
121
+ `@source ${JSON.stringify(runtimePath("**/*.{astro,js,ts}"))};`,
122
+ `@source ${JSON.stringify(path.join(virtualConfig.docsDir, "**/*.{md,mdx}"))};`,
123
+ "",
124
+ ].join("\n")
125
+ : "/* ReallySimpleDocs CSS disabled by config. */\n";
126
+ fs.writeFileSync(generatedStyles, styleSource);
127
+ if (normalizedOptions.css) {
128
+ injectScript("page-ssr", 'import "virtual:reallysimpledocs/styles.css";');
129
+ }
151
130
 
152
131
  injectRoute({
153
132
  pattern: routePattern(normalizedOptions.routeBase),
@@ -178,8 +157,9 @@ export default function reallySimpleDocs(options = {}) {
178
157
  }),
179
158
  ],
180
159
  markdown: {
181
- processor: astroMarkdown({
182
- remarkPlugins: [previewSourceRemarkPlugin],
160
+ processor: unified({
161
+ remarkPlugins: [[remarkPreviewSource, { docsDir }]],
162
+ rehypePlugins: [rehypeBasecoatTables],
183
163
  }),
184
164
  shikiConfig,
185
165
  },
@@ -190,42 +170,30 @@ export default function reallySimpleDocs(options = {}) {
190
170
  plugins: [
191
171
  tailwindcss(),
192
172
  {
193
- name: "reallysimpledocs-virtual-modules",
194
- configureServer(server) {
195
- const invalidateMdxPages = () => {
196
- const module = server.moduleGraph.getModuleById("\0virtual:reallysimpledocs/mdx-pages");
197
- if (module) server.moduleGraph.invalidateModule(module);
198
- server.ws.send({ type: "full-reload" });
199
- };
200
-
201
- server.watcher.add(path.join(docsDir, "**/*.mdx"));
202
- server.watcher.on("add", (file) => {
203
- if (file.endsWith(".mdx") && isInsideDir(docsDir, file)) invalidateMdxPages();
204
- });
205
- server.watcher.on("unlink", (file) => {
206
- if (file.endsWith(".mdx") && isInsideDir(docsDir, file)) invalidateMdxPages();
207
- });
173
+ name: "reallysimpledocs-preview-scripts",
174
+ enforce: "pre",
175
+ transform(code, id) {
176
+ if (!id.endsWith(".mdx") || !isInsideDir(id, docsDir)) return null;
177
+ const transformed = transformPreviewScripts(code);
178
+ return transformed === code ? null : { code: transformed, map: null };
208
179
  },
180
+ },
181
+ {
182
+ name: "reallysimpledocs-virtual-modules",
209
183
  resolveId(id) {
210
184
  if (id === "virtual:reallysimpledocs/config") return "\0virtual:reallysimpledocs/config";
211
185
  if (id === "virtual:reallysimpledocs/mdx-pages") return "\0virtual:reallysimpledocs/mdx-pages";
212
- if (id === "virtual:reallysimpledocs/mdx-components") return "\0virtual:reallysimpledocs/mdx-components";
213
186
  if (id === "virtual:reallysimpledocs/styles.css") return generatedStyles;
214
- if (id === "virtual:reallysimpledocs/components/SidebarHeader") {
215
- return "\0virtual:reallysimpledocs/components/SidebarHeader";
216
- }
217
- if (id === "virtual:reallysimpledocs/components/SidebarFooter") {
218
- return "\0virtual:reallysimpledocs/components/SidebarFooter";
219
- }
220
- if (id === "virtual:reallysimpledocs/components/ContentHeader") {
221
- return "\0virtual:reallysimpledocs/components/ContentHeader";
187
+ for (const name of Object.keys(components)) {
188
+ if (id === `virtual:reallysimpledocs/components/${name}`) {
189
+ return `\0virtual:reallysimpledocs/components/${name}`;
190
+ }
222
191
  }
223
192
  return null;
224
193
  },
225
194
  load(id) {
226
195
  if (id === "\0virtual:reallysimpledocs/config") return `export default ${JSON.stringify(virtualConfig)};`;
227
196
  if (id === "\0virtual:reallysimpledocs/mdx-pages") {
228
- const mdxModules = collectMdxModules(docsDir);
229
197
  const imports = Object.entries(mdxModules)
230
198
  .map(([slug, filePath], index) => `import * as mdx${index} from ${JSON.stringify(filePath)};`)
231
199
  .join("\n");
@@ -241,17 +209,10 @@ export async function getMdxPage(slug) {
241
209
  }
242
210
  `;
243
211
  }
244
- if (id === "\0virtual:reallysimpledocs/mdx-components") {
245
- return componentModuleSource(mdxComponents);
246
- }
247
- if (id === "\0virtual:reallysimpledocs/components/SidebarHeader") {
248
- return `export { default } from ${JSON.stringify(resolveUserId(components.SidebarHeader))};`;
249
- }
250
- if (id === "\0virtual:reallysimpledocs/components/SidebarFooter") {
251
- return `export { default } from ${JSON.stringify(resolveUserId(components.SidebarFooter))};`;
252
- }
253
- if (id === "\0virtual:reallysimpledocs/components/ContentHeader") {
254
- return `export { default } from ${JSON.stringify(resolveUserId(components.ContentHeader))};`;
212
+ for (const [name, componentPath] of Object.entries(components)) {
213
+ if (id === `\0virtual:reallysimpledocs/components/${name}`) {
214
+ return `export { default } from ${JSON.stringify(resolveUserId(componentPath))};`;
215
+ }
255
216
  }
256
217
  return null;
257
218
  },
@@ -263,3 +224,8 @@ export async function getMdxPage(slug) {
263
224
  },
264
225
  };
265
226
  }
227
+
228
+ function isInsideDir(filePath, dir) {
229
+ const relative = path.relative(dir, filePath);
230
+ return relative && !relative.startsWith("..") && !path.isAbsolute(relative);
231
+ }
@@ -0,0 +1,140 @@
1
+ const previewTag = "Preview";
2
+ const scriptTagPattern = /<script(?<attrs>[^>]*)>(?<content>[\s\S]*?)<\/script>/g;
3
+
4
+ export function transformPreviewScripts(source) {
5
+ let output = "";
6
+ let cursor = 0;
7
+
8
+ while (cursor < source.length) {
9
+ const open = findNextTag(source, previewTag, cursor);
10
+ if (!open) {
11
+ output += source.slice(cursor);
12
+ break;
13
+ }
14
+
15
+ const close = findMatchingCloseTag(source, open);
16
+ if (!close) {
17
+ output += source.slice(cursor);
18
+ break;
19
+ }
20
+
21
+ const body = source.slice(open.end, close.start);
22
+ const rewrittenBody = transformPreviewScriptBody(body);
23
+
24
+ output += source.slice(cursor, open.start);
25
+ if (rewrittenBody === body) {
26
+ output += source.slice(open.start, close.end);
27
+ } else {
28
+ output += addPreviewCodeAttribute(source.slice(open.start, open.end), body);
29
+ output += rewrittenBody;
30
+ output += source.slice(close.start, close.end);
31
+ }
32
+
33
+ cursor = close.end;
34
+ }
35
+
36
+ return output;
37
+ }
38
+
39
+ function transformPreviewScriptBody(body) {
40
+ return body.replace(scriptTagPattern, (match, attrs = "", content = "") => {
41
+ if (/\ssrc\s*=/.test(attrs) || /(?:^|\s)is:inline(?:\s|=|$)/.test(attrs)) return match;
42
+ return `<script${attrs} is:inline>{${JSON.stringify(content)}}</script>`;
43
+ });
44
+ }
45
+
46
+ function addPreviewCodeAttribute(openTag, body) {
47
+ if (/(?:^|\s)code(?:\s|=|>)/.test(openTag)) return openTag;
48
+ return `${openTag.slice(0, -1)} code={${JSON.stringify(body.trim())}}>`;
49
+ }
50
+
51
+ function findMatchingCloseTag(source, open) {
52
+ if (open.selfClosing) return null;
53
+ let depth = 1;
54
+ let cursor = open.end;
55
+
56
+ while (cursor < source.length) {
57
+ const tag = findNextTag(source, previewTag, cursor);
58
+ if (!tag) return null;
59
+ if (tag.closing) depth -= 1;
60
+ else if (!tag.selfClosing) depth += 1;
61
+ if (depth === 0) return tag;
62
+ cursor = tag.end;
63
+ }
64
+
65
+ return null;
66
+ }
67
+
68
+ function findNextTag(source, name, fromIndex) {
69
+ let cursor = fromIndex;
70
+ while (cursor < source.length) {
71
+ const start = source.indexOf("<", cursor);
72
+ if (start === -1) return null;
73
+ if (source.startsWith("<!--", start)) {
74
+ const commentEnd = source.indexOf("-->", start + 4);
75
+ cursor = commentEnd === -1 ? source.length : commentEnd + 3;
76
+ continue;
77
+ }
78
+
79
+ const tag = readTag(source, start);
80
+ if (!tag) {
81
+ cursor = start + 1;
82
+ continue;
83
+ }
84
+ if (tag.name === name) return tag;
85
+ if (!tag.closing && !tag.selfClosing && tag.name.toLowerCase() === "script") {
86
+ const scriptEnd = source.toLowerCase().indexOf("</script>", tag.end);
87
+ cursor = scriptEnd === -1 ? source.length : scriptEnd + "</script>".length;
88
+ continue;
89
+ }
90
+ cursor = tag.end;
91
+ }
92
+ return null;
93
+ }
94
+
95
+ function readTag(source, start) {
96
+ let cursor = start + 1;
97
+ let closing = false;
98
+
99
+ if (source[cursor] === "/") {
100
+ closing = true;
101
+ cursor += 1;
102
+ }
103
+
104
+ const nameStart = cursor;
105
+ while (/[$A-Z_a-z0-9-]/.test(source[cursor] || "")) cursor += 1;
106
+ const name = source.slice(nameStart, cursor);
107
+ if (!name) return null;
108
+
109
+ let quote = "";
110
+ while (cursor < source.length) {
111
+ const char = source[cursor];
112
+ if (quote) {
113
+ if (char === "\\" && source[cursor + 1]) {
114
+ cursor += 2;
115
+ continue;
116
+ }
117
+ if (char === quote) quote = "";
118
+ cursor += 1;
119
+ continue;
120
+ }
121
+ if (char === '"' || char === "'" || char === "`") {
122
+ quote = char;
123
+ cursor += 1;
124
+ continue;
125
+ }
126
+ if (char === ">") {
127
+ const beforeEnd = source.slice(start, cursor).trimEnd();
128
+ return {
129
+ start,
130
+ end: cursor + 1,
131
+ name,
132
+ closing,
133
+ selfClosing: beforeEnd.endsWith("/"),
134
+ };
135
+ }
136
+ cursor += 1;
137
+ }
138
+
139
+ return null;
140
+ }