reallysimpledocs 1.0.0-beta.3 → 1.0.0-beta.5

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 (43) hide show
  1. package/README.md +33 -31
  2. package/package.json +10 -5
  3. package/src/astro/index.js +46 -43
  4. package/src/astro/preview-source.js +90 -0
  5. package/src/components/index.js +1 -0
  6. package/src/css/custom.css +259 -29
  7. package/src/css/styles/luma.css +11 -0
  8. package/src/css/styles/lyra.css +11 -0
  9. package/src/css/styles/maia.css +11 -0
  10. package/src/css/styles/mira.css +11 -0
  11. package/src/css/styles/nova.css +11 -0
  12. package/src/css/styles/rhea.css +11 -0
  13. package/src/css/styles/sera.css +11 -0
  14. package/src/css/styles/vega.css +11 -0
  15. package/src/runtime/components/BaseLayout.astro +20 -27
  16. package/src/runtime/components/CommandDialog.astro +29 -14
  17. package/src/runtime/components/DefaultContentHeader.astro +2 -0
  18. package/src/runtime/components/DefaultHead.astro +120 -0
  19. package/src/runtime/components/DefaultHeadContent.astro +2 -0
  20. package/src/runtime/components/DefaultSidebarFooter.astro +2 -0
  21. package/src/runtime/components/DefaultSidebarHeader.astro +1 -1
  22. package/src/runtime/components/DocsLayout.astro +90 -122
  23. package/src/runtime/components/DropdownMenu.astro +1 -1
  24. package/src/runtime/components/Sidebar.astro +4 -0
  25. package/src/runtime/components/ThemeToggle.astro +21 -0
  26. package/src/runtime/components/mdx/Callout.astro +2 -9
  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 +2 -16
  30. package/src/runtime/components/mdx/CodeGroup.astro +7 -11
  31. package/src/runtime/components/mdx/Preview.astro +7 -12
  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/docs.js +122 -16
  36. package/src/runtime/lib/highlight.js +1 -14
  37. package/src/runtime/lib/macros.js +11 -2
  38. package/src/runtime/lib/markdown-export.js +163 -0
  39. package/src/runtime/lib/site.js +0 -1
  40. package/src/runtime/pages/search-index.ts +10 -1
  41. package/src/css/overrides.css +0 -1
  42. package/src/css/sources.css +0 -1
  43. package/src/runtime/scripts/copy-code.js +0 -109
package/README.md CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  A simple documentation system for Astro, built with **Basecoat** and **Tailwind CSS**.
4
4
 
5
- reallysimpledocs keeps docs portable: content lives in `docs/`, navigation lives in `docs/docs.json`, and the Astro integration owns the docs UI.
5
+ ReallySimpleDocs keeps docs portable: content lives in `docs/`, navigation lives in `docs/docs.json`, and the Astro integration owns the docs UI.
6
6
 
7
7
  ## Add to an Astro site
8
8
 
9
+ ReallySimpleDocs is an Astro integration. Add it to an existing Astro site, or create one first with `npm create astro@latest`.
10
+
9
11
  ```bash
10
- npm install reallysimpledocs@beta basecoat-css@beta tailwindcss
12
+ npm install reallysimpledocs basecoat-css tailwindcss
11
13
  ```
12
14
 
13
15
  Add the integration:
@@ -59,49 +61,41 @@ docs/
59
61
  Welcome to the docs.
60
62
  ```
61
63
 
62
- reallysimpledocs injects docs pages, Lunr search, per-page Markdown exports, `llms.txt`, and `llms-full.txt`.
64
+ ReallySimpleDocs injects docs pages, Lunr search, per-page Markdown exports, `llms.txt`, and `llms-full.txt`.
63
65
 
64
- 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:
66
+ Use component overrides for layout chrome such as sidebar branding, extra head tags, or header controls between search and the built-in theme toggle:
65
67
 
66
- ```mdx
67
- # Button
68
-
69
- <Callout title="MDX works">MDX is optional. Plain Markdown stays the default.</Callout>
68
+ ```js
69
+ reallySimpleDocs({
70
+ components: {
71
+ ContentHeader: "./src/components/ContentHeader.astro",
72
+ },
73
+ });
70
74
  ```
71
75
 
72
- ## Use Basecoat on the rest of the site
76
+ For example, put only the GitHub button in `ContentHeader`; the theme toggle stays built in.
73
77
 
74
- reallysimpledocs styles and scripts its generated docs pages. For custom Astro pages outside the docs route, use Basecoat directly in your own layout.
78
+ Use `.mdx` when a page needs ReallySimpleDocs components. Common components are available by default, so you do not need to import them in each page:
75
79
 
76
- Install stays the same:
80
+ ```mdx
81
+ # Button
77
82
 
78
- ```bash
79
- npm install reallysimpledocs@beta basecoat-css@beta tailwindcss
83
+ <Callout title="MDX works">MDX is optional. Plain Markdown stays the default.</Callout>
80
84
  ```
81
85
 
82
- Create a site stylesheet:
83
-
84
- ```css
85
- /* src/styles/site.css */
86
- @import "tailwindcss";
87
- @import "basecoat-css/base";
88
- @import "basecoat-css/styles/vega";
86
+ ## Use Basecoat outside the docs
89
87
 
90
- @source "../src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}";
91
- ```
88
+ By default, ReallySimpleDocs manages one Tailwind/Basecoat stylesheet for the app. It scans `src/`, `docs/`, and the ReallySimpleDocs runtime, so Basecoat and Tailwind classes used by custom Astro pages outside the docs route are included in the same CSS build.
92
89
 
93
- Load that stylesheet and Basecoat's JavaScript in your custom layout:
90
+ Disable managed CSS only when you want to provide the full stylesheet pipeline yourself:
94
91
 
95
- ```astro
96
- ---
97
- import "../styles/site.css";
98
- import "basecoat-css/all.min";
99
- ---
100
-
101
- <slot />
92
+ ```js
93
+ reallySimpleDocs({
94
+ css: false,
95
+ });
102
96
  ```
103
97
 
104
- Use the same Basecoat `style` in your reallysimpledocs config and your site stylesheet if you want the docs and custom pages to match.
98
+ Use `css: false` when you bring your own Tailwind/Basecoat/RSD stylesheet. ReallySimpleDocs still provides its managed JavaScript.
105
99
 
106
100
  ## Local development (this repo)
107
101
 
@@ -110,6 +104,14 @@ npm install
110
104
  npm run dev
111
105
  ```
112
106
 
107
+ Useful checks before publishing changes:
108
+
109
+ ```bash
110
+ npm run build
111
+ npm pack --dry-run
112
+ npm run smoke:consumer
113
+ ```
114
+
113
115
  ## Documentation
114
116
 
115
117
  Go to [ReallySimpleDocs.com](https://reallysimpledocs.com).
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "reallysimpledocs",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
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",
@@ -20,9 +23,9 @@
20
23
  "check": "npm run build && npm pack --dry-run && npm run smoke:consumer"
21
24
  },
22
25
  "dependencies": {
26
+ "@astrojs/markdown-remark": "^7.2.0",
23
27
  "@astrojs/mdx": "^6.0.3",
24
28
  "@tailwindcss/vite": "4.1.17",
25
- "astro": "^6.4.6",
26
29
  "github-slugger": "^2.0.0",
27
30
  "lucide-static": "^0.552.0",
28
31
  "lunr": "^2.3.9",
@@ -30,11 +33,13 @@
30
33
  "shiki": "^4.1.0"
31
34
  },
32
35
  "peerDependencies": {
33
- "basecoat-css": "^1.0.0-beta.2",
36
+ "astro": "^6.4.6",
37
+ "basecoat-css": "^1.0.0-beta.5",
34
38
  "tailwindcss": ">=4.1.17 <5"
35
39
  },
36
40
  "devDependencies": {
37
- "basecoat-css": "1.0.0-beta.2",
41
+ "astro": "^6.4.6",
42
+ "basecoat-css": "^1.0.0-beta.5",
38
43
  "tailwindcss": "4.1.17"
39
44
  },
40
45
  "overrides": {
@@ -2,7 +2,9 @@ import { fileURLToPath } from "node:url";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import mdx from "@astrojs/mdx";
5
+ import { unified } from "@astrojs/markdown-remark";
5
6
  import tailwindcss from "@tailwindcss/vite";
7
+ import { remarkPreviewSource } from "./preview-source.js";
6
8
 
7
9
  const basecoatStyles = new Set(["vega", "nova", "maia", "lyra", "mira", "luma", "sera", "rhea"]);
8
10
 
@@ -26,10 +28,7 @@ const normalizeArray = (value) => (Array.isArray(value) ? value : value ? [value
26
28
 
27
29
  const normalizeImportPath = (filePath) => filePath.replaceAll(path.sep, "/");
28
30
 
29
- const isInsideDir = (dir, file) => {
30
- const relative = path.relative(dir, file);
31
- return relative && !relative.startsWith("..") && !path.isAbsolute(relative);
32
- };
31
+ const normalizeBoolean = (value, fallback = true) => (value === undefined ? fallback : Boolean(value));
33
32
 
34
33
  const shikiConfig = {
35
34
  themes: {
@@ -71,6 +70,7 @@ export default function reallySimpleDocs(options = {}) {
71
70
  siteFile: options.siteFile || null,
72
71
  style,
73
72
  customCss: normalizeArray(options.customCss),
73
+ css: normalizeBoolean(options.css),
74
74
  components: options.components || {},
75
75
  routeBase: options.routeBase ?? "/docs",
76
76
  assetsBase: options.assetsBase || "/assets",
@@ -79,7 +79,7 @@ export default function reallySimpleDocs(options = {}) {
79
79
  return {
80
80
  name: "reallysimpledocs",
81
81
  hooks: {
82
- "astro:config:setup": ({ injectRoute, updateConfig, config }) => {
82
+ "astro:config:setup": ({ injectRoute, injectScript, updateConfig, config }) => {
83
83
  const root = fileURLToPath(config.root);
84
84
  const generatedDir = path.join(root, ".astro", "reallysimpledocs");
85
85
  const generatedStyles = path.join(generatedDir, "styles.css");
@@ -91,27 +91,39 @@ export default function reallySimpleDocs(options = {}) {
91
91
  siteFile: normalizedOptions.siteFile ? path.resolve(root, normalizedOptions.siteFile) : null,
92
92
  routeBase: normalizeRouteBase(normalizedOptions.routeBase),
93
93
  };
94
+ const mdxModules = collectMdxModules(docsDir);
94
95
  const resolveUserId = (id) => (id?.startsWith(".") ? path.resolve(root, id) : id);
95
- const components = {
96
- SidebarHeader:
97
- normalizedOptions.components.SidebarHeader || runtimePath("components/DefaultSidebarHeader.astro"),
96
+ const componentDefaults = {
97
+ Head: runtimePath("components/DefaultHeadContent.astro"),
98
+ SidebarHeader: runtimePath("components/DefaultSidebarHeader.astro"),
99
+ SidebarFooter: runtimePath("components/DefaultSidebarFooter.astro"),
100
+ ContentHeader: runtimePath("components/DefaultContentHeader.astro"),
98
101
  };
99
- fs.mkdirSync(generatedDir, { recursive: true });
100
- fs.writeFileSync(
101
- generatedStyles,
102
- [
103
- '@import "tailwindcss";',
104
- '@import "basecoat-css/base";',
105
- `@import "basecoat-css/styles/${style}";`,
106
- `@import ${JSON.stringify(cssPath("sources.css"))};`,
107
- `@import ${JSON.stringify(cssPath("custom.css"))};`,
108
- `@import ${JSON.stringify(cssPath("overrides.css"))};`,
109
- ...normalizedOptions.customCss.map((css) => `@import ${JSON.stringify(resolveUserId(css))};`),
110
- `@source ${JSON.stringify(runtimePath("**/*.{astro,js,ts}"))};`,
111
- `@source ${JSON.stringify(path.join(virtualConfig.docsDir, "**/*.{md,mdx}"))};`,
112
- "",
113
- ].join("\n"),
102
+ const components = Object.fromEntries(
103
+ Object.entries(componentDefaults).map(([name, defaultPath]) => [
104
+ name,
105
+ normalizedOptions.components[name] || defaultPath,
106
+ ]),
114
107
  );
108
+ fs.mkdirSync(generatedDir, { recursive: true });
109
+ const styleSource = normalizedOptions.css
110
+ ? [
111
+ '@import "tailwindcss";',
112
+ '@import "basecoat-css/base";',
113
+ `@import "basecoat-css/styles/${style}";`,
114
+ `@import ${JSON.stringify(cssPath("custom.css"))};`,
115
+ `@import ${JSON.stringify(cssPath(`styles/${style}.css`))};`,
116
+ ...normalizedOptions.customCss.map((css) => `@import ${JSON.stringify(resolveUserId(css))};`),
117
+ `@source ${JSON.stringify(path.join(root, "src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"))};`,
118
+ `@source ${JSON.stringify(runtimePath("**/*.{astro,js,ts}"))};`,
119
+ `@source ${JSON.stringify(path.join(virtualConfig.docsDir, "**/*.{md,mdx}"))};`,
120
+ "",
121
+ ].join("\n")
122
+ : "/* ReallySimpleDocs CSS disabled by config. */\n";
123
+ fs.writeFileSync(generatedStyles, styleSource);
124
+ if (normalizedOptions.css) {
125
+ injectScript("page-ssr", 'import "virtual:reallysimpledocs/styles.css";');
126
+ }
115
127
 
116
128
  injectRoute({
117
129
  pattern: routePattern(normalizedOptions.routeBase),
@@ -142,6 +154,9 @@ export default function reallySimpleDocs(options = {}) {
142
154
  }),
143
155
  ],
144
156
  markdown: {
157
+ processor: unified({
158
+ remarkPlugins: [[remarkPreviewSource, { docsDir }]],
159
+ }),
145
160
  shikiConfig,
146
161
  },
147
162
  vite: {
@@ -152,34 +167,20 @@ export default function reallySimpleDocs(options = {}) {
152
167
  tailwindcss(),
153
168
  {
154
169
  name: "reallysimpledocs-virtual-modules",
155
- configureServer(server) {
156
- const invalidateMdxPages = () => {
157
- const module = server.moduleGraph.getModuleById("\0virtual:reallysimpledocs/mdx-pages");
158
- if (module) server.moduleGraph.invalidateModule(module);
159
- server.ws.send({ type: "full-reload" });
160
- };
161
-
162
- server.watcher.add(path.join(docsDir, "**/*.mdx"));
163
- server.watcher.on("add", (file) => {
164
- if (file.endsWith(".mdx") && isInsideDir(docsDir, file)) invalidateMdxPages();
165
- });
166
- server.watcher.on("unlink", (file) => {
167
- if (file.endsWith(".mdx") && isInsideDir(docsDir, file)) invalidateMdxPages();
168
- });
169
- },
170
170
  resolveId(id) {
171
171
  if (id === "virtual:reallysimpledocs/config") return "\0virtual:reallysimpledocs/config";
172
172
  if (id === "virtual:reallysimpledocs/mdx-pages") return "\0virtual:reallysimpledocs/mdx-pages";
173
173
  if (id === "virtual:reallysimpledocs/styles.css") return generatedStyles;
174
- if (id === "virtual:reallysimpledocs/components/SidebarHeader") {
175
- return "\0virtual:reallysimpledocs/components/SidebarHeader";
174
+ for (const name of Object.keys(components)) {
175
+ if (id === `virtual:reallysimpledocs/components/${name}`) {
176
+ return `\0virtual:reallysimpledocs/components/${name}`;
177
+ }
176
178
  }
177
179
  return null;
178
180
  },
179
181
  load(id) {
180
182
  if (id === "\0virtual:reallysimpledocs/config") return `export default ${JSON.stringify(virtualConfig)};`;
181
183
  if (id === "\0virtual:reallysimpledocs/mdx-pages") {
182
- const mdxModules = collectMdxModules(docsDir);
183
184
  const imports = Object.entries(mdxModules)
184
185
  .map(([slug, filePath], index) => `import * as mdx${index} from ${JSON.stringify(filePath)};`)
185
186
  .join("\n");
@@ -195,8 +196,10 @@ export async function getMdxPage(slug) {
195
196
  }
196
197
  `;
197
198
  }
198
- if (id === "\0virtual:reallysimpledocs/components/SidebarHeader") {
199
- return `export { default } from ${JSON.stringify(resolveUserId(components.SidebarHeader))};`;
199
+ for (const [name, componentPath] of Object.entries(components)) {
200
+ if (id === `\0virtual:reallysimpledocs/components/${name}`) {
201
+ return `export { default } from ${JSON.stringify(resolveUserId(componentPath))};`;
202
+ }
200
203
  }
201
204
  return null;
202
205
  },
@@ -0,0 +1,90 @@
1
+ import { toMarkdown } from "mdast-util-to-markdown";
2
+ import { mdxToMarkdown } from "mdast-util-mdx";
3
+
4
+ export function remarkPreviewSource({ docsDir } = {}) {
5
+ const normalizedDocsDir = docsDir ? normalizePath(docsDir).replace(/\/$/, "") : "";
6
+
7
+ return (tree, file) => {
8
+ if (normalizedDocsDir && !isDocsFile(file?.path, normalizedDocsDir)) return;
9
+
10
+ visit(tree, (node) => {
11
+ if (node?.type !== "mdxJsxFlowElement") return;
12
+ if (node.name === "Preview") addPreviewSource(node);
13
+ if (node.name === "Tabs") addTabsMetadata(node);
14
+ });
15
+ };
16
+ }
17
+
18
+ function isDocsFile(filePath, docsDir) {
19
+ const normalized = normalizePath(filePath || "");
20
+ return normalized === docsDir || normalized.startsWith(`${docsDir}/`);
21
+ }
22
+
23
+ function normalizePath(value) {
24
+ return String(value || "").replaceAll("\\", "/");
25
+ }
26
+
27
+ function addPreviewSource(node) {
28
+ node.attributes ||= [];
29
+ if (hasAttr(node, "code")) return;
30
+
31
+ const source = toMarkdown({ type: "root", children: node.children || [] }, { extensions: [mdxToMarkdown()] }).trim();
32
+ node.attributes.push({ type: "mdxJsxAttribute", name: "code", value: source });
33
+ }
34
+
35
+ function addTabsMetadata(node) {
36
+ node.attributes ||= [];
37
+ if (hasAttr(node, "tabs")) return;
38
+
39
+ const tabNodes = (node.children || []).filter((child) => child?.type === "mdxJsxFlowElement" && child.name === "Tab");
40
+ if (!tabNodes.length) return;
41
+
42
+ const seen = new Map();
43
+ const tabs = tabNodes.map((tab, index) => {
44
+ tab.attributes ||= [];
45
+ const title = getAttr(tab, "title") || `Tab ${index + 1}`;
46
+ const value = uniqueValue(slugify(title) || `tab-${index + 1}`, seen);
47
+ if (!hasAttr(tab, "slot")) {
48
+ tab.attributes.push({ type: "mdxJsxAttribute", name: "slot", value });
49
+ }
50
+ return {
51
+ label: title,
52
+ slot: value,
53
+ };
54
+ });
55
+
56
+ node.attributes.push({
57
+ type: "mdxJsxAttribute",
58
+ name: "tabs",
59
+ value: JSON.stringify(tabs),
60
+ });
61
+ }
62
+
63
+ function visit(node, visitor) {
64
+ visitor(node);
65
+ if (!Array.isArray(node?.children)) return;
66
+ for (const child of node.children) visit(child, visitor);
67
+ }
68
+
69
+ function getAttr(node, name) {
70
+ const attr = (node.attributes || []).find((item) => item.type === "mdxJsxAttribute" && item.name === name);
71
+ return typeof attr?.value === "string" ? attr.value : "";
72
+ }
73
+
74
+ function hasAttr(node, name) {
75
+ return (node.attributes || []).some((item) => item.type === "mdxJsxAttribute" && item.name === name);
76
+ }
77
+
78
+ function slugify(value) {
79
+ return String(value || "")
80
+ .trim()
81
+ .toLowerCase()
82
+ .replace(/[^a-z0-9]+/g, "-")
83
+ .replace(/^-+|-+$/g, "");
84
+ }
85
+
86
+ function uniqueValue(value, seen) {
87
+ const count = seen.get(value) || 0;
88
+ seen.set(value, count + 1);
89
+ return count ? `${value}-${count + 1}` : value;
90
+ }
@@ -10,4 +10,5 @@ export { default as CodeGroup } from "../runtime/components/mdx/CodeGroup.astro"
10
10
  export { default as Preview } from "../runtime/components/mdx/Preview.astro";
11
11
  export { default as Step } from "../runtime/components/mdx/Step.astro";
12
12
  export { default as Steps } from "../runtime/components/mdx/Steps.astro";
13
+ export { default as Tab } from "../runtime/components/mdx/Tab.astro";
13
14
  export { default as Tabs } from "../runtime/components/mdx/Tabs.astro";