You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b85dc09
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: README.md
+35-1Lines changed: 35 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,12 +39,46 @@ That's a working docs site. See [shelldocs.dev/docs/getting-started/quick-start]
39
39
```
40
40
-**Static site output.**`shelldocs build` produces static HTML ready for GitHub Pages, Vercel, Netlify, Cloudflare, anywhere. Base-href rewrite + SPA 404 fallback included.
41
41
42
+
## Versioned docs
43
+
44
+
Put each version in its own content folder with the same sub-structure (`content/docs/v0.3/...`, `content/docs/v0.2.1/...`; dots in folder names are fine) and register them:
The current version is the one whose `RootUrl` prefixes the path (segment-aware), otherwise the `latest` one. With 2+ versions a `<VersionSelector />` renders under the package selector (and in the mobile drawer; in the `TopNav` layout it sits in the header). Switching versions keeps the same page when it exists, else the current package's root, else the version's first page. Switching packages keeps the version. Inside a version the sidebar shows only that version's tree, prev/next never crosses into another version, search shows the current version plus pages outside every version, and the version folder is left out of the breadcrumb. Everything is server-rendered `<a href>`s plus `shelldocs.js` delegation, so it works on static hosts.
55
+
56
+
> Routes declared as `/docs/{*Path:nonfile}` don't match a URL whose **last** segment has a dot (`/docs/v0.2.1`). Pages below it (`/docs/v0.2.1/introduction`) are fine, and the selectors never link to a bare version root unless it has an `index.md`. If you add one, drop `:nonfile` from the route.
57
+
58
+
## Component previews
59
+
60
+
-**`razor:preview` fences render everything** — several sibling components, HTML wrappers (`<div class="flex gap-2">…</div>`) and text, in order, inside one frame. The source tab shows the whole fence.
-**Attributes a static preview can't evaluate are skipped, not fatal:**`OnClick="HandleClick"`, `@onclick`, `@bind-*`, `@ref`, non-primitive parameter types, unparseable values. Each logs a warning and the component still renders.
63
+
-**Stateful demos from real files.** For demos that need `@code` (dialogs, bound selects, toasts, charts with data), write a `.razor` component, register it, and drop `<DemoPreview Component="ButtonClickDemo" Title="Optional" />` into markdown. The source tab shows `{DemoSourceRoot}/**/ButtonClickDemo.razor`:
The Razor SDK drops `.razor` files from build/publish output, so ship them explicitly. Use a `None` item, because `Content Update` doesn't survive publish:
|[`ShellDocs.Markdown`](src/ShellDocs.Markdown)| Markdig pipeline. Frontmatter parser, `razor:preview` fence extractor, inline Razor tag extractor, per-property type coercion |
49
83
|[`ShellDocs.Core`](src/ShellDocs.Core)| Navigation graph, search index, plain-text extraction. No UI |
50
84
|[`ShellDocs.Tokens`](src/ShellDocs.Tokens)| Design-system CSS variables. shadcn-compatible names for interop with ShellUI and Tailwind-shaped design systems |
- Scoped prev/next (skips out-of-scope neighbours), folder-section lookup by URL, first page under a URL prefix.
265
+
266
+
### Versions
267
+
268
+
`ShellDocsOptions.Versions` (`AddVersion(id, label, rootUrl, description, latest)`) maps each docs version to a content folder URL. `DocsVersionResolver` (singleton, stateless — the current path is always passed in) answers every version question the chrome asks:
269
+
270
+
| Question | Answer |
271
+
|---|---|
272
+
| Current version | Version whose `RootUrl` is a segment-aware prefix of the path (`UrlPath.IsUnder`), longest wins → else `latest` → else `Versions[0]`|
273
+
| Sidebar nodes | Inside a version: that version folder node's `Children` (`FindFolder`). Otherwise `Root.Children`|
274
+
| Version switch href | Same relative page in the target version → current package's root in the target version → target version's first page |
275
+
| Package href |`{version}` token replaced with the current version's `Id`; a versioned root that isn't a page lands on its first page |
276
+
| Prev/next |`GetPrevNext(node, n => InSameScope(n.Url, node.Url))`|
277
+
| Search | Entries in the current version + entries outside every version root |
278
+
| Breadcrumb | Version folder node removed |
279
+
280
+
All answers are computed at render time and emitted as plain `<a href>`, so prerendered/static pages behave identically; dropdown open/close is `shelldocs.js` delegation on `.pkg`/`.ver`.
Then the plain HTML with `data-slot` placeholders is emitted alongside; the DOM ends up interleaved. Details in the [markdown pipeline notes](#markdown-pipeline-internals).
340
359
341
-
**Parameter serialization:** frontmatter values are strings from YAML. `TypeRegistry` inspects each component's `[Parameter]` properties to know the target type and coerces (int, bool, enum, string). Complex types (records, DTOs) need JSON literals in the markdown.
360
+
**Parameter serialization:** attribute values are strings. `SlotRenderer.BuildParameters` matches them to `[Parameter]` properties (case-insensitively) and coerces string, bool, char, numeric primitives and enums, accepting Razor forms: a leading `@`, `@( … )`, `Type.Member` enum values, `A | B` flags, numeric suffixes, `@null`. Anything else is skipped with a logged warning instead of throwing: directive attributes (`@onclick`, `@bind-*`, `@ref`), delegate/`EventCallback` params, unsupported types, bad values, and unknown attributes on components without a `CaptureUnmatchedValues` catch-all.
361
+
362
+
**`razor:preview` fences** are parsed by `PreviewParser` (a hand-rolled, quote- and `@( … )`-aware tag scanner) into `PreviewSlot.Nodes`: component, element and text nodes in source order. `SlotRenderer.RenderNodes` emits them as real render-tree elements and components, so wrappers keep their children under interactive re-renders too. Razor-only constructs (`@code { }`, `@* *@`, `@using` lines) are skipped for rendering but stay in the source view.
363
+
364
+
**`<DemoPreview Component="X" />`** renders registered component `X` inside `PreviewFrame` and reads `{DemoSourceRoot}/**/X.razor` (shallowest match, cached and re-read when the file's write time changes) for the source tab.
0 commit comments