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 2159e36
Browse filesBrowse the repository at this point in the historyBrowse files
docs: generic components, redirects and the page title option
CHANGELOG Unreleased entries; README sections on redirects, generic components and RenderPageTitle; ARCHITECTURE on DocsRedirects, the middleware, generic closing and the build steps; installation and markdown-syntax pages, with a generic ValueBadge example component.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,17 +8,22 @@ All notable changes to ShellDocs land here. Format follows [Keep a Changelog](ht
8
8
9
9
-**Preview layout.**`razor:preview stretch` (fence info string), or `Layout="stretch"` on `<DemoPreview>` / `<ComponentPreview>` / `PreviewFrame`, lets block-level components (charts, inputs, tables) fill the frame instead of shrinking to their content. `center` stays the default.
10
10
-**`not-prose` class.** Every `.shelldocs-prose` rule skips `.not-prose` subtrees. Preview frames carry it; add it to any element that should keep page typography out.
11
+
-**Generic components in markdown.**`RegisterComponentsFromAssembly` now registers generic components under their bare name (`<BarChart>`), and `razor:preview`, inline tags and `<ComponentPreview>` close them from a type-parameter attribute, as Razor does: `<BarChart TItem="SalesRow" />`. Type arguments take C# spellings (`int?`, `List<string>`, full or simple type names). Without one, `object` is used when the constraints allow it (with a warning); otherwise the component renders an inline error instead of breaking the page. A non-generic component with the same name keeps the tag. `<AutoTypeTable>` lists the type parameters.
12
+
-**Redirects.** URLs that aren't pages now redirect: content folders and version roots to their first page (`/docs` prefers unversioned pages, then the latest version), and unversioned URLs that exist in the latest version to that page (`/docs/cli/dev` → `/docs/v2.0/cli/dev`). `o.AddRedirect(from, to, permanent)` adds segment-aware prefix rules for moved content; computed redirects answer 302, rules 301. Middleware registered by `AddShellDocs` serves them before routing (so dotted version roots like `/docs/v0.2.1` work), and `shelldocs build` writes them as redirect pages. Opt out with `o.EnableRedirects = false`.
13
+
-**`ShellDocsOptions.RenderPageTitle`.** Renders frontmatter `title` as the page heading and `description` as a lead paragraph when the body doesn't start with its own `# Heading`. Off by default.
11
14
12
15
### Changed
13
16
14
17
-**`razor:preview` child content is parsed as Razor, not markdown.** Component bodies inside a fence (and `<ComponentPreview>` bodies) go through the same node parser as the fence itself: elements wrap nested components exactly as written and nothing is wrapped in `<p>`. Inline component tags in prose still take markdown bodies.
15
18
-**`ThemeToggle` works without a Blazor runtime.** Clicks are handled by `shelldocs.js` and both icons render, with CSS picking one from `<html class="dark">`.
19
+
-**`ShellDocs.Components` references the ASP.NET Core shared framework** (for the redirect middleware) instead of the `Microsoft.AspNetCore.Components.Web` package. It already read content from disk, so it was server-side in practice.
16
20
17
21
### Fixed
18
22
19
23
-**Prose styles leaked into previews.** Paragraph margins, list padding, heading sizes and link underlines from `.shelldocs-prose` hit live components and beat Tailwind's layered utilities (gaps inside cards and menus, underlined nav links).
20
24
-**Nested markup inside a preview component was mangled.**`<Navbar><div><a/>…<ThemeToggle/></div></Navbar>` closed the `<div>` before the nested component and wrapped loose text in `<p>`.
21
25
-**Theme state only followed ShellDocs' own toggle.** A component library flipping `<html class="dark">` left the stored theme and `ThemeState` stale. `shelldocs.js` now watches the class, saves it under `shelldocs-theme` and pushes it into `ThemeState`.
26
+
-**`shelldocs build` prerendered "Page not found" pages** when the csproj copied `meta.json` but not the `.md` files (`<Content Update="content/**/*.md">`). Build only mirrored `content/` when it was missing entirely; it now fills in every file publish left out. The example project's csproj is fixed too.
22
27
-**`enhancedload` handlers never ran.** Blazor raises the event through `Blazor.addEventListener`, not as a DOM event, so re-applying the theme, re-attaching the TOC and closing the mobile nav after enhanced navigation (static SSR apps) silently did nothing.
-**Markdown-first authoring.** YAML frontmatter, Shiki-highlighted code fences, live `razor:preview` examples, inline component tags mid-prose. Set `o.RenderPageTitle = true` to render the frontmatter `title` and `description` as the page header instead of writing `# Title`.
33
33
-**File-based navigation.** Drop a `.md` in `content/docs/` and it's a page. Sidebar, breadcrumb, prev/next and TOC come from the folder tree; `meta.json` controls order, dividers, subsections and hidden pages.
34
34
-**`Cmd+K` search** across titles, headings and page text, with body snippets. The index is built in memory at startup, so there's no external service; search needs a running Blazor app (it isn't available in the static export).
35
35
-**Blazor-native.** Your components render as real Razor components, not iframes.
@@ -63,14 +63,23 @@ The current version is the one whose `RootUrl` prefixes the path (segment-aware)
63
63
64
64
It's all server-rendered links plus `shelldocs.js`, so it works on static hosts.
65
65
66
-
> 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.
66
+
### Redirects
67
+
68
+
URLs that aren't pages redirect instead of 404ing: content folders and version roots go to their first page (`/docs/v0.2.1` → `/docs/v0.2.1/introduction`; `/docs` prefers unversioned pages, then the latest version), and unversioned URLs that exist in the latest version go there (`/docs/cli/dev` → `/docs/v0.3/cli/dev`). Add rules for moved content:
69
+
70
+
```csharp
71
+
o.AddRedirect("/docs/v0.3.0", "/docs/v0.3"); // also /docs/v0.3.0/x → /docs/v0.3/x (301)
72
+
```
73
+
74
+
`AddShellDocs` registers middleware that answers these before routing, so a dotted version root works even though `/docs/{*Path:nonfile}` routes can't match it. `shelldocs build` writes the same redirects as static pages. Turn it off with `o.EnableRedirects = false`.
67
75
68
76
## Component previews
69
77
70
78
-**Preview | Code toolbar on every example**, with copy and a ⋯ menu: *Open in new tab*, *Report a bug*, *Suggest something*. The issue links go to `https://github.com/{GitHubRepo}/issues/new`, pre-filled with the example and page URL; point them elsewhere with `o.IssueTrackerUrl`.
71
79
-**`razor:preview` fences render everything.** Several sibling components, HTML wrappers (`<div class="flex gap-2">…</div>`) and text render in order inside one frame, and the Code tab shows the whole fence.
72
80
-**Child content is Razor.** Component bodies inside a fence are parsed like the fence itself, so `<Navbar><div>…<ThemeToggle /></div></Navbar>` keeps its structure and nothing is wrapped in `<p>`.
73
81
-**Page typography stays out.** Preview frames are `not-prose`, so prose margins, list padding and link underlines don't reach your components.
82
+
-**Generic components work.** Set the type argument as Razor does: `<BarChart TItem="SalesRow" />`, `<DataTable TItem="int" />`. Libraries' generic components are registered under their bare name.
74
83
-**Centred or stretched.** Examples are centred; `razor:preview stretch` (or `Layout="stretch"` on `<DemoPreview>` / `<ComponentPreview>`) lets charts, inputs and tables fill the frame.
-**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.
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+18-5Lines changed: 18 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,14 +54,14 @@ Turns markdown into HTML plus typed component slots. Depends on `ShellDocs.Core`
54
54
55
55
### `ShellDocs.Components`
56
56
57
-
The Razor class library everyone references. Depends on `ShellDocs.Core`, `ShellDocs.Markdown`, `ShellDocs.Tokens`, `ShellIcons.Blazor`.
57
+
The Razor class library everyone references. Depends on `ShellDocs.Core`, `ShellDocs.Markdown`, `ShellDocs.Tokens`, `ShellIcons.Blazor` and the ASP.NET Core shared framework (it reads content from disk and runs middleware, so it's server-side).
- Services: `DocsPageState` (scoped), `DocsVersionResolver`and `DocsRedirects` (singletons), plus the redirect middleware.
65
65
-`wwwroot/shelldocs.js` and `wwwroot/shelldocs-theme.css`.
66
66
67
67
### `ShellDocs.Tokens`
@@ -124,6 +124,8 @@ Placeholders plus a slot list keep everything at render time: no generated Razor
124
124
-`ComponentSlot` → `<DynamicComponent>` with parameters from `SlotRenderer.BuildParameters`.
125
125
-`PreviewSlot` → `<PreviewFrame Id="preview-N">`, where `SlotRenderer.RenderNodes` emits elements and components as real render-tree nodes, so wrappers keep their children under interactive re-renders.
126
126
127
+
**Generic components** register under their bare name (`TypeRegistry.TagNameOf`) as open definitions. `SlotRenderer.Component` closes one per use with `GenericComponents.Close`, reading attributes named after the type parameters (`TItem="int"`, resolved from C# spellings across loaded assemblies) or falling back to `object`; one it can't close renders a `.shelldocs-render-error` element instead. A non-generic component with the same name keeps the tag.
128
+
127
129
Component child markup is rendered recursively. Inline tags in prose take markdown bodies (`SlotRenderer.FromMarkup`, dedented first, since Markdig treats 4-space indentation as a code block). Inside a `razor:preview` and in `<ComponentPreview>` the body is Razor: `SlotRenderer.FromRazor` parses it with `PreviewParser` and emits real elements, so wrappers around nested components stay intact. Child tags named after a `RenderFragment` parameter (`<Icon>` → `Alert.Icon`) are routed into that slot.
128
130
129
131
**Parameter coercion.** Attribute values are strings. `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`. It never throws. Anything static markup can't set is skipped with a logged warning: directive attributes, delegate/`EventCallback` parameters, unsupported types, unparseable values, unknown attributes on components without a `CaptureUnmatchedValues` catch-all, and child content on components without a plain `RenderFragment ChildContent`.
@@ -169,6 +171,16 @@ Runtime queries: `ResolveByUrl` is a case-insensitive dictionary lookup on norma
169
171
170
172
All answers are computed at render time and emitted as plain `<a href>`, so prerendered pages behave identically.
171
173
174
+
### Redirects
175
+
176
+
`DocsRedirects` (singleton) maps URLs that aren't pages to a target, computed once from the graph and versions:
177
+
178
+
- content folders and version roots → their first page, within the folder's version scope (a folder holding version folders → its first unversioned page, else the latest version's);
179
+
- unversioned URLs that exist in the latest version → that page;
180
+
-`AddRedirect` rules on top: segment-aware prefixes that may chain into the computed redirects. They are the only thing that can redirect an existing page.
181
+
182
+
`DocsRedirectMiddleware` runs first in the pipeline (added by an `IStartupFilter`, so no `Program.cs` change) and answers 302 for computed redirects or 301 for permanent rules, keeping `PathBase` and the query string. Running before routing is what makes `/docs/v0.2.1` work. It also serves the full map at `/_shelldocs/redirects.json` for `shelldocs build`. `EnableRedirects = false` leaves it out.
183
+
172
184
---
173
185
174
186
## Search
@@ -223,11 +235,12 @@ One neutral, shadcn-shaped palette of CSS variables in `ShellDocs.Tokens/tokens.
223
235
224
236
`shelldocs build`:
225
237
226
-
1.`dotnet publish -c Release` into `obj/shelldocs-publish` (mirroring `content/`into it if the csproj didn't copy it).
238
+
1.`dotnet publish -c Release` into `obj/shelldocs-publish`, then copies in any `content/`file publish left out (a csproj that copies `meta.json` but not `.md`).
227
239
2. Builds the navigation graph and collects every URL (visible and hidden) plus `/`.
228
240
3.`PrerenderRunner` starts the published app on a free port, requests each URL and writes `<output>/<path>/index.html`.
229
-
4. Merges the published `wwwroot/` into the output without overwriting prerendered HTML.
230
-
5. Optionally rewrites `<base href>` in every HTML file, copies `index.html` to `404.html`, and with `--site-url` writes `sitemap.xml`, `robots.txt` and `og:*` meta.
241
+
4. Reads the app's redirect map and writes a redirect page for each source URL without a prerendered page (meta refresh plus `location.replace`, relative to `<base href>`).
242
+
5. Merges the published `wwwroot/` into the output without overwriting prerendered HTML.
243
+
6. Optionally rewrites `<base href>` in every HTML file, copies `index.html` to `404.html`, and with `--site-url` writes `sitemap.xml`, `robots.txt` and `og:*` meta.
Copy file name to clipboardExpand all lines: examples/ShellDocs.Preview/content/docs/installation.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,4 +55,6 @@ shelldocs build
55
55
-`--spa-fallback` copies `index.html` to `404.html`.
56
56
-`--site-url https://docs.example.com` emits `sitemap.xml`, `robots.txt` and `og:` meta tags.
57
57
58
-
In the static output, navigation, sidebar sections, the mobile menu, the version and package selectors, tabs, preview tabs and menus, the table of contents, the theme toggle and code copy all work through `shelldocs.js`. Search, the desktop sidebar-collapse button and stateful demo components still need a running Blazor app (Server or WebAssembly).
58
+
In the static output, navigation, sidebar sections, the mobile menu, the version and package selectors, tabs, preview tabs and menus, the table of contents, the theme toggle and code copy all work through `shelldocs.js`. Search, the desktop sidebar-collapse button and stateful demo components still need a running Blazor Server app (interactive server rendering).
59
+
60
+
URLs that aren't pages (folders, version roots, unversioned URLs that moved into a version) become redirect pages in the static output, the same redirects the running app answers.
Copy file name to clipboardExpand all lines: examples/ShellDocs.Preview/content/docs/markdown-syntax.md
+16Lines changed: 16 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,6 +21,8 @@ order: 10
21
21
---
22
22
```
23
23
24
+
With `o.RenderPageTitle = true`, ShellDocs renders `title` as the page's heading and `description` as a lead paragraph, so pages don't need a `# Title` line. A page whose body starts with its own `# Heading` keeps it.
25
+
24
26
## Standard markdown works
25
27
26
28
Headings, lists, tables, code fences, images, links — all standard:
@@ -72,3 +74,17 @@ Examples are centred. Add `stretch` to the info string (`razor:preview stretch`)
72
74
```
73
75
74
76
Preview frames are marked `not-prose`, so the page's typography (paragraph margins, list padding, link underlines) never reaches the components inside. Add the class to any other element that should opt out.
77
+
78
+
### Generic components
79
+
80
+
Generic components (`@typeparam TValue`) register under their bare name. Set the type argument the way Razor does, as an attribute named after the type parameter. Attribute values are then coerced to the closed type:
Type arguments accept C# spellings (`int`, `int?`, `List<string>`, `MyApp.Models.Product`). Without one, ShellDocs uses `object` when the constraints allow it and logs a warning; a type it can't resolve shows an inline error. `<ComponentPreview Component="ValueBadge" TValue="int" … />` works the same way.
0 commit comments