@wrikka/create-docs 0.1.0 → 0.2.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.
@@ -259,21 +259,23 @@ export function createStaticDataSource(
259
259
  async collections(): Promise<CollectionMeta[]> {
260
260
  return options.collections.map((c) => {
261
261
  const dirs = dirMeta.get(c.meta.id);
262
- if (!dirs?.size) return c.meta;
263
- const sections = [...dirs.entries()]
264
- .map(([dirKey, m]) => ({
265
- id: dirKey.split("/").pop() ?? dirKey,
266
- label: m.title ?? dirKey.split("/").pop() ?? dirKey,
267
- icon: m.icon,
268
- order: m.order,
269
- collapsed: m.collapsed,
270
- }))
271
- .sort(
272
- (a, b) =>
273
- (a.order ?? Number.MAX_SAFE_INTEGER) -
274
- (b.order ?? Number.MAX_SAFE_INTEGER),
275
- );
276
- return { ...c.meta, sections };
262
+ const fromDirs = [...(dirs?.entries() ?? [])].map(([dirKey, m]) => ({
263
+ id: dirKey.split("/").pop() ?? dirKey,
264
+ label: m.title ?? dirKey.split("/").pop() ?? dirKey,
265
+ icon: m.icon,
266
+ order: m.order,
267
+ collapsed: m.collapsed,
268
+ }));
269
+ const dirIds = new Set(fromDirs.map((s) => s.id));
270
+ const fromMeta = (c.meta.sections ?? []).filter(
271
+ (s) => !dirIds.has(s.id) && !dirIds.has(s.label),
272
+ );
273
+ const sections = [...fromDirs, ...fromMeta].sort(
274
+ (a, b) =>
275
+ (a.order ?? Number.MAX_SAFE_INTEGER) -
276
+ (b.order ?? Number.MAX_SAFE_INTEGER),
277
+ );
278
+ return sections.length ? { ...c.meta, sections } : c.meta;
277
279
  });
278
280
  },
279
281
 
@@ -15,10 +15,12 @@ export { createDocsApp, mountDocsApp } from "./app";
15
15
  // Components (for custom pages/layouts)
16
16
  export { AccentPicker } from "./components/AccentPicker";
17
17
  export { ApiPlayground } from "./components/ApiPlayground";
18
+ export { ApiReference } from "./components/ApiReference";
18
19
  export { AskAiDialog } from "./components/AskAiDialog";
19
20
  export { BackToTop } from "./components/BackToTop";
20
21
  export { Breadcrumbs } from "./components/Breadcrumbs";
21
22
  export { CollectionDropdown } from "./components/CollectionDropdown";
23
+ export { ContextMenu, type ContextMenuItem } from "./components/ContextMenu";
22
24
  export { DocMarkdown } from "./components/DocMarkdown";
23
25
  export { DocPrevNext } from "./components/DocPrevNext";
24
26
  export { DocsDropdown } from "./components/DocsDropdown";
@@ -63,6 +65,7 @@ export type {
63
65
  MarkdownConfig,
64
66
  PluginInfo,
65
67
  ShowcaseInfo,
68
+ TranslateConfig,
66
69
  VersionsConfig,
67
70
  } from "./config";
68
71
  export type { DirMeta, StaticSourceOptions } from "./content";
@@ -114,7 +117,10 @@ export { HomePage } from "./pages/HomePage";
114
117
  export { IssuesPage } from "./pages/IssuesPage";
115
118
  export { OAuthCallbackPage } from "./pages/OAuthCallbackPage";
116
119
  export { PluginsPage } from "./pages/PluginsPage";
120
+ export { SearchPage } from "./pages/SearchPage";
117
121
  export { ShowcasePage } from "./pages/ShowcasePage";
122
+ export { TranslatePage } from "./pages/TranslatePage";
123
+ export { officialPlugins } from "./plugins-catalog";
118
124
  export { setupPwa } from "./pwa";
119
125
  export { createDocsRouter } from "./router";
120
126
  export type { SeoInput } from "./seo";
@@ -1,10 +1,10 @@
1
1
  import { useParams } from "@tanstack/solid-router";
2
- import { createMemo, Show } from "solid-js";
2
+ import { createMemo, createSignal, Show } from "solid-js";
3
3
  import { ApiPlayground } from "../components/ApiPlayground";
4
- import { DocMarkdown } from "../components/DocMarkdown";
4
+ import { ApiReference } from "../components/ApiReference";
5
5
  import { DocPrevNext } from "../components/DocPrevNext";
6
6
  import { findApiCollection, useDocs } from "../context";
7
- import type { ApiEndpoint, DocEntry } from "../types";
7
+ import type { DocEntry } from "../types";
8
8
 
9
9
  const METHOD_BADGE: Record<string, string> = {
10
10
  GET: "bg-accent/15 text-accent border-accent/30",
@@ -21,123 +21,25 @@ const STYLE_ICON: Record<string, string> = {
21
21
  rpc: "i-mdi:api",
22
22
  };
23
23
 
24
- function endpointMarkdown(ep: ApiEndpoint, style: string): string {
25
- const lines: string[] = [];
26
-
27
- if (style === "cli") {
28
- if (ep.description) lines.push(ep.description, "");
29
- const cmd = ep.cli
30
- ? [
31
- ep.cli.command,
32
- ep.cli.subcommand,
33
- ...(ep.cli.args?.map((a) =>
34
- a.required ? `<${a.name}>` : `[${a.name}]`,
35
- ) ?? []),
36
- ].join(" ")
37
- : `${ep.method} ${ep.path}`;
38
- lines.push("## Usage", "", "```bash", cmd, "```", "");
39
- if (ep.cli?.args?.length) {
40
- lines.push(
41
- "## Arguments",
42
- "",
43
- "| Name | Required | Description |",
44
- "| --- | --- | --- |",
45
- );
46
- for (const a of ep.cli.args) {
47
- lines.push(
48
- `| \`${a.name}\` | ${a.required ? "yes" : "no"} | ${a.description ?? ""} |`,
49
- );
50
- }
51
- lines.push("");
52
- }
53
- if (ep.requestBody?.example != null) {
54
- lines.push("## Options", "");
55
- lines.push(
56
- "```json",
57
- JSON.stringify(ep.requestBody.example, null, 2),
58
- "```",
59
- "",
60
- );
61
- }
62
- } else if (style === "graphql") {
63
- if (ep.description) lines.push(ep.description, "");
64
- if (ep.graphql?.query) {
65
- lines.push("## Query", "", "```graphql", ep.graphql.query, "```", "");
66
- }
67
- if (ep.graphql?.variables != null) {
68
- lines.push("## Variables", "");
69
- lines.push(
70
- "```json",
71
- JSON.stringify(ep.graphql.variables, null, 2),
72
- "```",
73
- "",
74
- );
75
- }
76
- } else {
77
- if (ep.description) lines.push(ep.description, "");
78
- if (ep.parameters.length > 0) {
79
- lines.push(
80
- "## Parameters",
81
- "",
82
- "| Name | In | Required | Description |",
83
- "| --- | --- | --- | --- |",
84
- );
85
- for (const p of ep.parameters) {
86
- lines.push(
87
- `| \`${p.name}\` | ${p.in} | ${p.required ? "yes" : "no"} | ${p.description ?? ""} |`,
88
- );
89
- }
90
- lines.push("");
91
- }
92
- if (ep.requestBody) {
93
- lines.push("## Request Body", "");
94
- if (ep.requestBody.description)
95
- lines.push(ep.requestBody.description, "");
96
- if (ep.requestBody.example != null) {
97
- lines.push(
98
- "```json",
99
- JSON.stringify(ep.requestBody.example, null, 2),
100
- "```",
101
- "",
102
- );
103
- }
104
- }
105
- }
106
-
107
- const codes = Object.keys(ep.responses);
108
- if (codes.length > 0) {
109
- lines.push(style === "cli" ? "## Exit codes" : "## Responses", "");
110
- for (const code of codes) {
111
- const r = ep.responses[code]!;
112
- lines.push(`### ${code} — ${r.description}`);
113
- if (r.example != null) {
114
- lines.push(
115
- "",
116
- "```json",
117
- JSON.stringify(r.example, null, 2),
118
- "```",
119
- "",
120
- );
121
- }
122
- }
123
- }
124
- return lines.join("\n");
125
- }
126
-
127
- function endpointHeader(ep: ApiEndpoint, style: string) {
128
- if (style === "cli") {
129
- const cmd = ep.cli
130
- ? [ep.cli.command, ep.cli.subcommand].filter(Boolean).join(" ")
131
- : `${ep.method} ${ep.path}`;
132
- return { label: cmd, icon: "i-mdi:console-line" };
133
- }
134
- if (style === "graphql") {
135
- return {
136
- label: ep.graphql?.operation ?? ep.summary ?? "GraphQL",
137
- icon: "i-mdi:graphql",
138
- };
139
- }
140
- return { label: `${ep.method} ${ep.path}`, icon: "" };
24
+ function CopyInline(props: { text: () => string }) {
25
+ const [copied, setCopied] = createSignal(false);
26
+ return (
27
+ <button
28
+ type="button"
29
+ aria-label="Copy"
30
+ onClick={() => {
31
+ navigator.clipboard.writeText(props.text()).catch(() => {});
32
+ setCopied(true);
33
+ setTimeout(() => setCopied(false), 1500);
34
+ }}
35
+ class="w-7 h-7 inline-flex items-center justify-center rounded-md border border-border text-muted hover:text-foreground hover:bg-surface transition-colors cursor-pointer"
36
+ >
37
+ <span
38
+ class={copied() ? "i-mdi:check text-success" : "i-mdi:content-copy"}
39
+ aria-hidden="true"
40
+ />
41
+ </button>
42
+ );
141
43
  }
142
44
 
143
45
  export function ApiEndpointPage() {
@@ -165,10 +67,15 @@ export function ApiEndpointPage() {
165
67
  })),
166
68
  );
167
69
 
168
- const header = () => {
70
+ const cliLabel = () => {
169
71
  const ep = endpoint();
170
- if (!ep) return { label: "", icon: "" };
171
- return endpointHeader(ep, style());
72
+ if (!ep) return "";
73
+ if (style() === "cli") {
74
+ return ep.cli
75
+ ? [ep.cli.command, ep.cli.subcommand].filter(Boolean).join(" ")
76
+ : `${ep.method} ${ep.path}`;
77
+ }
78
+ return ep.graphql?.operation ?? ep.summary ?? "GraphQL";
172
79
  };
173
80
 
174
81
  return (
@@ -180,50 +87,56 @@ export function ApiEndpointPage() {
180
87
  >
181
88
  {(ep) => (
182
89
  <>
183
- <div class="flex flex-wrap items-center gap-2 pb-4 mb-4 border-b border-border">
90
+ <Show when={ep().tag}>
91
+ <div class="text-[11px] uppercase tracking-wide text-muted mb-2">
92
+ {ep().tag}
93
+ </div>
94
+ </Show>
95
+ <Show when={ep().summary}>
96
+ <h1 class="text-2xl font-bold text-foreground mt-0 mb-3">
97
+ {ep().summary}
98
+ </h1>
99
+ </Show>
100
+
101
+ <div class="flex flex-wrap items-center gap-2 p-3 mb-6 rounded-xl border border-border bg-surface/50">
184
102
  <Show when={style() === "rest" || style() === "rpc"}>
185
103
  <span
186
104
  class={`inline-flex items-center px-2.5 py-1 rounded-md border text-xs font-bold ${METHOD_BADGE[ep().method] ?? ""}`}
187
105
  >
188
106
  {ep().method}
189
107
  </span>
190
- <code class="text-sm font-mono text-foreground">
108
+ <code class="text-sm font-mono text-foreground break-all">
109
+ {ep().server ? `${ep().server}` : ""}
191
110
  {ep().path}
192
111
  </code>
112
+ <CopyInline text={() => `${ep().server ?? ""}${ep().path}`} />
193
113
  </Show>
194
114
 
195
115
  <Show when={style() === "graphql" || style() === "cli"}>
196
- <span class="inline-flex items-center gap-1.5 text-sm font-mono text-foreground">
116
+ <span class="inline-flex items-center gap-1.5 text-sm font-mono text-foreground min-w-0">
197
117
  <span
198
- class={`${header().icon} text-primary`}
118
+ class={`${style() === "cli" ? "i-mdi:console-line" : "i-mdi:graphql"} text-primary shrink-0`}
199
119
  aria-hidden="true"
200
120
  />
201
- {header().label}
121
+ <span class="truncate">{cliLabel()}</span>
202
122
  </span>
123
+ <CopyInline text={cliLabel} />
203
124
  </Show>
204
125
 
205
- <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full border border-border bg-surface text-[10px] uppercase tracking-wide font-semibold text-muted">
126
+ <span class="ml-auto inline-flex items-center gap-1 px-2 py-0.5 rounded-full border border-border bg-background text-[10px] uppercase tracking-wide font-semibold text-muted shrink-0">
206
127
  <span
207
128
  class={`${STYLE_ICON[style()] ?? "i-mdi:api"} text-xs`}
208
129
  aria-hidden="true"
209
130
  />
210
131
  {style()}
211
132
  </span>
212
-
213
- <Show when={ep().tag}>
214
- <span class="ml-auto text-[11px] uppercase tracking-wide text-muted">
215
- {ep().tag}
216
- </span>
217
- </Show>
218
133
  </div>
219
134
 
220
- <Show when={ep().summary}>
221
- <h1 class="text-2xl font-bold text-foreground mt-0 mb-4">
222
- {ep().summary}
223
- </h1>
224
- </Show>
135
+ <ApiReference endpoint={ep()} style={style()} />
225
136
 
226
- <DocMarkdown source={endpointMarkdown(ep(), style())} />
137
+ <div class="lg:hidden">
138
+ <ApiPlayground endpoint={ep()} style={style()} />
139
+ </div>
227
140
  </>
228
141
  )}
229
142
  </Show>
@@ -1,4 +1,4 @@
1
- import { useParams } from "@tanstack/solid-router";
1
+ import { Link, useParams } from "@tanstack/solid-router";
2
2
  import { dump as dumpYaml } from "js-yaml";
3
3
  import { createResource, createSignal, Show } from "solid-js";
4
4
  import { AskAiDialog } from "../components/AskAiDialog";
@@ -108,6 +108,15 @@ export function DocPage() {
108
108
  <span class="i-mdi:robot-happy-outline" aria-hidden="true" />
109
109
  Ask AI
110
110
  </button>
111
+ <Show when={config.features?.translate || config.translate}>
112
+ <Link
113
+ to="/translate"
114
+ class="inline-flex items-center gap-1.5 px-2.5 h-8 rounded-md border border-border text-xs text-muted hover:text-foreground hover:bg-surface transition-colors no-underline"
115
+ >
116
+ <span class="i-mdi:translate" aria-hidden="true" />
117
+ Translate
118
+ </Link>
119
+ </Show>
111
120
  <Show when={showFrontmatterToggle()}>
112
121
  <button
113
122
  type="button"
@@ -93,23 +93,32 @@ export function PluginsPage() {
93
93
  const list = plugins() ?? [];
94
94
  const groups = new Map<string, typeof list>();
95
95
  for (const p of list) {
96
- const cat = p.name.startsWith("@")
97
- ? "Packages"
98
- : p.name.includes("adapter")
99
- ? "Adapters"
100
- : p.name.startsWith("MCP") ||
101
- p.name.includes("LLM") ||
102
- p.name.includes("OAuth") ||
103
- p.name.includes("PWA") ||
104
- p.name.includes("SEO") ||
105
- p.name.includes("Search")
106
- ? "Integrations"
107
- : "Core";
96
+ const cat =
97
+ p.category ??
98
+ (p.name.startsWith("@")
99
+ ? "Packages"
100
+ : p.name.includes("adapter")
101
+ ? "Adapters"
102
+ : p.name.startsWith("MCP") ||
103
+ p.name.includes("LLM") ||
104
+ p.name.includes("OAuth") ||
105
+ p.name.includes("PWA") ||
106
+ p.name.includes("SEO") ||
107
+ p.name.includes("Search")
108
+ ? "Integrations"
109
+ : "Core");
108
110
  const g = groups.get(cat) ?? [];
109
111
  g.push(p);
110
112
  groups.set(cat, g);
111
113
  }
112
- const order = ["Core", "Integrations", "Adapters", "Packages"];
114
+ const order = [
115
+ "Core",
116
+ "Adapters",
117
+ "AI",
118
+ "Integrations",
119
+ "Content",
120
+ "Packages",
121
+ ];
113
122
  return [...groups.entries()].sort(([a], [b]) => {
114
123
  const ia = order.indexOf(a);
115
124
  const ib = order.indexOf(b);
@@ -120,6 +129,15 @@ export function PluginsPage() {
120
129
  });
121
130
  };
122
131
 
132
+ const statusBadge = (status?: string) => {
133
+ if (!status || status === "built-in")
134
+ return "bg-success/10 text-success border-success/30";
135
+ if (status === "beta")
136
+ return "bg-warning/10 text-warning border-warning/30";
137
+ if (status === "planned") return "bg-surface text-muted border-border";
138
+ return "bg-primary/10 text-primary border-primary/30";
139
+ };
140
+
123
141
  return (
124
142
  <div class="max-w-5xl mx-auto px-6 py-10 pb-24">
125
143
  <div class="flex items-start gap-3 mb-2">
@@ -203,6 +221,13 @@ export function PluginsPage() {
203
221
  </span>
204
222
  </Show>
205
223
  </div>
224
+ <Show when={p.status}>
225
+ <span
226
+ class={`text-[10px] px-1.5 py-0.5 rounded border shrink-0 ${statusBadge(p.status)}`}
227
+ >
228
+ {p.status}
229
+ </span>
230
+ </Show>
206
231
  <Show when={p.url}>
207
232
  <a
208
233
  href={p.url}