diff --git a/.gitignore b/.gitignore index becc9ce..d57159d 100644 --- a/.gitignore +++ b/.gitignore @@ -486,4 +486,7 @@ $RECYCLE.BIN/ # MyLittleContentEngine files output/ -vcrsharp-logs/ \ No newline at end of file +vcrsharp-logs/ + +# Playwright MCP debugging artifacts (screenshots + console logs) +.playwright-mcp/ diff --git a/Generate-WidgetScreenshots.ps1 b/Generate-WidgetScreenshots.ps1 index 3baf54f..cb1db0d 100644 --- a/Generate-WidgetScreenshots.ps1 +++ b/Generate-WidgetScreenshots.ps1 @@ -1,85 +1,110 @@ #!/usr/bin/env pwsh - #Requires -Version 7.0 +<# +.SYNOPSIS + Parallel screenshot generation for the Spectre docs tapes. + +.DESCRIPTION + Drives the globally-installed `vcr` tool over every .tape in the docs example + projects. The global tool runs VcrSharp's native (browserless) backend — an + in-process ConPTY + VT parser, no ttyd and no Chromium — so each run is its own + isolated pseudo-console with no shared terminal server. That removes the + concurrency bug that once forced serial execution (a stray linefeed injected + into SVG/GIF output when ttyd was driven in parallel), so the tapes record + concurrently and parallel output is content-identical to serial modulo + timing-sampled frames. + + On a 16-logical-core box the full set lands in well under a minute at the + default throttle, versus many minutes one-at-a-time. The floor is the single + longest tape (live.tape, ~37s). + +.PARAMETER Throttle + Max concurrent tapes. The tapes spend most of their time idle-waiting on + Sleep/EndBuffer, so oversubscribing past core count helps. Default: 1.5x + logical cores. + +.PARAMETER VcrExe + The vcr command (or a path to a specific build). Defaults to the global tool + resolved from PATH — install with `dotnet tool install --global vcr`. Override + to point at a local build if you're iterating on VcrSharp itself. + +.PARAMETER SkipBuild + Skip building the example projects. They must already be built — the tapes + invoke `dotnet run --no-build`. +#> +param( + [int]$Throttle = [int][math]::Ceiling([Environment]::ProcessorCount * 1.5), + [string]$VcrExe = 'vcr', + [switch]$SkipBuild +) -# Build the projects -Write-Host "Building projects..." -ForegroundColor Cyan -dotnet build Spectre.Docs.Examples +$ErrorActionPreference = 'Stop' +$root = $PSScriptRoot -if ($LASTEXITCODE -ne 0) { - Write-Error "Build failed with exit code $LASTEXITCODE" - exit $LASTEXITCODE +# Resolve the vcr command up front so a missing tool fails fast with a clear +# message instead of erroring once per tape inside the parallel block. Get-Command +# handles both a command on PATH (the default) and an explicit path override. +$vcr = Get-Command $VcrExe -ErrorAction SilentlyContinue +if (-not $vcr) { + Write-Error "vcr not found ('$VcrExe'). Install the global tool with 'dotnet tool install --global vcr', or pass -VcrExe to a local build." + exit 1 } - -dotnet build Spectre.Docs.Cli.Examples - -if ($LASTEXITCODE -ne 0) { - Write-Error "Build failed with exit code $LASTEXITCODE" - exit $LASTEXITCODE +$VcrPath = $vcr.Source + +if (-not $SkipBuild) { + Write-Host "Building example projects..." -ForegroundColor Cyan + dotnet build "$root\Spectre.Docs.Examples" -c Debug --nologo -v q + if ($LASTEXITCODE -ne 0) { Write-Error "Build failed (Examples)"; exit $LASTEXITCODE } + dotnet build "$root\Spectre.Docs.Cli.Examples" -c Debug --nologo -v q + if ($LASTEXITCODE -ne 0) { Write-Error "Build failed (Cli.Examples)"; exit $LASTEXITCODE } } -# Generate all screenshots -Write-Host "`nGenerating screenshots..." -ForegroundColor Cyan - $tapeFiles = @( - Get-ChildItem "Spectre.Docs.Examples\VCR\*.tape" - Get-ChildItem "Spectre.Docs.Cli.Examples\VCR\*.tape" + Get-ChildItem "$root\Spectre.Docs.Examples\VCR\*.tape" + Get-ChildItem "$root\Spectre.Docs.Cli.Examples\VCR\*.tape" ) -$totalFiles = $tapeFiles.Count -Write-Host "Found $totalFiles tape files to process`n" -ForegroundColor Green - -$results = $tapeFiles | ForEach-Object -Parallel { - $file = $_ - $fileName = $file.Name - $gifName = $file.BaseName + ".gif" - +Write-Host "`nGenerating $($tapeFiles.Count) screenshots via '$VcrPath' — throttle=$Throttle, cores=$([Environment]::ProcessorCount)`n" -ForegroundColor Green + +$sw = [System.Diagnostics.Stopwatch]::StartNew() +$results = $tapeFiles | ForEach-Object -ThrottleLimit $Throttle -Parallel { + $exe = $using:VcrPath; $root = $using:root + Set-Location $root # tapes use paths relative to the docs root + $name = $_.Name + $t = [System.Diagnostics.Stopwatch]::StartNew() try { - Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Processing: $fileName" -ForegroundColor Yellow - - - # Execute VCR command - vcr $file.FullName | Out-Null - + & $exe $_.FullName *> $null + $t.Stop() if ($LASTEXITCODE -eq 0) { - Write-Host "[$(Get-Date -Format 'HH:mm:ss')] ✓ Completed: $fileName" -ForegroundColor Green - return @{ Success = $true; File = $fileName } + Write-Host (" ✓ {0,-44} {1,5:N1}s" -f $name, $t.Elapsed.TotalSeconds) -ForegroundColor DarkGreen + [pscustomobject]@{ Success = $true; File = $name; Seconds = $t.Elapsed.TotalSeconds } } else { - Write-Warning "[$(Get-Date -Format 'HH:mm:ss')] ✗ Failed: $fileName (Exit code: $LASTEXITCODE)" - return @{ Success = $false; File = $fileName; ExitCode = $LASTEXITCODE } + Write-Warning (" ✗ {0} (exit {1})" -f $name, $LASTEXITCODE) + [pscustomobject]@{ Success = $false; File = $name; ExitCode = $LASTEXITCODE } } + } catch { + $t.Stop() + Write-Error " ✗ $name : $_" + [pscustomobject]@{ Success = $false; File = $name; Error = $_.Exception.Message } } - catch { - Write-Error "[$(Get-Date -Format 'HH:mm:ss')] ✗ Error processing $fileName : $_" - return @{ Success = $false; File = $fileName; Error = $_.Exception.Message } - } -} -ThrottleLimit 1 -# THEORETICALLY, this could be increased for more parallelism, but VCRSharp might have issues with concurrent runs. -# Either we do or ttyd does. It seems an extra line feed gets added to the output (svg and gif) when run in parallel -# and I don't even know how to begin debugging that. +} +$sw.Stop() +$ok = ($results | Where-Object Success).Count +$fail = ($results | Where-Object { -not $_.Success }).Count -# Report results Write-Host "`n========================================" -ForegroundColor Cyan -Write-Host "Screenshot Generation Summary" -ForegroundColor Cyan +Write-Host "Screenshot Generation Summary" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan +Write-Host ("Total: {0} | Success: {1} | Failed: {2} | Wall: {3:N1}s" -f $tapeFiles.Count, $ok, $fail, $sw.Elapsed.TotalSeconds) ` + -ForegroundColor $(if ($fail -eq 0) { 'Green' } else { 'Yellow' }) -$successful = ($results | Where-Object { $_.Success }).Count -$failed = ($results | Where-Object { -not $_.Success }).Count - -Write-Host "Total: $totalFiles | Success: $successful | Failed: $failed" -ForegroundColor $(if ($failed -eq 0) { 'Green' } else { 'Yellow' }) - -if ($failed -gt 0) { +if ($fail -gt 0) { Write-Host "`nFailed files:" -ForegroundColor Red $results | Where-Object { -not $_.Success } | ForEach-Object { Write-Host " - $($_.File)" -ForegroundColor Red - if ($_.Error) { - Write-Host " Error: $($_.Error)" -ForegroundColor Gray - } - if ($_.ExitCode) { - Write-Host " Exit code: $($_.ExitCode)" -ForegroundColor Gray - } + if ($_.Error) { Write-Host " Error: $($_.Error)" -ForegroundColor Gray } + if ($_.ExitCode) { Write-Host " Exit: $($_.ExitCode)" -ForegroundColor Gray } } exit 1 } - -Write-Host "`n✓ All screenshots generated successfully!" -ForegroundColor Green +Write-Host "`n✓ All screenshots generated!" -ForegroundColor Green diff --git a/README.md b/README.md index eaa4ded..4e7f6ee 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ This is the documentation website for **Spectre.Console** (rich console UI libra ## Technology Stack -- **.NET 10.0** - Blazor Server application -- **MyLittleContentEngine** - Markdown-based content management framework +- **.NET 11.0** - Blazor Server application +- **Pennington** - Markdown-based content engine for .NET - **MonorailCss** - Utility-first CSS framework -- **Roslyn** - Code analysis for live code sample integration +- **Pennington.TreeSitter** - Tree-sitter code-fragment extraction for live code samples ## Getting Started @@ -119,68 +119,71 @@ Your blog content here... Blog posts are automatically sorted by date (newest first) and appear at `/blog/your-file-name`. -## Linking to Code with xmldocid +## Linking to Code with `:symbol` fences -The site uses **Roslyn integration** to embed live code from your source files directly into documentation. This ensures code samples are always up-to-date with the actual implementation. +The site uses **Pennington.TreeSitter** to embed live code from your source files directly into documentation. This ensures code samples are always up-to-date with the actual implementation. ### Syntax -Use the special code fence `` ```csharp:xmldocid ``: +Use the special code fence `` ```csharp:symbol ``. The body is a source file path (relative to the repo root) optionally followed by `> Type.Member`: ````markdown -```csharp:xmldocid -M:Spectre.Docs.Examples.AsciiCast.Samples.ProgressSample.Run(Spectre.Console.IAnsiConsole) -M:Spectre.Docs.Examples.AsciiCast.Samples.ProgressSample.CreateTasks(Spectre.Console.ProgressContext,System.Random) +```csharp:symbol +Spectre.Docs.Examples/Showcase/ProgressSample.cs > ProgressSample.Run +Spectre.Docs.Examples/Showcase/ProgressSample.cs > ProgressSample.CreateTasks ``` ```` -### XML Doc ID Format +### Reference format -Use standard .NET XML documentation identifiers: +Address code by **file path + name path** — a name path survives the line shifts that break hard-coded ranges: -| Type | Prefix | Example | -|------|--------|---------| -| Method | `M:` | `M:Namespace.ClassName.MethodName(Param.Type)` | -| Type/Class | `T:` | `T:Spectre.Console.Examples.MyExample` | -| Property | `P:` | `P:Namespace.ClassName.PropertyName` | -| Field | `F:` | `F:Namespace.ClassName.FieldName` | +| Target | Body shape | Example | +|--------|------------|---------| +| Whole file | `` | `Spectre.Docs.Examples/Showcase/ProgressSample.cs` | +| A type | ` > Type` | `... > ProgressSample` | +| A member | ` > Type.Member` | `... > ProgressSample.Run` | +| Nested member | ` > Outer.Inner.Member` | `... > DrawCommand.Settings` | -**Finding XML Doc IDs:** +Modifiers (comma-separated, order-independent) follow the suffix: -1. Navigate to the source file containing the code -2. For methods: `M:` + full namespace + class + method name + parameter types -3. For classes: `T:` + full namespace + class name +| Modifier | Effect | +|----------|--------| +| `,bodyonly` | Render only the member body, stripping the declaration and braces | +| `,imports` | Prepend the file's top-of-file `using` statements | +| `,signatures` | Replace member bodies with `{ … }` for an outline view | +| `symbol-diff` | Emit a unified diff between two members (exactly two references) | **Example from progress.md:** ````markdown -```csharp:xmldocid -M:Spectre.Docs.Examples.AsciiCast.Samples.ProgressSample.Run(Spectre.Console.IAnsiConsole) +```csharp:symbol,bodyonly +Spectre.Docs.Examples/Showcase/ProgressSample.cs > ProgressSample.Run ``` ```` -This extracts the `Run` method from `ProgressSample.cs` and renders it with syntax highlighting. +This extracts the body of the `Run` method from `ProgressSample.cs` and renders it with syntax highlighting. ### How It Works -The site is configured to analyze the solution via Roslyn (`Program.cs:42-45`): +Tree-sitter is wired in `Program.cs`, reading source files directly — no MSBuild workspace, no compilation: ```csharp -.WithConnectedRoslynSolution(_ => new CodeAnalysisOptions +builder.Services.AddTreeSitter(treeSitter => { - SolutionPath = "../Spectre.Docs.sln", -}) + treeSitter.ContentRoot = ".."; // repo root, so fence paths resolve against the sibling example projects +}); ``` -When MyLittleContentEngine encounters `csharp:xmldocid`: -1. Parses the XML Doc ID -2. Uses Roslyn to find the symbol in the compiled solution -3. Extracts the source code +When Pennington encounters a `csharp:symbol` fence: +1. Resolves the file path relative to `ContentRoot` +2. Uses tree-sitter to locate the named member within the file +3. Extracts the source text (applying any modifiers) 4. Renders as syntax-highlighted HTML This approach provides: - **Always up-to-date code** - Changes in source automatically reflect in docs -- **Type safety** - Broken references fail at build time +- **Rename-safe references** - A name path tolerates line shifts; broken references surface in the build report - **No duplication** - Single source of truth for code diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-command-hierarchies.tape b/Spectre.Docs.Cli.Examples/VCR/cli-command-hierarchies.tape index 23e8513..fba4993 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-command-hierarchies.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-command-hierarchies.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 20 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.CommandHierarchies.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-configuring-app.tape b/Spectre.Docs.Cli.Examples/VCR/cli-configuring-app.tape index 3c8ca67..98915d0 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-configuring-app.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-configuring-app.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 22 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.ConfiguringCommandApp.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-custom-type-converters.tape b/Spectre.Docs.Cli.Examples/VCR/cli-custom-type-converters.tape index c46de94..2f8af0a 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-custom-type-converters.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-custom-type-converters.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 18 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.CustomTypeConverters.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-customizing-help.tape b/Spectre.Docs.Cli.Examples/VCR/cli-customizing-help.tape index e90f629..247782d 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-customizing-help.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-customizing-help.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 24 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.CustomizingHelpText.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-defining-arguments.tape b/Spectre.Docs.Cli.Examples/VCR/cli-defining-arguments.tape index bbac665..d0ddfba 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-defining-arguments.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-defining-arguments.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 20 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.DefiningCommandsAndArguments.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-di-tutorial.tape b/Spectre.Docs.Cli.Examples/VCR/cli-di-tutorial.tape index 2ce14b1..3117fbc 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-di-tutorial.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-di-tutorial.tape @@ -1,11 +1,12 @@ -Output "Spectre.Docs/Content/assets/cli-di-tutorial.svg" +Output "Spectre.Docs/Content/assets/cli-di-tutorial.svg" Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 70 +Set Cols 82 Set Rows 16 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.DependencyInjection.DIComplete.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-dictionary-options.tape b/Spectre.Docs.Cli.Examples/VCR/cli-dictionary-options.tape index 874e129..9a70d60 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-dictionary-options.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-dictionary-options.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 90 +Set Cols 82 Set Rows 20 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.DictionaryOptions.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-error-handling.tape b/Spectre.Docs.Cli.Examples/VCR/cli-error-handling.tape index b53adaf..d67ce0f 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-error-handling.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-error-handling.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 90 +Set Cols 82 Set Rows 24 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.HandlingErrorsAndExitCodes.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-flag-arguments.tape b/Spectre.Docs.Cli.Examples/VCR/cli-flag-arguments.tape index cc9a3ac..1f4af0f 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-flag-arguments.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-flag-arguments.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 20 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.FlagArguments.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-hiding-commands.tape b/Spectre.Docs.Cli.Examples/VCR/cli-hiding-commands.tape index 2296f99..0834c05 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-hiding-commands.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-hiding-commands.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 20 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.HidingCommandsAndOptions.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-homepage.tape b/Spectre.Docs.Cli.Examples/VCR/cli-homepage.tape index 2a2a865..f50c47d 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-homepage.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-homepage.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 70 +Set Cols 82 Set Rows 14 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.QuickStart.HomePageExample.Run(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-intercepting-execution.tape b/Spectre.Docs.Cli.Examples/VCR/cli-intercepting-execution.tape index a5b994f..66851ed 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-intercepting-execution.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-intercepting-execution.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 14 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.InterceptingCommandExecution.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-logging-tutorial.tape b/Spectre.Docs.Cli.Examples/VCR/cli-logging-tutorial.tape index c46a1a2..ac5d4d8 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-logging-tutorial.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-logging-tutorial.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 90 +Set Cols 82 Set Rows 20 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.Logging.LoggingComplete.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-multi-command-tutorial.tape b/Spectre.Docs.Cli.Examples/VCR/cli-multi-command-tutorial.tape index 95024b0..d29c15e 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-multi-command-tutorial.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-multi-command-tutorial.tape @@ -1,11 +1,12 @@ -Output "Spectre.Docs/Content/assets/cli-multi-command-tutorial.svg" +Output "Spectre.Docs/Content/assets/cli-multi-command-tutorial.svg" Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 70 +Set Cols 82 Set Rows 16 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.MultiCommand.Finished.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-quickstart.tape b/Spectre.Docs.Cli.Examples/VCR/cli-quickstart.tape index deb422b..835f268 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-quickstart.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-quickstart.tape @@ -1,11 +1,12 @@ -Output "Spectre.Docs/Content/assets/cli-quickstart.svg" +Output "Spectre.Docs/Content/assets/cli-quickstart.svg" Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 70 +Set Cols 82 Set Rows 12 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.QuickStart.Complete.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Cli.Examples/VCR/cli-required-options.tape b/Spectre.Docs.Cli.Examples/VCR/cli-required-options.tape index b764753..9f53270 100644 --- a/Spectre.Docs.Cli.Examples/VCR/cli-required-options.tape +++ b/Spectre.Docs.Cli.Examples/VCR/cli-required-options.tape @@ -4,8 +4,9 @@ Set Shell "pwsh" Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" -Set Cols 80 +Set Cols 82 Set Rows 18 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "Spectre.Docs.Cli.Examples/bin/Debug/net10.0" Env SPECTRE_APP "M:Spectre.Docs.Cli.Examples.DemoApps.MakingOptionsRequired.Demo.RunAsync(System.String[])" diff --git a/Spectre.Docs.Examples/Showcase/LandingBarChartSample.cs b/Spectre.Docs.Examples/Showcase/LandingBarChartSample.cs new file mode 100644 index 0000000..5be22bf --- /dev/null +++ b/Spectre.Docs.Examples/Showcase/LandingBarChartSample.cs @@ -0,0 +1,22 @@ +using Spectre.Console; + +namespace Spectre.Docs.Examples.Showcase; + +/// Landing-page bar chart demo: a static throughput chart. +internal class LandingBarChartSample : BaseSample +{ + /// + public override void Run(IAnsiConsole console) + { + var chart = new BarChart() + .Width(34) + .Label("[bold]Throughput[/]") + .AddItem("Rust", 92, Color.Orange1) + .AddItem("C#", 78, Color.Green) + .AddItem("Go", 64, Color.Blue) + .AddItem("Python", 51, Color.Yellow) + .AddItem("Zig", 35, Color.Red); + + console.Write(chart); + } +} diff --git a/Spectre.Docs.Examples/Showcase/LandingCalendarSample.cs b/Spectre.Docs.Examples/Showcase/LandingCalendarSample.cs new file mode 100644 index 0000000..2778b8b --- /dev/null +++ b/Spectre.Docs.Examples/Showcase/LandingCalendarSample.cs @@ -0,0 +1,22 @@ +using Spectre.Console; + +namespace Spectre.Docs.Examples.Showcase; + +/// Landing-page calendar demo: a static month view with highlighted event days. +internal class LandingCalendarSample : BaseSample +{ + /// + public override void Run(IAnsiConsole console) + { + var calendar = new Calendar(2026, 6) + .Border(TableBorder.Rounded) + .HighlightStyle(Style.Parse("yellow bold")) + .HeaderStyle(Style.Parse("bold")); + + calendar.AddCalendarEvent(2026, 6, 3); + calendar.AddCalendarEvent(2026, 6, 12); + calendar.AddCalendarEvent(2026, 6, 24); + + console.Write(calendar); + } +} diff --git a/Spectre.Docs.Examples/Showcase/LandingPanelSample.cs b/Spectre.Docs.Examples/Showcase/LandingPanelSample.cs new file mode 100644 index 0000000..b9aa8d2 --- /dev/null +++ b/Spectre.Docs.Examples/Showcase/LandingPanelSample.cs @@ -0,0 +1,24 @@ +using Spectre.Console; + +namespace Spectre.Docs.Examples.Showcase; + +/// Landing-page panel demo: a static release-notes panel. +internal class LandingPanelSample : BaseSample +{ + /// + public override void Run(IAnsiConsole console) + { + var body = new Markup( + "[green]v0.55.0[/] is now available\n\n" + + "[grey]•[/] New layout engine\n" + + "[grey]•[/] 12 additional spinners\n" + + "[grey]•[/] Faster ANSI output"); + + var panel = new Panel(body) + .Header("[bold]Release notes[/]") + .RoundedBorder() + .BorderColor(Color.Grey); + + console.Write(panel); + } +} diff --git a/Spectre.Docs.Examples/Showcase/LandingProgressSample.cs b/Spectre.Docs.Examples/Showcase/LandingProgressSample.cs new file mode 100644 index 0000000..c1d79b5 --- /dev/null +++ b/Spectre.Docs.Examples/Showcase/LandingProgressSample.cs @@ -0,0 +1,33 @@ +using Spectre.Console; + +namespace Spectre.Docs.Examples.Showcase; + +/// Landing-page progress demo: multi-task bars that fill in place, looping. +internal class LandingProgressSample : BaseSample +{ + /// + public override void Run(IAnsiConsole console) + { + console.Progress() + .AutoClear(false) + .Columns( + new TaskDescriptionColumn(), + new ProgressBarColumn(), + new PercentageColumn()) + .Start(ctx => + { + var random = new Random(7); + var restore = ctx.AddTask("[green]Restoring packages[/]"); + var build = ctx.AddTask("[blue]Compiling sources[/]"); + var test = ctx.AddTask("[yellow]Running tests[/]"); + + while (!ctx.IsFinished) + { + restore.Increment(random.NextDouble() * 3.2); + build.Increment(random.NextDouble() * 2.4); + test.Increment(random.NextDouble() * 1.8); + Thread.Sleep(60); + } + }); + } +} diff --git a/Spectre.Docs.Examples/Showcase/LandingPromptSample.cs b/Spectre.Docs.Examples/Showcase/LandingPromptSample.cs new file mode 100644 index 0000000..46a9d8c --- /dev/null +++ b/Spectre.Docs.Examples/Showcase/LandingPromptSample.cs @@ -0,0 +1,45 @@ +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace Spectre.Docs.Examples.Showcase; + +/// +/// Landing-page prompt demo: a faithful selection prompt whose highlight sweeps through the +/// choices and back, looping. (A real selection prompt is interactive and cannot be captured +/// non-interactively, so the appearance is composed with real Spectre primitives.) +/// +internal class LandingPromptSample : BaseSample +{ + /// + public override void Run(IAnsiConsole console) + { + string[] choices = ["Build", "Test", "Publish", "Deploy", "Clean"]; + const string title = "What do you want to [green]run[/]?"; + + // Sweep down then back up so the recording loops smoothly. + int[] sequence = [0, 1, 2, 3, 4, 3, 2, 1]; + + console.Live(new Text(string.Empty)).AutoClear(false).Start(ctx => + { + foreach (var selected in sequence) + { + var rows = new List + { + new Markup(title), + Text.Empty, + }; + + for (var i = 0; i < choices.Length; i++) + { + rows.Add(i == selected + ? new Markup($"[blue]> {choices[i]}[/]") + : new Markup($"[grey] {choices[i]}[/]")); + } + + ctx.UpdateTarget(new Rows(rows)); + ctx.Refresh(); + Thread.Sleep(550); + } + }); + } +} diff --git a/Spectre.Docs.Examples/Showcase/LandingStatusSample.cs b/Spectre.Docs.Examples/Showcase/LandingStatusSample.cs new file mode 100644 index 0000000..2019efd --- /dev/null +++ b/Spectre.Docs.Examples/Showcase/LandingStatusSample.cs @@ -0,0 +1,36 @@ +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace Spectre.Docs.Examples.Showcase; + +/// +/// Landing-page status demo: several completed steps with a final step whose spinner spins +/// indefinitely. This is the only animated card in the gallery — the loop runs a whole number +/// of spinner cycles so the recording loops seamlessly. +/// +internal class LandingStatusSample : BaseSample +{ + /// + public override void Run(IAnsiConsole console) + { + var frames = Spinner.Known.Dots.Frames; + + IRenderable Build(string spinner) => new Rows( + new Markup("[green]✓[/] Restored dependencies"), + new Markup("[green]✓[/] Compiled [bold]142[/] files"), + new Markup("[green]✓[/] Bundled assets"), + new Markup("[green]✓[/] Ran 86 tests"), + new Markup($"[blue]{spinner}[/] Deploying…")); + + console.Live(Build(frames[0])).Start(ctx => + { + // Five full cycles of the spinner so the recording loops seamlessly. + for (var i = 0; i < frames.Count * 5; i++) + { + ctx.UpdateTarget(Build(frames[i % frames.Count])); + ctx.Refresh(); + Thread.Sleep(85); + } + }); + } +} diff --git a/Spectre.Docs.Examples/Showcase/LandingTableSample.cs b/Spectre.Docs.Examples/Showcase/LandingTableSample.cs new file mode 100644 index 0000000..3380b28 --- /dev/null +++ b/Spectre.Docs.Examples/Showcase/LandingTableSample.cs @@ -0,0 +1,33 @@ +using Spectre.Console; + +namespace Spectre.Docs.Examples.Showcase; + +/// Landing-page table demo: a static, data-rich service-status table (wide card). +internal class LandingTableSample : BaseSample +{ + /// + public override void Run(IAnsiConsole console) + { + var table = new Table() + .RoundedBorder() + .BorderColor(Color.Grey) + .Expand(); + + table.AddColumn("[bold]Service[/]"); + table.AddColumn("[bold]Version[/]"); + table.AddColumn("[bold]Status[/]"); + table.AddColumn("[bold]Region[/]"); + table.AddColumn("[bold]Uptime[/]"); + table.AddColumn(new TableColumn("[bold]CPU[/]") { Alignment = Justify.Right }); + + table.AddRow("api-gateway", "v2.4.1", "[green]healthy[/]", "us-east-1", "99.98%", "12%"); + table.AddRow("auth-service", "v1.9.0", "[green]healthy[/]", "us-east-1", "99.95%", "8%"); + table.AddRow("payments", "v3.1.2", "[yellow]degraded[/]", "eu-west-1", "97.20%", "[yellow]64%[/]"); + table.AddRow("search", "v0.8.7", "[green]healthy[/]", "us-west-2", "99.99%", "23%"); + table.AddRow("worker", "v2.0.0", "[red]offline[/]", "eu-west-1", "[red]0.00%[/]", "0%"); + table.AddRow("cache", "v7.2.1", "[green]healthy[/]", "us-east-1", "100.0%", "41%"); + table.AddRow("cdn-edge", "v4.5.0", "[green]healthy[/]", "global", "99.97%", "17%"); + + console.Write(table); + } +} diff --git a/Spectre.Docs.Examples/Showcase/LandingTreeSample.cs b/Spectre.Docs.Examples/Showcase/LandingTreeSample.cs new file mode 100644 index 0000000..53efe1d --- /dev/null +++ b/Spectre.Docs.Examples/Showcase/LandingTreeSample.cs @@ -0,0 +1,25 @@ +using Spectre.Console; + +namespace Spectre.Docs.Examples.Showcase; + +/// Landing-page tree demo: a static project layout. +internal class LandingTreeSample : BaseSample +{ + /// + public override void Run(IAnsiConsole console) + { + var tree = new Tree("[bold]src/[/]") + .Guide(TreeGuide.Line); + + var lib = tree.AddNode("[green]Spectre.Console/[/]"); + lib.AddNode("AnsiConsole.cs"); + + var widgets = tree.AddNode("[green]Widgets/[/]"); + widgets.AddNode("Table.cs"); + widgets.AddNode("BarChart.cs"); + + tree.AddNode("README.md"); + + console.Write(tree); + } +} diff --git a/Spectre.Docs.Examples/VCR/align.tape b/Spectre.Docs.Examples/VCR/align.tape index 55cfd17..ba33bbe 100644 --- a/Spectre.Docs.Examples/VCR/align.tape +++ b/Spectre.Docs.Examples/VCR/align.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 8 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase align" diff --git a/Spectre.Docs.Examples/VCR/bar-chart.tape b/Spectre.Docs.Examples/VCR/bar-chart.tape index 5620a08..4216992 100644 --- a/Spectre.Docs.Examples/VCR/bar-chart.tape +++ b/Spectre.Docs.Examples/VCR/bar-chart.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 8 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase bar-chart" diff --git a/Spectre.Docs.Examples/VCR/breakdown-chart.tape b/Spectre.Docs.Examples/VCR/breakdown-chart.tape index 606b243..ce8011d 100644 --- a/Spectre.Docs.Examples/VCR/breakdown-chart.tape +++ b/Spectre.Docs.Examples/VCR/breakdown-chart.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 8 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase breakdown-chart" diff --git a/Spectre.Docs.Examples/VCR/calendar.tape b/Spectre.Docs.Examples/VCR/calendar.tape index 1a4135d..472d14b 100644 --- a/Spectre.Docs.Examples/VCR/calendar.tape +++ b/Spectre.Docs.Examples/VCR/calendar.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 16 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase calendar" diff --git a/Spectre.Docs.Examples/VCR/canvas-image.tape b/Spectre.Docs.Examples/VCR/canvas-image.tape index 31ce622..4a0f47a 100644 --- a/Spectre.Docs.Examples/VCR/canvas-image.tape +++ b/Spectre.Docs.Examples/VCR/canvas-image.tape @@ -1,11 +1,12 @@ +Output "Spectre.Docs/Content/assets/canvas-image.svg" + +Set StaticOutput true +Set FitToContent true Set DisableCursor true Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 -Set Rows 20 -Set EndBuffer 2s +Set Rows 40 Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase canvas-image" -Wait -Screenshot "Spectre.Docs/Content/assets/canvas-image.svg" diff --git a/Spectre.Docs.Examples/VCR/canvas.tape b/Spectre.Docs.Examples/VCR/canvas.tape index 91b3584..6054662 100644 --- a/Spectre.Docs.Examples/VCR/canvas.tape +++ b/Spectre.Docs.Examples/VCR/canvas.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 16 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project ./Spectre.Docs.Examples/ --no-build showcase canvas" diff --git a/Spectre.Docs.Examples/VCR/capabilities.tape b/Spectre.Docs.Examples/VCR/capabilities.tape index 8a75b6e..d78d69f 100644 --- a/Spectre.Docs.Examples/VCR/capabilities.tape +++ b/Spectre.Docs.Examples/VCR/capabilities.tape @@ -7,10 +7,11 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 12 +Set Size "fit-height" Set EndBuffer 5s Set WorkingDirectory "./Spectre.Docs.Examples/" -Type "dotnet run -- showcase capabilities" +Type "dotnet run --no-build -- showcase capabilities" Enter Wait Sleep 1000ms @@ -20,7 +21,7 @@ Type "$env:NO_COLOR='true'" Enter Sleep 500ms -Type "dotnet run -- showcase capabilities" +Type "dotnet run --no-build -- showcase capabilities" Enter Wait Sleep 1000ms @@ -30,7 +31,7 @@ Type "$env:CI_SERVER='yes'" Enter Sleep 500MS -Type "dotnet run -- showcase capabilities" +Type "dotnet run --no-build -- showcase capabilities" Enter Wait Sleep 3s \ No newline at end of file diff --git a/Spectre.Docs.Examples/VCR/columns.tape b/Spectre.Docs.Examples/VCR/columns.tape index 889b495..eae2aac 100644 --- a/Spectre.Docs.Examples/VCR/columns.tape +++ b/Spectre.Docs.Examples/VCR/columns.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 16 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase columns" diff --git a/Spectre.Docs.Examples/VCR/creating-custom-renderables-tutorial.tape b/Spectre.Docs.Examples/VCR/creating-custom-renderables-tutorial.tape index 13f73ba..000b656 100644 --- a/Spectre.Docs.Examples/VCR/creating-custom-renderables-tutorial.tape +++ b/Spectre.Docs.Examples/VCR/creating-custom-renderables-tutorial.tape @@ -1,12 +1,12 @@ -Output "anim.svg" +Output "Spectre.Docs/Content/assets/creating-custom-renderables-tutorial.svg" +Set StaticOutput true Set DisableCursor true Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 -Set Rows 10 +Set Rows 8 +Set Size "fit-height" Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase creating-custom-renderables-tutorial" -Wait -Screenshot "Spectre.Docs/Content/assets/creating-custom-renderables-tutorial.svg" \ No newline at end of file diff --git a/Spectre.Docs.Examples/VCR/figlet.tape b/Spectre.Docs.Examples/VCR/figlet.tape index 88a07ae..58ca745 100644 --- a/Spectre.Docs.Examples/VCR/figlet.tape +++ b/Spectre.Docs.Examples/VCR/figlet.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 8 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase figlet" diff --git a/Spectre.Docs.Examples/VCR/getting-started.tape b/Spectre.Docs.Examples/VCR/getting-started.tape index 3305282..62e898f 100644 --- a/Spectre.Docs.Examples/VCR/getting-started.tape +++ b/Spectre.Docs.Examples/VCR/getting-started.tape @@ -1,10 +1,12 @@ +Output "Spectre.Docs/Content/assets/getting-started-tutorial.svg" + +Set StaticOutput true +Set FitToContent true Set DisableCursor true Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 -Set Rows 5 +Set Rows 30 Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase getting-started-tutorial" -Wait -Screenshot "Spectre.Docs/Content/assets/getting-started-tutorial.svg" diff --git a/Spectre.Docs.Examples/VCR/grid.tape b/Spectre.Docs.Examples/VCR/grid.tape index 30c3bd1..f89d66b 100644 --- a/Spectre.Docs.Examples/VCR/grid.tape +++ b/Spectre.Docs.Examples/VCR/grid.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 12 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase grid" diff --git a/Spectre.Docs.Examples/VCR/hero.tape b/Spectre.Docs.Examples/VCR/hero.tape index 2b1bc08..db3cc8d 100644 --- a/Spectre.Docs.Examples/VCR/hero.tape +++ b/Spectre.Docs.Examples/VCR/hero.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 16 -Set EndBuffer 2s +Set Size "fit-height" +Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase hero" diff --git a/Spectre.Docs.Examples/VCR/interactive-prompt-tutorial.tape b/Spectre.Docs.Examples/VCR/interactive-prompt-tutorial.tape index d22171f..1cfb67e 100644 --- a/Spectre.Docs.Examples/VCR/interactive-prompt-tutorial.tape +++ b/Spectre.Docs.Examples/VCR/interactive-prompt-tutorial.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 16 +Set Size "fit-height" Set EndBuffer 5s Set TypingSpeed 190ms diff --git a/Spectre.Docs.Examples/VCR/json.tape b/Spectre.Docs.Examples/VCR/json.tape index 4ff6e42..40f6e9d 100644 --- a/Spectre.Docs.Examples/VCR/json.tape +++ b/Spectre.Docs.Examples/VCR/json.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 18 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase json" diff --git a/Spectre.Docs.Examples/VCR/landing-bar-chart.tape b/Spectre.Docs.Examples/VCR/landing-bar-chart.tape new file mode 100644 index 0000000..8e8830b --- /dev/null +++ b/Spectre.Docs.Examples/VCR/landing-bar-chart.tape @@ -0,0 +1,11 @@ +Output "Spectre.Docs/Content/assets/landing-bar-chart.svg" + +Set StaticOutput true +Set FontSize 13 +Set Theme "one dark" +Set TransparentBackground "true" +Set DisableCursor true +Set Cols 39 +Set Rows 7 + +Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase landing-bar-chart" diff --git a/Spectre.Docs.Examples/VCR/landing-calendar.tape b/Spectre.Docs.Examples/VCR/landing-calendar.tape new file mode 100644 index 0000000..44d5365 --- /dev/null +++ b/Spectre.Docs.Examples/VCR/landing-calendar.tape @@ -0,0 +1,11 @@ +Output "Spectre.Docs/Content/assets/landing-calendar.svg" + +Set StaticOutput true +Set FontSize 13 +Set Theme "one dark" +Set TransparentBackground "true" +Set DisableCursor true +Set Cols 52 +Set Rows 12 + +Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase landing-calendar" diff --git a/Spectre.Docs.Examples/VCR/landing-panel.tape b/Spectre.Docs.Examples/VCR/landing-panel.tape new file mode 100644 index 0000000..3b5785e --- /dev/null +++ b/Spectre.Docs.Examples/VCR/landing-panel.tape @@ -0,0 +1,11 @@ +Output "Spectre.Docs/Content/assets/landing-panel.svg" + +Set StaticOutput true +Set FontSize 13 +Set Theme "one dark" +Set TransparentBackground "true" +Set DisableCursor true +Set Cols 52 +Set Rows 8 + +Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase landing-panel" diff --git a/Spectre.Docs.Examples/VCR/landing-progress.tape b/Spectre.Docs.Examples/VCR/landing-progress.tape new file mode 100644 index 0000000..e24bb1e --- /dev/null +++ b/Spectre.Docs.Examples/VCR/landing-progress.tape @@ -0,0 +1,11 @@ +Output "Spectre.Docs/Content/assets/landing-progress.svg" + +Set FontSize 13 +Set Theme "one dark" +Set TransparentBackground "true" +Set DisableCursor true +Set Size fit +Set Cols 39 +Set Rows 5 + +Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase landing-progress" diff --git a/Spectre.Docs.Examples/VCR/landing-prompt.tape b/Spectre.Docs.Examples/VCR/landing-prompt.tape new file mode 100644 index 0000000..b50efa4 --- /dev/null +++ b/Spectre.Docs.Examples/VCR/landing-prompt.tape @@ -0,0 +1,11 @@ +Output "Spectre.Docs/Content/assets/landing-prompt.svg" + +Set FontSize 13 +Set Theme "one dark" +Set TransparentBackground "true" +Set DisableCursor true +Set Cols 39 +Set Rows 8 +Set Size fit + +Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase landing-prompt" diff --git a/Spectre.Docs.Examples/VCR/landing-status.tape b/Spectre.Docs.Examples/VCR/landing-status.tape new file mode 100644 index 0000000..450604f --- /dev/null +++ b/Spectre.Docs.Examples/VCR/landing-status.tape @@ -0,0 +1,11 @@ +Output "Spectre.Docs/Content/assets/landing-status.svg" + +Set FontSize 13 +Set Theme "one dark" +Set TransparentBackground "true" +Set DisableCursor true +Set Cols 27 +Set Rows 6 +Set Size fit + +Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase landing-status" diff --git a/Spectre.Docs.Examples/VCR/landing-table.tape b/Spectre.Docs.Examples/VCR/landing-table.tape new file mode 100644 index 0000000..a67bf4a --- /dev/null +++ b/Spectre.Docs.Examples/VCR/landing-table.tape @@ -0,0 +1,11 @@ +Output "Spectre.Docs/Content/assets/landing-table.svg" + +Set StaticOutput true +Set FontSize 13 +Set Theme "one dark" +Set TransparentBackground "true" +Set DisableCursor true +Set Cols 77 +Set Rows 12 + +Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase landing-table" diff --git a/Spectre.Docs.Examples/VCR/landing-tree.tape b/Spectre.Docs.Examples/VCR/landing-tree.tape new file mode 100644 index 0000000..5ce12fd --- /dev/null +++ b/Spectre.Docs.Examples/VCR/landing-tree.tape @@ -0,0 +1,11 @@ +Output "Spectre.Docs/Content/assets/landing-tree.svg" + +Set StaticOutput true +Set FontSize 13 +Set Theme "one dark" +Set TransparentBackground "true" +Set DisableCursor true +Set Cols 39 +Set Rows 8 + +Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase landing-tree" diff --git a/Spectre.Docs.Examples/VCR/layout.tape b/Spectre.Docs.Examples/VCR/layout.tape index 4388d96..7e3e6de 100644 --- a/Spectre.Docs.Examples/VCR/layout.tape +++ b/Spectre.Docs.Examples/VCR/layout.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 25 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase layout" diff --git a/Spectre.Docs.Examples/VCR/live.tape b/Spectre.Docs.Examples/VCR/live.tape index 1f51507..6b78a43 100644 --- a/Spectre.Docs.Examples/VCR/live.tape +++ b/Spectre.Docs.Examples/VCR/live.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 14 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase live" diff --git a/Spectre.Docs.Examples/VCR/padder.tape b/Spectre.Docs.Examples/VCR/padder.tape index 013a776..2e4e9ef 100644 --- a/Spectre.Docs.Examples/VCR/padder.tape +++ b/Spectre.Docs.Examples/VCR/padder.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 8 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase padder" diff --git a/Spectre.Docs.Examples/VCR/panel.tape b/Spectre.Docs.Examples/VCR/panel.tape index d39849c..e4add4b 100644 --- a/Spectre.Docs.Examples/VCR/panel.tape +++ b/Spectre.Docs.Examples/VCR/panel.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 8 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase panel" diff --git a/Spectre.Docs.Examples/VCR/progress-bars-tutorial.tape b/Spectre.Docs.Examples/VCR/progress-bars-tutorial.tape index 710e32f..3e46c8d 100644 --- a/Spectre.Docs.Examples/VCR/progress-bars-tutorial.tape +++ b/Spectre.Docs.Examples/VCR/progress-bars-tutorial.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 12 +Set Size "fit-height" Set EndBuffer 5s diff --git a/Spectre.Docs.Examples/VCR/progress.tape b/Spectre.Docs.Examples/VCR/progress.tape index 187e873..98cc7ec 100644 --- a/Spectre.Docs.Examples/VCR/progress.tape +++ b/Spectre.Docs.Examples/VCR/progress.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 12 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase progress" diff --git a/Spectre.Docs.Examples/VCR/quickstart.tape b/Spectre.Docs.Examples/VCR/quickstart.tape index 8c181c6..59fa561 100644 --- a/Spectre.Docs.Examples/VCR/quickstart.tape +++ b/Spectre.Docs.Examples/VCR/quickstart.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 14 -Set EndBuffer 2s +Set Size "fit-height" +Set EndBuffer 3s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase quick-start" diff --git a/Spectre.Docs.Examples/VCR/rows.tape b/Spectre.Docs.Examples/VCR/rows.tape index c8e23a4..b3244f7 100644 --- a/Spectre.Docs.Examples/VCR/rows.tape +++ b/Spectre.Docs.Examples/VCR/rows.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 12 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase rows" diff --git a/Spectre.Docs.Examples/VCR/rule.tape b/Spectre.Docs.Examples/VCR/rule.tape index 2085291..7cc4a43 100644 --- a/Spectre.Docs.Examples/VCR/rule.tape +++ b/Spectre.Docs.Examples/VCR/rule.tape @@ -1,11 +1,11 @@ Output "Spectre.Docs/Content/assets/rule.svg" -Output "Spectre.Docs/Content/assets/rule.gif" Set DisableCursor true Set FontSize 22 Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 8 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase rule" diff --git a/Spectre.Docs.Examples/VCR/status-spinners-tutorial.tape b/Spectre.Docs.Examples/VCR/status-spinners-tutorial.tape index a3ab495..2d80343 100644 --- a/Spectre.Docs.Examples/VCR/status-spinners-tutorial.tape +++ b/Spectre.Docs.Examples/VCR/status-spinners-tutorial.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 6 +Set Size "fit-height" Set EndBuffer 5s diff --git a/Spectre.Docs.Examples/VCR/status.tape b/Spectre.Docs.Examples/VCR/status.tape index 73715fc..0fab24f 100644 --- a/Spectre.Docs.Examples/VCR/status.tape +++ b/Spectre.Docs.Examples/VCR/status.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 4 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase status" diff --git a/Spectre.Docs.Examples/VCR/table.tape b/Spectre.Docs.Examples/VCR/table.tape index 7982271..b6a08a0 100644 --- a/Spectre.Docs.Examples/VCR/table.tape +++ b/Spectre.Docs.Examples/VCR/table.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 14 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase table" diff --git a/Spectre.Docs.Examples/VCR/text-path.tape b/Spectre.Docs.Examples/VCR/text-path.tape index 6fb1da6..02122eb 100644 --- a/Spectre.Docs.Examples/VCR/text-path.tape +++ b/Spectre.Docs.Examples/VCR/text-path.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 6 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase text-path" diff --git a/Spectre.Docs.Examples/VCR/tree.tape b/Spectre.Docs.Examples/VCR/tree.tape index 150c0ae..31eb93a 100644 --- a/Spectre.Docs.Examples/VCR/tree.tape +++ b/Spectre.Docs.Examples/VCR/tree.tape @@ -6,6 +6,7 @@ Set Theme "one dark" Set TransparentBackground "true" Set Cols 82 Set Rows 12 +Set Size "fit-height" Set EndBuffer 5s Exec "dotnet run --project .\Spectre.Docs.Examples\ --no-build showcase tree" diff --git a/Spectre.Docs/BlogFrontMatter.cs b/Spectre.Docs/BlogFrontMatter.cs index 2b687fd..13077a3 100644 --- a/Spectre.Docs/BlogFrontMatter.cs +++ b/Spectre.Docs/BlogFrontMatter.cs @@ -1,32 +1,23 @@ -using MyLittleContentEngine.Models; +using Pennington.FrontMatter; namespace Spectre.Console; /// -/// Front matter for blog posts in the Spectre.Console documentation +/// Front matter for blog posts in the Spectre.Console documentation. /// -public class BlogFrontMatter : IFrontMatter +public record BlogFrontMatter : IFrontMatter, ITaggable, IRedirectable { public string Title { get; init; } = "Empty title"; public string Author { get; init; } = "Spectre.Console Team"; - public string Description { get; init; } = string.Empty; + public string? Description { get; init; } public DateTime Date { get; init; } = DateTime.Now; - public bool IsDraft { get; init; } = false; + public bool IsDraft { get; init; } public string[] Tags { get; init; } = []; public string Series { get; init; } = string.Empty; public string? RedirectUrl { get; init; } - public string? Section { get; init; } - public string? Uid { get; init; } = null; + public string? SectionLabel { get; init; } + public string? Uid { get; init; } public string? Repository { get; init; } - - public Metadata AsMetadata() - { - return new Metadata() - { - Title = Title, - Description = Description, - LastMod = Date, - RssItem = true - }; - } -} \ No newline at end of file + + DateTime? IFrontMatter.Date => Date; +} diff --git a/Spectre.Docs/Components/App.razor b/Spectre.Docs/Components/App.razor index bb60f7d..b3bb0ea 100644 --- a/Spectre.Docs/Components/App.razor +++ b/Spectre.Docs/Components/App.razor @@ -1,12 +1,17 @@ - +@using Microsoft.AspNetCore.WebUtilities +@inject IWebHostEnvironment WebHostEnvironment + + - - - - + + + + + + - + - + + @code { + static readonly string Version = DateTime.Now.Ticks.ToString(); + + string GetVersioned(string url) => WebHostEnvironment.IsDevelopment() + ? QueryHelpers.AddQueryString(url, "v", DateTime.Now.Ticks.ToString()) // if we are in dev mode, always cache bust + : QueryHelpers.AddQueryString(url, "v", Version); // in prod we'll use the static tick that we stored for all pages } \ No newline at end of file diff --git a/Spectre.Docs/Components/Layouts/BlogLayout.razor b/Spectre.Docs/Components/Layouts/BlogLayout.razor index 8daf402..40ee4a9 100644 --- a/Spectre.Docs/Components/Layouts/BlogLayout.razor +++ b/Spectre.Docs/Components/Layouts/BlogLayout.razor @@ -3,7 +3,7 @@
-
+
@Body
diff --git a/Spectre.Docs/Components/Layouts/Column.razor b/Spectre.Docs/Components/Layouts/Column.razor deleted file mode 100644 index 6fe35e2..0000000 --- a/Spectre.Docs/Components/Layouts/Column.razor +++ /dev/null @@ -1,7 +0,0 @@ -
- @ChildContent -
- -@code { - [Parameter] public RenderFragment? ChildContent { get; set; } -} \ No newline at end of file diff --git a/Spectre.Docs/Components/Layouts/MainLayout.razor b/Spectre.Docs/Components/Layouts/MainLayout.razor index 6f59c80..cc711c4 100644 --- a/Spectre.Docs/Components/Layouts/MainLayout.razor +++ b/Spectre.Docs/Components/Layouts/MainLayout.razor @@ -1,7 +1,6 @@ @using System.Collections.Immutable -@using MyLittleContentEngine.Services.Content.TableOfContents @inherits LayoutComponentBase -@inject ITableOfContentService TableOfContentService +@inject TableOfContentsService TocService @inject NavigationManager NavigationManager
@@ -15,8 +14,12 @@ { @@ -31,14 +34,14 @@ - +
} -
+
@Body @@ -74,9 +77,9 @@ // Get the table of contents for the current section if (!string.IsNullOrEmpty(_currentSection)) { - _toc = (await TableOfContentService.GetNavigationTocAsync(currentPath, _currentSection)).First().Items.ToImmutableList(); + _toc = await TocService.GetNavigationTreeAsync(currentPath, _currentSection); } await base.OnInitializedAsync(); } -} \ No newline at end of file +} diff --git a/Spectre.Docs/Components/Layouts/TwoColumn.razor b/Spectre.Docs/Components/Layouts/TwoColumn.razor deleted file mode 100644 index 6960dba..0000000 --- a/Spectre.Docs/Components/Layouts/TwoColumn.razor +++ /dev/null @@ -1,7 +0,0 @@ -
- @ChildContent -
- -@code { - [Parameter] public RenderFragment? ChildContent { get; set; } -} \ No newline at end of file diff --git a/Spectre.Docs/Components/Pages/ApiReferencePage.razor b/Spectre.Docs/Components/Pages/ApiReferencePage.razor new file mode 100644 index 0000000..8504c1f --- /dev/null +++ b/Spectre.Docs/Components/Pages/ApiReferencePage.razor @@ -0,0 +1,100 @@ +@* Shared body for the API reference routes. An empty slug renders the type index for + the area; a slug renders a single type via the shared ApiReferenceView. The /console + and /cli prefixes mean MainLayout already supplies the correct section header + sidebar. *@ +@using Pennington.ApiMetadata +@inject ApiReferenceService Api +@inject PenningtonOptions PenningtonOptions + + + + + +@PenningtonOptions.SiteTitle - @_title + +
+ + +
+ @if (_notFound) + { +
+

No API reference page was found for @Slug.

+

Back to the @_areaLabel API reference index.

+
+ } + else if (_typePage is not null) + { + + } + else + { +
+

+ Auto-generated reference for every public, documented type in @_areaLabel. +

+ @foreach (var group in _groups) + { +

@group.Key

+ + } +
+ } +
+
+ +@code { + /// API metadata registration key — "console" or "cli". + [Parameter, EditorRequired] public required string Area { get; set; } + + /// Catch-all route remainder after /{area}/api/; empty for the index. + [Parameter] public string? Slug { get; set; } + + private string _title = "API Reference"; + private string? _description; + private string _areaLabel = ""; + private bool _notFound; + private ApiTypePage? _typePage; + private List> _groups = []; + + protected override async Task OnInitializedAsync() + { + _areaLabel = Area.Equals("cli", StringComparison.OrdinalIgnoreCase) + ? "Spectre.Console.Cli" + : "Spectre.Console"; + + var slug = (Slug ?? string.Empty).Trim('/'); + if (string.IsNullOrEmpty(slug)) + { + _title = $"{_areaLabel} API Reference"; + _description = $"Browse the public API of {_areaLabel}."; + var types = await Api.GetTypesAsync(Area); + _groups = types + .GroupBy(t => string.IsNullOrEmpty(t.Namespace) ? "(global namespace)" : t.Namespace) + .OrderBy(g => g.Key, StringComparer.Ordinal) + .ToList(); + return; + } + + _typePage = await Api.GetTypePageAsync(Area, slug); + if (_typePage is null) + { + _notFound = true; + _title = "Not found"; + return; + } + + _title = ApiReferenceService.DisplayName(_typePage.Summary); + _description = _typePage.Summary.Summary; + } +} diff --git a/Spectre.Docs/Components/Pages/Blog.razor b/Spectre.Docs/Components/Pages/Blog.razor index 00278c5..a671e78 100644 --- a/Spectre.Docs/Components/Pages/Blog.razor +++ b/Spectre.Docs/Components/Pages/Blog.razor @@ -1,4 +1,4 @@ -@page "/blog" +@page "/blog/" @using Spectre.Console @layout BlogLayout @inject IMarkdownContentService BlogService @@ -63,7 +63,7 @@ }
- @@ -80,7 +80,7 @@ protected override async Task OnInitializedAsync() { - _posts = await BlogService.GetAllContentPagesAsync(); - _posts = _posts.Where(p => !p.FrontMatter.IsDraft).ToList(); + var all = await BlogService.GetAllContentPagesAsync(); + _posts = all.Where(p => !p.FrontMatter.IsDraft).ToList(); } } \ No newline at end of file diff --git a/Spectre.Docs/Components/Pages/BlogDetails.razor b/Spectre.Docs/Components/Pages/BlogDetails.razor index 3bac725..8c3e6e3 100644 --- a/Spectre.Docs/Components/Pages/BlogDetails.razor +++ b/Spectre.Docs/Components/Pages/BlogDetails.razor @@ -2,6 +2,7 @@ @using Spectre.Console @layout BlogLayout @inject IMarkdownContentService BlogService +@inject NavigationManager NavigationManager @if (_post == null) { @@ -35,7 +36,8 @@ protected override async Task OnInitializedAsync() { - var page = await BlogService.GetRenderedContentPageByUrlOrDefault(FileName); + var url = "/blog/" + (FileName ?? string.Empty).Trim('/'); + var page = await BlogService.GetRenderedContentPageByUrlOrDefault(url); if (page == null) { _post = null; @@ -43,8 +45,8 @@ return; } - _post = page.Value.Page; - _postContent = page.Value.HtmlContent; + _post = page.Page; + _postContent = page.HtmlContent; if (_post != null && !string.IsNullOrWhiteSpace(_post.FrontMatter.Series)) { diff --git a/Spectre.Docs/Components/Pages/CliApiPage.razor b/Spectre.Docs/Components/Pages/CliApiPage.razor new file mode 100644 index 0000000..e1a6245 --- /dev/null +++ b/Spectre.Docs/Components/Pages/CliApiPage.razor @@ -0,0 +1,7 @@ +@page "/cli/reference/api/{*slug}" + + + +@code { + [Parameter] public string? Slug { get; set; } +} diff --git a/Spectre.Docs/Components/Pages/CliIndex.razor b/Spectre.Docs/Components/Pages/CliIndex.razor index c4d8f5f..e3812db 100644 --- a/Spectre.Docs/Components/Pages/CliIndex.razor +++ b/Spectre.Docs/Components/Pages/CliIndex.razor @@ -1,9 +1,9 @@ -@page "/cli" +@page "/cli/" @using Spectre.Console @inject IMarkdownContentService CliContentServices -@inject ContentEngineOptions ContentEngineOptions +@inject PenningtonOptions PenningtonOptions -Spectre.Console.Cli Documentation - @ContentEngineOptions.SiteTitle +Spectre.Console.Cli Documentation - @PenningtonOptions.SiteTitle @@ -20,13 +20,13 @@ dotnet add package Spectre.Console.Cli

Then try this quick example that creates a simple greeting command:

- - T:Spectre.Docs.Cli.Examples.DemoApps.QuickStart.GreetSettings - T:Spectre.Docs.Cli.Examples.DemoApps.QuickStart.GreetCommand + + Spectre.Docs.Cli.Examples/DemoApps/QuickStart/HomePageExample.cs > GreetSettings + Spectre.Docs.Cli.Examples/DemoApps/QuickStart/HomePageExample.cs > GreetCommand

In your Program.cs, wire it up:

- M:Spectre.Docs.Cli.Examples.DemoApps.QuickStart.HomePageExample.Run(System.String[]) + Spectre.Docs.Cli.Examples/DemoApps/QuickStart/HomePageExample.cs > HomePageExample.Run

Run it with dotnet run -- World --count 3 to see typed argument parsing in action.

diff --git a/Spectre.Docs/Components/Pages/CliPage.razor b/Spectre.Docs/Components/Pages/CliPage.razor index edf5021..656552c 100644 --- a/Spectre.Docs/Components/Pages/CliPage.razor +++ b/Spectre.Docs/Components/Pages/CliPage.razor @@ -1,9 +1,8 @@ @page "/cli/{*fileName:nonfile}" -@using MyLittleContentEngine.Services.Content.TableOfContents @using Spectre.Console @inject IMarkdownContentService CliMarkdownContentServices -@inject ContentEngineOptions ContentEngineOptions -@inject ITableOfContentService TableOfContentService +@inject PenningtonOptions PenningtonOptions +@inject TableOfContentsService TocService @@ -14,16 +13,13 @@ Outline="@_outline" NavigationInfo="@_navigationInfo" Description="@_description" - Tags="@_tags" - SiteTitle="@ContentEngineOptions.SiteTitle" - Theme="tertiary-one" /> + SiteTitle="@PenningtonOptions.SiteTitle" /> @code { private string? _postContent; private OutlineEntry[]? _outline; private NavigationInfo? _navigationInfo; private string? _description; - private IEnumerable? _tags; [Parameter] public required string FileName { get; init; } = string.Empty; @@ -35,17 +31,16 @@ fileName = "index"; } - var url = $"cli/{fileName}"; + var url = $"/cli/{fileName}"; var renderedPage = await CliMarkdownContentServices.GetRenderedContentPageByUrlOrDefault(url); if (renderedPage != null) { - _postContent = renderedPage.Value.HtmlContent; - _outline = renderedPage.Value.Page.Outline; - _description = renderedPage.Value.Page.FrontMatter.Description; - _tags = renderedPage.Value.Page.FrontMatter.Tags; + _postContent = renderedPage.HtmlContent; + _outline = renderedPage.Page.Outline; + _description = renderedPage.Page.FrontMatter.Description; - _navigationInfo = await TableOfContentService.GetNavigationInfoAsync($"/{url}"); + _navigationInfo = await TocService.GetNavigationInfoAsync(url); } } } diff --git a/Spectre.Docs/Components/Pages/ConsoleApiPage.razor b/Spectre.Docs/Components/Pages/ConsoleApiPage.razor new file mode 100644 index 0000000..814126c --- /dev/null +++ b/Spectre.Docs/Components/Pages/ConsoleApiPage.razor @@ -0,0 +1,7 @@ +@page "/console/reference/api/{*slug}" + + + +@code { + [Parameter] public string? Slug { get; set; } +} diff --git a/Spectre.Docs/Components/Pages/ConsoleIndex.razor b/Spectre.Docs/Components/Pages/ConsoleIndex.razor index 00c1ba0..da4fb54 100644 --- a/Spectre.Docs/Components/Pages/ConsoleIndex.razor +++ b/Spectre.Docs/Components/Pages/ConsoleIndex.razor @@ -1,9 +1,9 @@ -@page "/console" +@page "/console/" @using Spectre.Console @inject IMarkdownContentService ConsoleContentServices -@inject ContentEngineOptions ContentEngineOptions +@inject PenningtonOptions PenningtonOptions -Spectre.Console Documentation - @ContentEngineOptions.SiteTitle +Spectre.Console Documentation - @PenningtonOptions.SiteTitle @@ -20,7 +20,7 @@ dotnet add package Spectre.Console

Then try this quick example that demonstrates styled text, a table, and a status spinner:

- M:Spectre.Docs.Examples.Showcase.QuickStartSample.Run(Spectre.Console.IAnsiConsole) + Spectre.Docs.Examples/Showcase/QuickStartSample.cs > QuickStartSample.Run

Your application should look something like this:

@@ -58,6 +58,7 @@ @code { private Dictionary>> _groupedPages = new(); + private static readonly Dictionary SectionNames = new() { { "tutorials", "Tutorials" }, diff --git a/Spectre.Docs/Components/Pages/ConsolePage.razor b/Spectre.Docs/Components/Pages/ConsolePage.razor index 962c4ff..145f48d 100644 --- a/Spectre.Docs/Components/Pages/ConsolePage.razor +++ b/Spectre.Docs/Components/Pages/ConsolePage.razor @@ -1,9 +1,8 @@ @page "/console/{*fileName:nonfile}" -@using MyLittleContentEngine.Services.Content.TableOfContents @using Spectre.Console @inject IMarkdownContentService ConsoleMarkdownContentServices -@inject ContentEngineOptions ContentEngineOptions -@inject ITableOfContentService TableOfContentService +@inject PenningtonOptions PenningtonOptions +@inject TableOfContentsService TocService @@ -14,16 +13,13 @@ Outline="@_outline" NavigationInfo="@_navigationInfo" Description="@_description" - Tags="@_tags" - SiteTitle="@ContentEngineOptions.SiteTitle" - Theme="primary" /> + SiteTitle="@PenningtonOptions.SiteTitle" /> @code { private string? _postContent; private OutlineEntry[]? _outline; private NavigationInfo? _navigationInfo; private string? _description; - private IEnumerable? _tags; [Parameter] public required string FileName { get; init; } = string.Empty; @@ -35,17 +31,16 @@ fileName = "index"; } - var url = $"console/{fileName}"; + var url = $"/console/{fileName}"; var renderedPage = await ConsoleMarkdownContentServices.GetRenderedContentPageByUrlOrDefault(url); if (renderedPage != null) { - _postContent = renderedPage.Value.HtmlContent; - _outline = renderedPage.Value.Page.Outline; - _description = renderedPage.Value.Page.FrontMatter.Description; - _tags = renderedPage.Value.Page.FrontMatter.Tags; + _postContent = renderedPage.HtmlContent; + _outline = renderedPage.Page.Outline; + _description = renderedPage.Page.FrontMatter.Description; - _navigationInfo = await TableOfContentService.GetNavigationInfoAsync($"/{url}"); + _navigationInfo = await TocService.GetNavigationInfoAsync(url); } } } diff --git a/Spectre.Docs/Components/Pages/Home.razor b/Spectre.Docs/Components/Pages/Home.razor index 8cc0d6c..648e924 100644 --- a/Spectre.Docs/Components/Pages/Home.razor +++ b/Spectre.Docs/Components/Pages/Home.razor @@ -1,190 +1,156 @@ @page "/" -@using System.Collections.Immutable -@using Spectre.Console -@inject ContentEngineOptions ContentEngineOptions -@inject IMarkdownContentService ConsoleContentServices -@inject IMarkdownContentService CliContentServices +@inject PenningtonOptions PenningtonOptions -@ContentEngineOptions.SiteTitle +@PenningtonOptions.SiteTitle -
- -
-
-

- +
+ + +
+ @* Decorative backdrops: soft sky glow + faint ghost watermark behind the headline. + Offsets/opacity/mask are inline so they don't depend on arbitrary-value utilities. *@ + + + +
+ + v0.55 + A .NET library for console applications + + +

+ Spectre.Console

-

- Beautiful console applications with Spectre.Console + +

+ Formatted, interactive console output in C# — + tables, + charts, + prompts, and + live displays.

-
-
- -
- +
+ +
+

- -
- -
-
-
-
- - - -
-

Console Documentation

-
+ + - - View all Console docs - - - - -
-
- - -
-
-
-
- - - -
-

CLI Documentation

-
+ +
+
Quick start
+

Install and start rendering

+

+ Targets .NET Standard 2.0 through .NET 10. Add the package and write to + AnsiConsole. +

+ +
+ @foreach (var pkg in Packages) + { +
+
-

- Build powerful command-line applications with type-safe commands, argument parsing, and dependency - injection. -

- - -
-

- Get Started -

- + Install @pkg.Name +
+
$ dotnet add package @pkg.Name
+ } +
- - View all CLI docs - - - - -
-
-
- - -
-

Quick Start

-
-
-

- - - - Install Spectre.Console -

-
dotnet package add Spectre.Console
-
-
-

- - - - Install Spectre.Console.Cli -

-
dotnet package add Spectre.Console.Cli
-
+
+
@code { - private ImmutableList> _consolePages = []; - private ImmutableList> _cliPages = []; - - protected override async Task OnInitializedAsync() - { - _consolePages = await ConsoleContentServices.GetAllContentPagesAsync(); - _cliPages = await CliContentServices.GetAllContentPagesAsync(); - - await base.OnInitializedAsync(); - } - -} \ No newline at end of file + private const string PrimaryBtn = + "inline-flex items-center justify-center gap-2 rounded-lg bg-primary-600 hover:bg-primary-500 text-white font-medium px-5 py-2.5 text-sm transition-colors"; + + private const string GhostBtn = + "inline-flex items-center justify-center gap-2 rounded-lg border border-base-300 dark:border-base-700 bg-white dark:bg-base-800 text-base-700 dark:text-base-200 hover:border-base-400 dark:hover:border-base-600 px-5 py-2.5 text-sm font-medium transition-colors"; + + private record Widget(string Kicker, string Title, string Desc, string Svg, string TermTitle, string Span); + + // 12-column bento: row 1 = 7+5, row 2 = 4+4+4, row 3 = 5+4+3. Each SVG's Cols is sized + // proportional to its span (see the .tape files) so the fixed-size terminal sits sensibly in + // its card without overflowing the narrow ones. Span class strings are literals so MonorailCss + // detects them. + private static readonly Widget[] Widgets = + [ + new("Table", "Tables", "Columns size themselves, cells wrap, and borders are configurable — 20+ styles.", "landing-table", "dotnet run", "lg:col-span-7"), + new("Calendar", "Calendar", "A highlightable month view with events.", "landing-calendar", "cal", "lg:col-span-5"), + new("BarChart", "Charts", "Bar, breakdown, and spark charts written straight to stdout.", "landing-bar-chart", "chart", "lg:col-span-4"), + new("Progress", "Progress", "Multi-task bars that update in place without flicker.", "landing-progress", "build", "lg:col-span-4"), + new("Tree", "Trees", "Render nested structures with connecting guides.", "landing-tree", "tree", "lg:col-span-4"), + new("Panel", "Panels", "Wrap any renderable in a titled, styled border.", "landing-panel", "news", "lg:col-span-5"), + new("Prompt", "Prompts", "Selection, multi-select, and text prompts with built-in validation.", "landing-prompt", "setup", "lg:col-span-4"), + new("Status", "Status & spinners", "80+ spinner styles for long-running work.", "landing-status", "deploy", "lg:col-span-3"), + ]; + + private record Package(string Name); + + private static readonly Package[] Packages = + [ + new("Spectre.Console"), + new("Spectre.Console.Cli"), + ]; +} diff --git a/Spectre.Docs/Components/Reference/ApiParameterGrid.razor b/Spectre.Docs/Components/Reference/ApiParameterGrid.razor new file mode 100644 index 0000000..cf76bb3 --- /dev/null +++ b/Spectre.Docs/Components/Reference/ApiParameterGrid.razor @@ -0,0 +1,29 @@ +@* Parameter table for a member card. Markup matches the original WidgetApiReference. *@ +@if (Parameters.Count > 0) +{ +
+

Parameters:

+
+ @foreach (var param in Parameters) + { +
+ @param.Name + @if (!string.IsNullOrEmpty(param.Type)) + { + (@param.Type) + } +
+
+ @if (!string.IsNullOrEmpty(param.DescriptionHtml)) + { + @((MarkupString)param.DescriptionHtml) + } +
+ } +
+
+} + +@code { + [Parameter, EditorRequired] public required IReadOnlyList Parameters { get; set; } +} diff --git a/Spectre.Docs/Components/Reference/ApiReferenceView.razor b/Spectre.Docs/Components/Reference/ApiReferenceView.razor new file mode 100644 index 0000000..e7c2918 --- /dev/null +++ b/Spectre.Docs/Components/Reference/ApiReferenceView.razor @@ -0,0 +1,116 @@ +@* Shared presentation for an API reference type. Markup mirrors the original + WidgetApiReference so the inline widget and the full /console/api · /cli/api + pages render identically; data comes from ApiReferenceModel. *@ + +
+ @if (!string.IsNullOrEmpty(Model.SummaryHtml)) + { +

@((MarkupString)Model.SummaryHtml)

+ } + + @if (Model.Constructors.Count > 0) + { +
+

Constructors

+
+ @foreach (var ctor in Model.Constructors) + { +
+
+ @((MarkupString)ctor.SignatureHtml) +
+ @if (!string.IsNullOrEmpty(ctor.SummaryHtml)) + { +

@((MarkupString)ctor.SummaryHtml)

+ } + +
+ } +
+
+ } + + @if (Model.Properties.Count > 0) + { +
+

Properties

+
+ @foreach (var prop in Model.Properties) + { +
+
+ @prop.Name + : @prop.Type +
+ @if (!string.IsNullOrEmpty(prop.SummaryHtml)) + { +

@((MarkupString)prop.SummaryHtml)

+ } +
+ } +
+
+ } + + @if (Model.Methods.Count > 0) + { +
+

Methods

+
+ @foreach (var method in Model.Methods) + { +
+
+ @((MarkupString)method.SignatureHtml) +
+ @if (!string.IsNullOrEmpty(method.SummaryHtml)) + { +

@((MarkupString)method.SummaryHtml)

+ } + + @if (!string.IsNullOrEmpty(method.ReturnsHtml)) + { +
+

Returns:

+

@((MarkupString)method.ReturnsHtml)

+
+ } +
+ } +
+
+ } + + @if (Model.ExtensionMethods.Count > 0) + { +
+

Extension Methods

+
+ @foreach (var ext in Model.ExtensionMethods) + { +
+
+ @((MarkupString)ext.SignatureHtml) +
+ @if (!string.IsNullOrEmpty(ext.SummaryHtml)) + { +

@((MarkupString)ext.SummaryHtml)

+ } + + @if (!string.IsNullOrEmpty(ext.ReturnsHtml)) + { +
+

Returns:

+

@((MarkupString)ext.ReturnsHtml)

+
+ } +
+ } +
+
+ } +
+ +@code { + [Parameter, EditorRequired] public required ApiReferenceModel Model { get; set; } +} diff --git a/Spectre.Docs/Components/Reference/SpinnerList.razor b/Spectre.Docs/Components/Reference/SpinnerList.razor index 9a0f90c..3f749e6 100644 --- a/Spectre.Docs/Components/Reference/SpinnerList.razor +++ b/Spectre.Docs/Components/Reference/SpinnerList.razor @@ -48,30 +48,48 @@ else } @code { diff --git a/Spectre.Docs/Components/Reference/WidgetApiReference.razor b/Spectre.Docs/Components/Reference/WidgetApiReference.razor index 19c52df..084d3f6 100644 --- a/Spectre.Docs/Components/Reference/WidgetApiReference.razor +++ b/Spectre.Docs/Components/Reference/WidgetApiReference.razor @@ -1,6 +1,5 @@ -@using System.Reflection @using Spectre.Docs.Services -@inject XmlDocumentationService XmlDocs +@inject ApiReferenceService Api @if (_isLoading) { @@ -14,227 +13,23 @@ return; } -
- @* Type Summary *@ - @if (!string.IsNullOrEmpty(_typeSummary)) - { -

@_typeSummary

- - } - - @* Constructors *@ - @if (_constructors.Any()) - { -
-

- Constructors -

-
- @foreach (var ctor in _constructors) - { -
-
- - @ctor.Signature - -
- @if (!string.IsNullOrEmpty(ctor.Summary)) - { -

@ctor.Summary

- } - @if (ctor.Parameters.Any()) - { -
-

Parameters:

-
- @foreach (var param in ctor.Parameters) - { -
- @param.Name - @if (!string.IsNullOrEmpty(param.Type)) - { - (@param.Type) - } -
-
- @if (!string.IsNullOrEmpty(param.Description)) - { - @param.Description - } -
- } -
-
- } -
- } -
-
- } - - @* Properties *@ - @if (_properties.Any()) - { -
-

- Properties -

-
- @foreach (var prop in _properties) - { -
-
- - @prop.Name - - : @prop.Type -
- @if (!string.IsNullOrEmpty(prop.Summary)) - { -

@prop.Summary

- } -
- } -
-
- } - - @* Methods *@ - @if (_methods.Any()) - { -
-

- Methods -

-
- @foreach (var method in _methods) - { -
-
- - @method.Signature - -
- @if (!string.IsNullOrEmpty(method.Summary)) - { -

@method.Summary

- } - @if (method.Parameters.Any()) - { -
-

Parameters:

-
- @foreach (var param in method.Parameters) - { -
- @param.Name - @if (!string.IsNullOrEmpty(param.Type)) - { - (@param.Type) - } -
-
- @if (!string.IsNullOrEmpty(param.Description)) - { - @param.Description - } -
- } -
-
- } - @if (!string.IsNullOrEmpty(method.Returns)) - { -
-

Returns:

-

@method.Returns

-
- } -
- } -
-
- } - - @* Extension Methods *@ - @if (_extensionMethods.Any()) - { -
-

- Extension Methods -

-
- @foreach (var ext in _extensionMethods) - { -
-
- - @ext.Signature - -
- @if (!string.IsNullOrEmpty(ext.Summary)) - { -

@ext.Summary

- } - @if (ext.Parameters.Any()) - { -
-

Parameters:

-
- @foreach (var param in ext.Parameters) - { -
- @param.Name - @if (!string.IsNullOrEmpty(param.Type)) - { - (@param.Type) - } -
-
- @if (!string.IsNullOrEmpty(param.Description)) - { - @param.Description - } -
- } -
-
- } - @if (!string.IsNullOrEmpty(ext.Returns)) - { -
-

Returns:

-

@ext.Returns

-
- } -
- } -
-
- } -
+@if (_model is not null) +{ + +} @code { + /// Reflection-style full type name, e.g. Spectre.Console.Table or Spectre.Console.TextPrompt`1. [Parameter] public string TypeName { get; set; } = string.Empty; + /// API metadata registration key the type belongs to. All inline widget docs are console types. + [Parameter] public string Area { get; set; } = "console"; + private bool _isLoading = true; private string? _error; - private string? _typeSummary; - private readonly List _constructors = []; - private readonly List _properties = []; - private readonly List _methods = []; - private readonly List _extensionMethods = []; + private ApiReferenceModel? _model; protected override async Task OnInitializedAsync() - { - await Task.Run(LoadApiReference); - } - - private void LoadApiReference() { try { @@ -244,35 +39,11 @@ return; } - // Get the type from Spectre assemblies - Type? type = null; - foreach (var assembly in XmlDocumentationService.Assemblies) - { - type = assembly.GetType(TypeName); - if (type != null) break; - } - - if (type == null) + _model = await Api.GetModelByTypeNameAsync(Area, TypeName); + if (_model is null) { - _error = $"Type '{TypeName}' not found in Spectre assemblies"; - return; + _error = $"No documented API reference found for '{TypeName}'."; } - - // Load type summary - var typeDoc = XmlDocs.GetDocumentation(type); - _typeSummary = typeDoc?.Summary; - - // Load constructors - LoadConstructors(type); - - // Load properties - LoadProperties(type); - - // Load methods - LoadMethods(type); - - // Load extension methods - LoadExtensionMethods(type); } catch (Exception ex) { @@ -283,175 +54,4 @@ _isLoading = false; } } - - private void LoadConstructors(Type type) - { - var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance) - .OrderBy(c => c.GetParameters().Length) - .ToList(); - - foreach (var ctor in constructors) - { - var doc = XmlDocs.GetDocumentation(ctor); - var parameters = ctor.GetParameters(); - - var paramInfos = parameters.Select(p => - { - var paramDoc = doc?.Parameters.FirstOrDefault(pd => pd.Name == p.Name); - return new ParameterApiInfo( - p.Name ?? "", - GetFriendlyTypeName(p.ParameterType), - paramDoc?.Description); - }).ToList(); - - var signature = $"{type.Name}({string.Join(", ", parameters.Select(p => $"{GetFriendlyTypeName(p.ParameterType)} {p.Name}"))})"; - - _constructors.Add(new ConstructorApiInfo( - signature, - doc?.Summary, - paramInfos)); - } - } - - private void LoadProperties(Type type) - { - var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) - .OrderBy(p => p.Name) - .ToList(); - - foreach (var prop in properties) - { - var doc = XmlDocs.GetDocumentation(prop); - - _properties.Add(new PropertyApiInfo( - prop.Name, - GetFriendlyTypeName(prop.PropertyType), - doc?.Summary)); - } - } - - private void LoadMethods(Type type) - { - var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) - .Where(m => !m.IsSpecialName) // Exclude property getters/setters - .OrderBy(m => m.Name) - .ToList(); - - foreach (var method in methods) - { - var doc = XmlDocs.GetDocumentation(method); - var parameters = method.GetParameters(); - - var paramInfos = parameters.Select(p => - { - var paramDoc = doc?.Parameters.FirstOrDefault(pd => pd.Name == p.Name); - return new ParameterApiInfo( - p.Name ?? "", - GetFriendlyTypeName(p.ParameterType), - paramDoc?.Description); - }).ToList(); - - var signature = $"{method.Name}({string.Join(", ", parameters.Select(p => $"{GetFriendlyTypeName(p.ParameterType)} {p.Name}"))})"; - if (method.ReturnType != typeof(void)) - { - signature = $"{GetFriendlyTypeName(method.ReturnType)} {signature}"; - } - - _methods.Add(new MethodApiInfo( - signature, - doc?.Summary, - doc?.Returns, - paramInfos)); - } - } - - private void LoadExtensionMethods(Type type) - { - var extensionMethods = XmlDocumentationService.Assemblies - .SelectMany(GetLoadableTypes) - .Where(t => t.IsSealed && !t.IsGenericType && !t.IsNested) - .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public)) - .Where(m => m.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false)) - .Where(m => m.GetParameters().Length > 0 && - (m.GetParameters()[0].ParameterType == type || - m.GetParameters()[0].ParameterType.IsAssignableFrom(type))) - .OrderBy(m => m.Name) - .ToList(); - - foreach (var method in extensionMethods) - { - var doc = XmlDocs.GetDocumentation(method); - var parameters = method.GetParameters().Skip(1).ToArray(); // Skip 'this' parameter - - var paramInfos = parameters.Select(p => - { - var paramDoc = doc?.Parameters.FirstOrDefault(pd => pd.Name == p.Name); - return new ParameterApiInfo( - p.Name ?? "", - GetFriendlyTypeName(p.ParameterType), - paramDoc?.Description); - }).ToList(); - - var signature = $"{method.Name}({string.Join(", ", parameters.Select(p => $"{GetFriendlyTypeName(p.ParameterType)} {p.Name}"))})"; - if (method.ReturnType != typeof(void)) - { - signature = $"{GetFriendlyTypeName(method.ReturnType)} {signature}"; - } - - _extensionMethods.Add(new ExtensionMethodApiInfo( - signature, - doc?.Summary, - doc?.Returns, - paramInfos, - method.DeclaringType?.FullName ?? "Unknown")); - } - } - - private static IEnumerable GetLoadableTypes(Assembly assembly) - { - try - { - return assembly.GetTypes(); - } - catch (ReflectionTypeLoadException ex) - { - return ex.Types.Where(t => t != null)!; - } - } - - private string GetFriendlyTypeName(Type type) - { - if (type == typeof(void)) return "void"; - if (type == typeof(int)) return "int"; - if (type == typeof(string)) return "string"; - if (type == typeof(bool)) return "bool"; - if (type == typeof(double)) return "double"; - if (type == typeof(float)) return "float"; - if (type == typeof(decimal)) return "decimal"; - if (type == typeof(long)) return "long"; - if (type == typeof(short)) return "short"; - if (type == typeof(byte)) return "byte"; - if (type == typeof(object)) return "object"; - - if (type.IsGenericType) - { - var genericTypeName = type.Name.Split('`')[0]; - var genericArgs = type.GetGenericArguments(); - var genericArgNames = string.Join(", ", genericArgs.Select(GetFriendlyTypeName)); - return $"{genericTypeName}<{genericArgNames}>"; - } - - return type.Name; - } - - private record ConstructorApiInfo(string Signature, string? Summary, List Parameters); - - private record PropertyApiInfo(string Name, string Type, string? Summary); - - private record MethodApiInfo(string Signature, string? Summary, string? Returns, List Parameters); - - private record ExtensionMethodApiInfo(string Signature, string? Summary, string? Returns, List Parameters, string DeclaringType); - - private record ParameterApiInfo(string Name, string Type, string? Description); - } diff --git a/Spectre.Docs/Components/Shared/DocumentationPageContent.razor b/Spectre.Docs/Components/Shared/DocumentationPageContent.razor index 8cfa250..93361e2 100644 --- a/Spectre.Docs/Components/Shared/DocumentationPageContent.razor +++ b/Spectre.Docs/Components/Shared/DocumentationPageContent.razor @@ -1,5 +1,3 @@ -@using MyLittleContentEngine.Services.Content.TableOfContents - @if (PostContent == null || NavigationInfo == null) {
Not found
@@ -8,27 +6,38 @@ @SiteTitle - @NavigationInfo.PageTitle - - - - - -
- @((MarkupString)PostContent) -
-
-
+
+ + +
+
+
+ @((MarkupString)PostContent) +
+
+ + @if (Outline is { Length: > 0 }) + { + + } +
+
@code { [Parameter] public string? PostContent { get; set; } [Parameter] public OutlineEntry[]? Outline { get; set; } [Parameter] public NavigationInfo? NavigationInfo { get; set; } [Parameter] public string? Description { get; set; } - [Parameter] public IEnumerable? Tags { get; set; } [Parameter] public required string SiteTitle { get; set; } - [Parameter] public required string Theme { get; set; } } diff --git a/Spectre.Docs/Components/Shared/Ghost.razor b/Spectre.Docs/Components/Shared/Ghost.razor new file mode 100644 index 0000000..c30d57a --- /dev/null +++ b/Spectre.Docs/Components/Shared/Ghost.razor @@ -0,0 +1,72 @@ +@* Spectre.Console ghost mascot. fill="currentColor" so callers set the color with text-* utilities. + Reused by the nav brand tile (SharedHeader) and the hero watermark (Home). *@ + + +@code { + /// CSS classes applied to the root svg (size + color via text-* utilities). + [Parameter] public string? Class { get; set; } + + /// Any extra attributes (e.g. role, title) are forwarded to the svg. + [Parameter(CaptureUnmatchedValues = true)] + public IDictionary? Extra { get; set; } +} diff --git a/Spectre.Docs/Components/Shared/PageHeader.razor b/Spectre.Docs/Components/Shared/PageHeader.razor index 6c8dcfb..166adf1 100644 --- a/Spectre.Docs/Components/Shared/PageHeader.razor +++ b/Spectre.Docs/Components/Shared/PageHeader.razor @@ -23,16 +23,4 @@ /// [Parameter] public string? Description { get; set; } - - /// - /// Optional tags to display as colored badges. - /// - [Parameter] - public IEnumerable? Tags { get; set; } - - /// - /// Theme name for tag coloring (e.g., "primary", "tertiary-one"). - /// - [Parameter] - public string Theme { get; set; } = "primary"; } diff --git a/Spectre.Docs/Components/Shared/PageWrapper.razor b/Spectre.Docs/Components/Shared/PageWrapper.razor deleted file mode 100644 index 8c596a9..0000000 --- a/Spectre.Docs/Components/Shared/PageWrapper.razor +++ /dev/null @@ -1,60 +0,0 @@ -@using MyLittleContentEngine.Services.Content.TableOfContents - -
- @if (HeaderContent != null) - { - @HeaderContent - } - - -
- -
- @ChildContent -
- - - @if (Outline is { Length: > 0 }) - { - - } -
-
- - - -@code { - /// - /// The main content to display in the page body. - /// - [Parameter] - public RenderFragment? ChildContent { get; set; } - - /// - /// Optional header content (typically a PageHeader component). - /// - [Parameter] - public RenderFragment? HeaderContent { get; set; } - - /// - /// Optional outline entries for "On this page" navigation. - /// - [Parameter] - public OutlineEntry[]? Outline { get; set; } -} diff --git a/Spectre.Docs/Components/Shared/Screenshot.razor b/Spectre.Docs/Components/Shared/Screenshot.razor index b84e7a8..d7aec83 100644 --- a/Spectre.Docs/Components/Shared/Screenshot.razor +++ b/Spectre.Docs/Components/Shared/Screenshot.razor @@ -1,20 +1,41 @@ @inject IWebHostEnvironment Environment -
-
- -
-
-
-
+
+
+ +
+ @Title +
-
+
@{ - var imgStyle = _aspectRatio != null - ? $"width: 100%; aspect-ratio: {_aspectRatio};" - : "width: 100%;"; + // Bare (gallery) SVGs are emitted by VCR with intrinsic width/height at a fixed + // FontSize, so we display them at natural size (centered, capped to the card). The + // terminal *window* stretches with its bento card, but the SVG inside does not — the + // font stays a constant ~13px regardless of how wide the card grows. Docs screenshots + // keep the responsive width:100% behaviour (+ parsed aspect-ratio). + var imgStyle = Bare + ? "max-width: 100%; height: auto;" + : (_aspectRatio != null + ? $"width: 100%; aspect-ratio: {_aspectRatio};" + : "width: 100%;"); + } + @Alt + @if (Dots) + { + } - @Alt
@@ -26,8 +47,51 @@ [Parameter] public string Alt { get; set; } = "Screenshot"; + /// Optional terminal title shown on the left of the window bar (e.g. "status — dotnet"). + [Parameter] + public string? Title { get; set; } + + /// + /// When true, drops the heavy outer chrome (shadow, padded frame, bleed margins) so the terminal + /// can sit flush inside another card (e.g. the homepage widget gallery). The terminal keeps a thin + /// border and its dark interior so it still reads as a window. + /// + [Parameter] + public bool Bare { get; set; } + + /// + /// When true, overlays a subtle 8-bit phosphor "dot mask" over the terminal screen — a faint CRT + /// texture that adds a little character to hero shots. Purely decorative: it sits above the image + /// with pointer-events:none and aria-hidden, so it never affects layout or a11y. + /// + [Parameter] + public bool Dots { get; set; } = true; + private string? _aspectRatio; + private string _outerClass => Bare + ? "not-prose mt-4 max-w-3xl" + : "not-prose max-w-3xl bg-base-950/80 dark:bg-base-800 border-1 dark:border-base-500/10 border-base-500/50 rounded-lg lg:rounded-xl p-1 lg:p-2 mt-4 -mx-3 lg:mx-0 shadow-sm lg:shadow-xl"; + + private string _frameClass => Bare + ? "not-prose flex flex-col rounded-lg overflow-hidden border border-base-700/60 bg-base-950 dark:bg-base-900" + : "not-prose flex flex-col rounded-lg lg:rounded-xl"; + + // Embedded (Bare) terminals use tighter horizontal padding so the SVG fills more of the + // card; this keeps the proportional font size consistent across the varied bento widths. + private string _bodyClass => + "relative rounded-lg inner-shadow py-2 px-2 bg-base-950/40 dark:bg-base-900 [background-image:repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(255,255,255,0.01)_2px,rgba(255,255,255,0.01)_4px)] " + + (Bare ? "lg:px-3" : "lg:px-6"); + + // Subtle 8-bit "phosphor" dot mask painted over the screen (above the image, below interactions). + // White dots at a low alpha on a 3px grid: the stipple only reads across the dark terminal interior + // and disappears against bright glyphs, so text stays crisp. Pairs with the body's faint scanlines + // (the repeating-linear-gradient above) to suggest a CRT shadow mask without being distracting. + private string _dotOverlayClass => + "pointer-events-none absolute inset-0 rounded-lg " + + "[background-image:radial-gradient(rgba(255,255,255,0.08)_0.5px,transparent_0.5px)] " + + "[background-size:3px_3px]"; + protected override void OnParametersSet() { _aspectRatio = null; diff --git a/Spectre.Docs/Components/Shared/SharedFooter.razor b/Spectre.Docs/Components/Shared/SharedFooter.razor index 9354961..30d5c85 100644 --- a/Spectre.Docs/Components/Shared/SharedFooter.razor +++ b/Spectre.Docs/Components/Shared/SharedFooter.razor @@ -1,5 +1,5 @@
-

© @DateTime.Now.Year Spectre.Console Documentation. Built with MyLittleContentEngine.

+

© @DateTime.Now.Year Spectre.Console Documentation. Built with Pennington.

\ No newline at end of file diff --git a/Spectre.Docs/Components/Shared/SharedHeader.razor b/Spectre.Docs/Components/Shared/SharedHeader.razor index ff87a0f..27aaac8 100644 --- a/Spectre.Docs/Components/Shared/SharedHeader.razor +++ b/Spectre.Docs/Components/Shared/SharedHeader.razor @@ -1,6 +1,6 @@ @inject NavigationManager NavigationManager -
-
+ @* Same SPA-region treatment as the desktop nav: only the link list swaps; the nav + element (which carries data-expanded toggled by JS) stays in place. *@