diff --git a/Spectre.Docs/Components/Layouts/MainLayout.razor b/Spectre.Docs/Components/Layouts/MainLayout.razor index cc711c4..c5100d1 100644 --- a/Spectre.Docs/Components/Layouts/MainLayout.razor +++ b/Spectre.Docs/Components/Layouts/MainLayout.razor @@ -43,9 +43,9 @@
-
+
@Body -
+
diff --git a/Spectre.Docs/Components/Pages/Blog.razor.metadata.yml b/Spectre.Docs/Components/Pages/Blog.razor.metadata.yml new file mode 100644 index 0000000..6268d34 --- /dev/null +++ b/Spectre.Docs/Components/Pages/Blog.razor.metadata.yml @@ -0,0 +1,4 @@ +# Metadata sidecar for the Razor landing page at "/blog/" so it projects a content record +# and gets a social card + og:image / description meta tags. +title: Blog +description: "News, updates, and insights from the Spectre.Console team." diff --git a/Spectre.Docs/Components/Pages/CliIndex.razor.metadata.yml b/Spectre.Docs/Components/Pages/CliIndex.razor.metadata.yml new file mode 100644 index 0000000..dd1e788 --- /dev/null +++ b/Spectre.Docs/Components/Pages/CliIndex.razor.metadata.yml @@ -0,0 +1,4 @@ +# Metadata sidecar for the Razor landing page at "/cli/" so it projects a content record +# and gets a social card + og:image / description meta tags. +title: Spectre.Console.Cli Documentation +description: "Build powerful command-line applications with Spectre.Console.Cli - type-safe argument parsing, nested commands, and dependency injection for .NET" diff --git a/Spectre.Docs/Components/Pages/ConsoleIndex.razor.metadata.yml b/Spectre.Docs/Components/Pages/ConsoleIndex.razor.metadata.yml new file mode 100644 index 0000000..9202baa --- /dev/null +++ b/Spectre.Docs/Components/Pages/ConsoleIndex.razor.metadata.yml @@ -0,0 +1,4 @@ +# Metadata sidecar for the Razor landing page at "/console/" so it projects a content record +# and gets a social card + og:image / description meta tags. +title: Spectre.Console Documentation +description: "Create beautiful, cross-platform console applications with Spectre.Console - tables, progress bars, charts, and rich text formatting for .NET" diff --git a/Spectre.Docs/Components/Pages/Home.razor.metadata.yml b/Spectre.Docs/Components/Pages/Home.razor.metadata.yml new file mode 100644 index 0000000..5149bb2 --- /dev/null +++ b/Spectre.Docs/Components/Pages/Home.razor.metadata.yml @@ -0,0 +1,4 @@ +# Metadata sidecar for the Razor landing page at "/". Makes the home page project a content +# record so it gets a social card (/social-cards/index.png) and the og:image / description meta tags. +title: Spectre.Console +description: "Beautiful, cross-platform console applications for .NET - tables, charts, prompts, and live displays." diff --git a/Spectre.Docs/Program.cs b/Spectre.Docs/Program.cs index 5fc6e50..140975d 100644 --- a/Spectre.Docs/Program.cs +++ b/Spectre.Docs/Program.cs @@ -1,7 +1,11 @@ +using System.Reflection; +using Ashcroft; using Mdazor; using Pennington.ApiMetadata.Reflection; using Pennington.Infrastructure; +using Pennington.LlmsTxt; using Pennington.MonorailCss; +using Pennington.SocialCards; using Pennington.TreeSitter; using Pennington.UI.Components; using Spectre.Console; @@ -9,12 +13,19 @@ using Spectre.Docs.Components.Reference; using Spectre.Docs.Components.Shared; using Spectre.Docs.Services; +using Spectre.Docs.SocialCardAssets; using ColorName = Pennington.MonorailCss.ColorName; using IContentService = Pennington.Content.IContentService; using IContentRenderer = Pennington.Pipeline.IContentRenderer; var builder = WebApplication.CreateBuilder(args); +// The documented subject's version for the /llms.txt front door. Resolved from the referenced +// Spectre.Console assembly so it tracks package bumps; the + SourceLink suffix is stripped. +var spectreVersion = typeof(AnsiConsole).Assembly + .GetCustomAttribute()? + .InformationalVersion?.Split('+', 2)[0]; + builder.Services.AddRazorComponents(); // Typed views over the Pennington content pipeline that preserve the ergonomics the Razor @@ -43,7 +54,23 @@ { penn.SiteTitle = "Spectre.Console Documentation"; penn.SiteDescription = "Beautiful console applications with Spectre.Console"; + penn.SiteVersion = spectreVersion; // → llms.txt `version:` (replaces penningtonVersion:) + penn.CanonicalBaseUrl = "https://spectreconsole.net"; penn.ContentRootPath = "Content"; + penn.SiteProjection.ContentSelector = "article"; + + // OpenGraph / Twitter social cards. Pennington discovers one card per page, auto-maps the + // /social-cards/{**slug}.png route (via UsePennington), and emits the og:image / twitter:image + // meta tags; Ashcroft does the drawing — the branded PNG as the background with the page + // title set in JetBrains Mono, anchored bottom-left clear of the watermark on the right. + penn.SocialCards = new SocialCardOptions + { + Render = (request, sp, _) => + { + var environment = sp.GetRequiredService(); + return SocialCardGenerator.Build(request, environment); + }, + }; penn.AddMarkdownContent(md => { @@ -64,6 +91,8 @@ md.ContentPath = "Content/blog"; md.BasePageUrl = "/blog"; }); + + penn.AddLlmsTxt(); }); // Reflection-backed API metadata providers, one keyed registration per reference area. @@ -80,6 +109,13 @@ builder.Services.AddApiMetadataFromCompiledAssembly("cli", opts => opts.FromPackageReference("Spectre.Console.Cli")); +builder.Services.AddLlmsSubtree(new LlmsSubtree( + "/cli/reference/api/", "CLI reference", + "Spectre.Console.Cli — command, configuration, and settings types.")); +builder.Services.AddLlmsSubtree(new LlmsSubtree( + "/console/reference/api/", "Console reference", + "Spectre.Console — widgets, prompts, rendering, and markup types.")); + builder.Services.AddScoped(); // Supplies API route discovery (so the static build emits the pages) and the sidebar @@ -114,11 +150,7 @@ PrimaryColorName = ColorName.Sky, AccentColorName = ColorName.Amber, BaseColorName = ColorName.Neutral, - AdditionalMappings = - { - ["tertiary-one"] = ColorName.Emerald, - ["tertiary-two"] = ColorName.Violet, - }, + AdditionalMappings = { ["tertiary-one"] = ColorName.Emerald, ["tertiary-two"] = ColorName.Violet, }, }, }); @@ -130,4 +162,4 @@ app.UsePennington(); app.UseMonorailCss(); -await app.RunOrBuildAsync(args); +await app.RunOrBuildAsync(args); \ No newline at end of file diff --git a/Spectre.Docs/Services/ApiReferenceContentService.cs b/Spectre.Docs/Services/ApiReferenceContentService.cs index d4df642..8076a5c 100644 --- a/Spectre.Docs/Services/ApiReferenceContentService.cs +++ b/Spectre.Docs/Services/ApiReferenceContentService.cs @@ -65,8 +65,10 @@ public async Task> GetIndexableEntriesAsync() // The default would index only the nav entries (the two index pages), so a type that // appears nowhere else — e.g. IAnsiConsole — is unreachable via search, and types that do // appear only match the big index listing rather than their own page. Add one searchable - // entry per type so each type page surfaces directly. Kept out of nav (SearchOnly) and out - // of llms.txt (ExcludeFromLlms) to avoid bloating either with the full type list. + // entry per type so each type page surfaces directly. Kept out of nav (SearchOnly); they + // still feed llms.txt, where the per-area `/{area}/reference/api/` subtree (declared via + // AddLlmsSubtree in Program.cs) splits them into a dedicated {prefix}llms.txt rather than + // bloating the front door. var builder = ImmutableList.CreateBuilder(); builder.AddRange(await GetContentTocEntriesAsync()); @@ -82,7 +84,7 @@ public async Task> GetIndexableEntriesAsync() int.MaxValue, [area, "reference", "api"], area, - null) { Description = type.Summary, SearchOnly = true, ExcludeFromLlms = true }); + null) { Description = type.Summary, SearchOnly = true }); } } return builder.ToImmutable(); diff --git a/Spectre.Docs/SocialCardAssets/JetBrainsMono[wght].ttf b/Spectre.Docs/SocialCardAssets/JetBrainsMono[wght].ttf new file mode 100644 index 0000000..b60e77f Binary files /dev/null and b/Spectre.Docs/SocialCardAssets/JetBrainsMono[wght].ttf differ diff --git a/Spectre.Docs/SocialCardAssets/SocialCardGenerator.cs b/Spectre.Docs/SocialCardAssets/SocialCardGenerator.cs new file mode 100644 index 0000000..219bc37 --- /dev/null +++ b/Spectre.Docs/SocialCardAssets/SocialCardGenerator.cs @@ -0,0 +1,44 @@ +using Ashcroft; +using Pennington.SocialCards; + +namespace Spectre.Docs.SocialCardAssets; + +public static class SocialCardGenerator +{ + // Social-card assets live in the project's SocialCardAssets folder. Resolved against the + // content root, so the paths are correct under both `dotnet run` and `dotnet run -- build` + // (the static-build crawler fetches /social-cards/*.png from the in-memory host). + public static Task Build(SocialCardRequest socialCardRequest, IWebHostEnvironment environment) + { + var socialCardAssetsPath = Path.Combine(environment.ContentRootPath, "SocialCardAssets"); + var cardBackgroundPath = Path.Combine(socialCardAssetsPath, "spectre-og-card.png"); + var cardFontPath = Path.Combine(socialCardAssetsPath, "JetBrainsMono[wght].ttf"); + + var card = SocialCard.Create(socialCardRequest.Width, socialCardRequest.Height) + .Background(cardBackgroundPath) + .Theme(new Theme { FontPath = cardFontPath }) + .At(Anchor.BottomLeft, stack => + { + stack.MaxWidth(960); + + stack.Text(socialCardRequest.Title, new TextStyle { Size = 72, Weight = 800, MaxLines = 2, ShrinkToFit = true }); + if (!string.IsNullOrWhiteSpace(socialCardRequest.Description)) + { + stack.Text(socialCardRequest.Description, + new TextStyle + { + MaxLines = 2, + ShrinkToFit = true, + Size = 30, + Weight = 200, + LetterSpacing = -0.5f + }); + } + + stack.Spacer(8); + stack.Meta(socialCardRequest.SiteTitle, color: "#38bdf8"); + }); + + return Task.FromResult(card.ToBytes()); + } +} \ No newline at end of file diff --git a/Spectre.Docs/SocialCardAssets/spectre-og-card.png b/Spectre.Docs/SocialCardAssets/spectre-og-card.png new file mode 100644 index 0000000..aadeffa Binary files /dev/null and b/Spectre.Docs/SocialCardAssets/spectre-og-card.png differ diff --git a/Spectre.Docs/Spectre.Docs.csproj b/Spectre.Docs/Spectre.Docs.csproj index d5580ce..33ec865 100644 --- a/Spectre.Docs/Spectre.Docs.csproj +++ b/Spectre.Docs/Spectre.Docs.csproj @@ -6,15 +6,24 @@ - - - - - + + + + + + + + + - + - - + + \ No newline at end of file