diff --git a/README.md b/README.md index dea7593..6002ded 100644 --- a/README.md +++ b/README.md @@ -97,9 +97,12 @@ dotnet run --project src/ProjGraph.Mcp ### Analyze Project Dependencies ```bash -# Tree format (default) +# Mermaid format (default) projgraph visualize ./MySolution.sln +# ASCII tree format +projgraph visualize ./MySolution.sln --format tree + # Mermaid format for documentation projgraph visualize ./MySolution.slnx --format mermaid --output docs/dependencies.mmd ``` diff --git a/docfx/docfx.json b/docfx/docfx.json index 0166f02..fe52007 100644 --- a/docfx/docfx.json +++ b/docfx/docfx.json @@ -20,14 +20,18 @@ "build": { "content": [ { - "files": ["api/*.yml"] + "files": [ + "api/*.yml" + ] }, { "files": [ "index.md", "license.md", "toc.yml", + "guides/*.md", "guides/toc.yml", + "samples/toc.yml", "dev/toc.yml" ] }, @@ -66,14 +70,21 @@ "dest": "_site", "template": [ "default", - "modern" + "modern", + "templates/projgraph" ], "globalMetadata": { "_appTitle": "ProjGraph", - "_appFooter": "Made with DocFX.", + "_appFooter": "
ProjGraph — diagrams from the code you already have
", "_enableSearch": true, "_appFaviconPath": "icon.png", - "pdf": false + "pdf": false, + "_appName": "ProjGraph", + "_appLogoPath": "icon.png", + "_gitContribute": { + "repo": "https://github.com/HandyS11/ProjGraph", + "branch": "develop" + } } } } diff --git a/docfx/guides/getting-started.md b/docfx/guides/getting-started.md new file mode 100644 index 0000000..bd57bf9 --- /dev/null +++ b/docfx/guides/getting-started.md @@ -0,0 +1,117 @@ +--- +title: Getting started +--- + +# Getting started + +Install ProjGraph, point it at code you already have, and read the diagram it +gives back. This page takes you through the first run for both the CLI and the +MCP server. For the full set of flags on each command, see the +[CLI reference](../../src/ProjGraph.Cli/README.md). + +## Before you start + +You need the [.NET 10 SDK](https://dotnet.microsoft.com/download) or later. +Check what you have: + +```bash +dotnet --version +``` + +ProjGraph reads source files directly. You do not need to build the project you +are analysing, run a database, or apply migrations first. + +## Install the CLI + +```bash +dotnet tool install -g ProjGraph.Cli +``` + +Confirm the tool is on your path. This lists the four commands and their +options: + +```bash +projgraph --help +``` + +If the command is not found, your shell has not picked up the global tools +directory yet. Open a new terminal, or see +[Troubleshooting](troubleshooting.md#projgraph-command-not-found). + +## Draw your first diagram + +Start with the solution you are standing in, because it needs no arguments +beyond a path: + +```bash +projgraph visualize ./MySolution.slnx +``` + +That prints a Mermaid graph to stdout, wrapped in a fenced code block ready to +paste into Markdown. To read it in the terminal instead, ask for the tree: + +```bash +projgraph visualize ./MySolution.slnx --format tree +``` + +Both render the same graph. [Output formats](output-formats.md) covers when to +reach for which. + +## Draw the rest + +Each command takes a path and writes to stdout, so you can redirect it anywhere: + +```bash +# Database schema, from an EF Core DbContext +projgraph erd ./Data/LibraryContext.cs + +# One class, with its base types and dependencies +projgraph classdiagram ./Models/Book.cs --inheritance --dependencies + +# Architectural metrics for the whole solution +projgraph stats ./MySolution.slnx +``` + +Use `--output` rather than a shell redirect when you want the file written for +you, including any missing directories: + +```bash +projgraph erd ./Data/LibraryContext.cs --output docs/database-schema.md +``` + +## Use it from an AI assistant + +The MCP server exposes the same four analyses as tools an assistant can call, +so you can ask about your architecture instead of remembering command names. + +Add the server to your MCP client's configuration. Replace `x.x.x` with the +current version from +[NuGet](https://www.nuget.org/packages/ProjGraph.Mcp): + +```json +{ + "servers": { + "ProjGraph.Mcp": { + "type": "stdio", + "command": "dnx", + "args": ["ProjGraph.Mcp@x.x.x", "--yes"] + } + } +} +``` + +Restart the client, then ask it something that needs the tools: + +```text +Show me the entity relationships in my DbContext. +Which projects in this solution are referenced the most? +``` + +The [MCP reference](../../src/ProjGraph.Mcp/README.md) documents every tool, +prompt, and resource the server provides. + +## Where to go next + +- [Output formats](output-formats.md) — pick between Mermaid, tree, and Markdown. +- [Showcase](../../samples/README.md) — real commands and the diagrams they produced. +- [Troubleshooting](troubleshooting.md) — when a diagram comes back empty. diff --git a/docfx/guides/output-formats.md b/docfx/guides/output-formats.md new file mode 100644 index 0000000..20333f0 --- /dev/null +++ b/docfx/guides/output-formats.md @@ -0,0 +1,143 @@ +--- +title: Output formats +--- + +# Output formats + +Every ProjGraph command writes text to stdout. What that text *is* — a Mermaid +diagram, an ASCII tree, or a fenced Markdown block — depends on the command, the +`--format` flag, and whether you asked for a file. This page explains the +choices so you can pick one deliberately. + +## The three renderings + +| Rendering | Available on | Shows | Renders as a picture in | +| --- | --- | --- | --- | +| `mermaid` | `visualize`, and always for `erd` / `classdiagram` | The whole graph, as diagram source | GitHub, GitLab, this site, VS Code | +| `tree` | `visualize` | Each root expanded recursively through its whole chain | Nowhere — it is already the picture | +| `flat` | `visualize` | Every project once, with its direct references only | Nowhere — it is already the picture | + +`erd` and `classdiagram` have no `--format` flag. Both always emit Mermaid, +because an entity relationship diagram and a class diagram have no useful +plain-text form. + +## Choosing between them + +`tree` and `flat` answer different questions, and the difference is not +cosmetic. + +**Tracing what a project pulls in transitively? Use `tree`.** It starts from the +projects nothing else references and expands each chain to its full depth. A +project that appears more than once is expanded on first sight and marked +`(see above)` afterwards, so the output stays finite: + +```bash +projgraph visualize ./MySolution.slnx --format tree +``` + +```text +🧪 ProjGraph.Tests.Contract +└── ProjGraph.Mcp + └── ProjGraph.Lib + ├── ProjGraph.Lib.ClassDiagram + │ └── ProjGraph.Lib.Core + │ └── ProjGraph.Core + └── ProjGraph.Lib.Dependencies + └── ProjGraph.Lib.Core (see above) +``` + +**Auditing what each project references directly? Use `flat`.** Every project is +listed exactly once, grouped by type, with only its immediate references. No +chain is followed, so nothing is repeated and nothing is elided: + +```bash +projgraph visualize ./MySolution.slnx --format flat +``` + +```text +🔷 ProjGraph.Lib +├── → ProjGraph.Lib.ClassDiagram +├── → ProjGraph.Lib.Dependencies +└── → ProjGraph.Lib.EntityFramework +🔷 ProjGraph.Lib.ClassDiagram +└── → ProjGraph.Lib.Core +``` + +**Committing to docs? Use `mermaid`,** which is the default for `visualize`: + +```bash +projgraph visualize ./MySolution.slnx +``` + +The emoji mark the project type: 🔷 library, 🚀 executable, 🧪 test project. + +## Fenced or raw: what `--output` changes + +This is the part that surprises people. The same command produces two different +things depending on where it is going: + +- **To stdout**, Mermaid arrives wrapped in a `mermaid` code fence, so you can + paste it straight into a Markdown file and have it render. +- **To a file via `--output`**, the extension decides. Only `.mmd` suppresses + the fence — a `.mmd` file is already understood to contain nothing but a + diagram. Every other extension, `.md` included, keeps it. + +```bash +# Fenced Markdown, ready to commit into a docs page +projgraph visualize ./MySolution.slnx --output docs/dependencies.md + +# Raw Mermaid, for a tool that parses .mmd directly +projgraph visualize ./MySolution.slnx --output docs/dependencies.mmd +``` + +Prefer `--output` over a shell redirect. It creates missing directories for you, +and it is what selects the right fencing. + +## Piping the output + +`mermaid` keeps stdout clean: progress messages go to stderr, so a redirect +captures the diagram and nothing else. + +`tree` and `flat` are written for a human at a terminal, and their progress +header goes to stdout along with the graph. If you redirect them, that header +lands in your file too. Use `--output`, which writes the rendered graph and +leaves the progress message on the terminal where it belongs: + +```bash +# Captures the header as well - probably not what you want +projgraph visualize ./MySolution.slnx --format tree > deps.txt + +# Captures the graph, with its title block +projgraph visualize ./MySolution.slnx --format tree --output deps.txt +``` + +The diagram's own title block is part of the render, so it is written to the +file either way. Drop it with `--show-title false`. + +## Titles + +Every diagram carries a title block naming what was analysed. Drop it when you +are embedding the diagram under a heading that already says the same thing: + +```bash +projgraph erd ./Data/LibraryContext.cs --show-title false +``` + +`--show-title` is available on `visualize`, `erd`, and `classdiagram`. + +## What `stats` does instead + +`stats` is the exception: it reports numbers, not a graph, so it has no +`--format` and no `--output`. It prints a formatted table to the terminal and +nothing else. + +```bash +projgraph stats ./MySolution.slnx --top 10 +``` + +## Rendering Mermaid on this site + +The [showcase](../../samples/README.md) pages embed their Mermaid output +directly, so you can see exactly what each command produces without running it. +That is the same text the CLI writes — the pages are regenerated from the tool +whenever it changes. diff --git a/docfx/guides/toc.yml b/docfx/guides/toc.yml index 6eadac2..14d7126 100644 --- a/docfx/guides/toc.yml +++ b/docfx/guides/toc.yml @@ -1,3 +1,12 @@ +- name: 🚀 Start here +- name: Getting started + href: getting-started.md +- name: Output formats + href: output-formats.md +- name: Troubleshooting + href: troubleshooting.md + +- name: 📖 Reference - name: CLI Guide href: ../../src/ProjGraph.Cli/README.md - name: MCP Guide diff --git a/docfx/guides/troubleshooting.md b/docfx/guides/troubleshooting.md new file mode 100644 index 0000000..db206a1 --- /dev/null +++ b/docfx/guides/troubleshooting.md @@ -0,0 +1,141 @@ +--- +title: Troubleshooting +--- + +# Troubleshooting + +ProjGraph reads source files rather than compiled assemblies, so when a diagram +comes back empty it is almost always because it could not find the thing you +meant, not because your code is wrong. This page maps the messages you might see +onto what to do about them. + +## `projgraph` command not found + +The tool installed, but your shell has not picked up the .NET global tools +directory. Open a new terminal first — that resolves it most of the time. + +If it persists, add the tools directory to your `PATH`: + +```bash +# Linux and macOS +export PATH="$PATH:$HOME/.dotnet/tools" + +# Windows (PowerShell) +$env:PATH += ";$env:USERPROFILE\.dotnet\tools" +``` + +Add the same line to your shell profile to make it stick. + +## `File must be a .sln, .slnx, or .csproj file.` + +`visualize` and `stats` analyse a solution or a project, not a directory and not +a source file. Point them at the file itself: + +```bash +projgraph visualize ./MySolution.slnx +projgraph stats ./src/MyApp/MyApp.csproj +``` + +## `No DbContext or ModelSnapshot .cs file found.` + +`erd` was given no path, and found nothing to analyse in the current directory. +It looks for files whose names end in `DbContext.cs` or that contain a model +snapshot. + +Give it the path explicitly: + +```bash +projgraph erd ./Data/LibraryContext.cs +``` + +If your context does not follow the `*DbContext.cs` naming convention, name it +directly with `--context`: + +```bash +projgraph erd ./Data/Persistence.cs --context LibraryContext +``` + +## The ERD is empty, or entities are missing + +ProjGraph discovers entities from the `DbSet` properties on your context, so +an entity that is not exposed as a `DbSet` will not appear unless something else +references it. + +Work through these in order: + +1. **Is the context `public`?** A non-public class is skipped. +2. **Are the entities reachable?** An entity registered only through + `modelBuilder.Entity()` in `OnModelCreating`, with no `DbSet`, is found + only if ProjGraph can resolve the type. +3. **Do the entity types live in another project?** ProjGraph searches the + workspace for them, starting from the nearest `.sln`, `.slnx`, or `.csproj`. + If your context and your entities are in unrelated directories with no + solution file above them, it has nothing to search. + +If the model is already migrated, the snapshot is the more reliable source, +because EF has already resolved the model in full: + +```bash +projgraph erd ./Migrations/LibraryContextModelSnapshot.cs +``` + +## The class diagram shows one class and nothing else + +That is the default. Base types and dependencies are opt-in, because following +them across a large workspace is expensive: + +```bash +projgraph classdiagram ./Models/Book.cs --inheritance --dependencies +``` + +Still missing types? Discovery only follows one level by default. Raise it: + +```bash +projgraph classdiagram ./Models/Book.cs -i -d --depth 5 +``` + +## `stats` reports zero projects + +The solution file parsed, but none of the projects it lists could be read. +Usually one of: + +- The `.csproj` files it references have moved or been deleted. +- The solution genuinely contains no projects. +- You pointed at a solution file in a different directory tree, so the relative + project paths inside it no longer resolve. + +Confirm the paths inside the solution file match what is on disk. + +## Parsing fails on newer C# syntax + +ProjGraph parses with Roslyn, and the version of Roslyn it ships with sets the +syntax it understands. If you use a language feature newer than the tool, +parsing that file can fail. + +Update the tool: + +```bash +dotnet tool update -g ProjGraph.Cli +``` + +## The MCP server returns nothing, or the client will not start it + +The server speaks JSON-RPC over stdio, which means **anything else written to +stdout corrupts the protocol**. If you are running a locally built copy, make +sure nothing in your build writes to stdout on startup. + +Check these in order: + +1. **Is `dnx` available?** Run `dnx --help`. It ships with the .NET 10 SDK. +2. **Is the version in your config real?** Replace `x.x.x` with a published + version from [NuGet](https://www.nuget.org/packages/ProjGraph.Mcp). +3. **Did the client restart?** Most MCP clients only read their server + configuration at startup. +4. **Can the server see your files?** The tools take paths, and the server can + only read what its process can reach. + +## Still stuck + +Open an issue at +[github.com/HandyS11/ProjGraph/issues](https://github.com/HandyS11/ProjGraph/issues) +with the command you ran and the output you got. diff --git a/docfx/index.md b/docfx/index.md index 32d26eb..4a055e0 100644 --- a/docfx/index.md +++ b/docfx/index.md @@ -1,5 +1,148 @@ --- title: ProjGraph +layout: landing --- -[!include[Welcome](../README.md)] +
+

Your code already describes its architecture. ProjGraph draws it.

+

One command turns a solution, an EF Core DbContext, or a single class into a Mermaid diagram or an ASCII tree. Run it from your terminal, or let an AI assistant run it for you over MCP.

+ +
+ +
+
+
LibraryContext.csinput
+
public class LibraryContext : DbContext
+{
+    public DbSet<Author>    Authors    { get; set; }
+    public DbSet<Book>      Books      { get; set; }
+    public DbSet<Publisher> Publishers { get; set; }
+    public DbSet<Review>    Reviews    { get; set; }
+}
+public class Book
+{
+    public int            Id          { get; set; }
+    [MaxLength(300)]
+    public string         Title       { get; set; }
+    public int            PublisherId { get; set; }
+    public Publisher      Publisher   { get; set; }
+    public List<Review>   Reviews     { get; set; }
+}
+
+
+
projgraph erdoutput
+ + + + + +Author + +Id PK +Name + +Book + +Id PK +Title max:300 +PublisherId FK + +Publisher + +Id PK +Name + +Review + +Id PK +Rating + + + + + + +1 +n +1 +n +1 +n + +
+
+ +

Nothing was annotated to produce this. ProjGraph reads the C# with Roslyn, resolves the navigation properties into relationships, and carries the constraints across — [MaxLength(300)] on the left becomes max:300 on the right. No build, no running database, no migration step.

+ +
+$ +dotnet tool install -g ProjGraph.Cli + +
+ +
+

What it draws

+

Four commands, each pointed at something you already have on disk.

+
+

projgraph visualize

Reads a .slnx, .sln, or .csproj and maps how the projects reference each other. Emits Mermaid by default, or an ASCII tree for the terminal.

+

projgraph erd

Turns an EF Core DbContext or a ModelSnapshot into an entity relationship diagram, including keys, constraints, owned types, and join tables.

+

projgraph classdiagram

Draws a class with its inheritance chain and dependencies, following related types across the workspace as deep as you ask it to.

+

projgraph stats

Reports project counts, type breakdown, dependency depth, and the hotspot projects that everything else references.

+
+
+ +
+

Two ways to run it

+

The same analysis, reached either by hand or by an assistant.

+
+
+

Command line

+
$ projgraph erd ./Data/LibraryContext.cs
+$ projgraph visualize ./MySolution.slnx --format mermaid
+$ projgraph stats ./MySolution.slnx --top 10
+

Writes to stdout by default, or to a file with --output. The Mermaid it emits renders as-is on GitHub and GitLab.

+ +
+
+

AI assistant

+
{
+  "servers": {
+    "ProjGraph.Mcp": {
+      "type": "stdio",
+      "command": "dnx",
+      "args": ["ProjGraph.Mcp@x.x.x", "--yes"]
+    }
+  }
+}
+

Drop this into any MCP client — GitHub Copilot, Claude, and others — then ask about your architecture in plain language.

+ +
+
+
+ +
+

See it on real code

+

Every diagram below was generated by ProjGraph from a project in this repository, and is regenerated whenever the tool changes.

+ +
+ +
+

Where to go next

+ +
diff --git a/docfx/samples/toc.yml b/docfx/samples/toc.yml new file mode 100644 index 0000000..1ccd9e6 --- /dev/null +++ b/docfx/samples/toc.yml @@ -0,0 +1,27 @@ +- name: 🗺️ Overview +- name: All samples + href: ../../samples/README.md + +- name: 🗄️ Entity relationships +- name: Simple DbContext + href: ../../samples/erd/simple-context/README.md +- name: Complex e-commerce + href: ../../samples/erd/complex-ecommerce/README.md + +- name: 🏢 Class diagrams +- name: Simple hierarchy + href: ../../samples/classdiagram/simple-hierarchy/README.md +- name: Design patterns + href: ../../samples/classdiagram/design-patterns/README.md +- name: Complex hierarchy + href: ../../samples/classdiagram/complex-hierarchy/README.md + +- name: 🌐 Dependency graphs +- name: Simple dependencies + href: ../../samples/visualize/simple-dependencies/README.md +- name: Modular architecture + href: ../../samples/visualize/modular-architecture/README.md + +- name: 📈 Metrics +- name: Solution metrics + href: ../../samples/stats/README.md diff --git a/docfx/templates/projgraph/public/fonts/inter-latin-400-normal.woff2 b/docfx/templates/projgraph/public/fonts/inter-latin-400-normal.woff2 new file mode 100644 index 0000000..f15b025 Binary files /dev/null and b/docfx/templates/projgraph/public/fonts/inter-latin-400-normal.woff2 differ diff --git a/docfx/templates/projgraph/public/fonts/inter-latin-600-normal.woff2 b/docfx/templates/projgraph/public/fonts/inter-latin-600-normal.woff2 new file mode 100644 index 0000000..d189794 Binary files /dev/null and b/docfx/templates/projgraph/public/fonts/inter-latin-600-normal.woff2 differ diff --git a/docfx/templates/projgraph/public/fonts/inter-latin-700-normal.woff2 b/docfx/templates/projgraph/public/fonts/inter-latin-700-normal.woff2 new file mode 100644 index 0000000..a68fb10 Binary files /dev/null and b/docfx/templates/projgraph/public/fonts/inter-latin-700-normal.woff2 differ diff --git a/docfx/templates/projgraph/public/fonts/jetbrains-mono-latin-400-normal.woff2 b/docfx/templates/projgraph/public/fonts/jetbrains-mono-latin-400-normal.woff2 new file mode 100644 index 0000000..5858873 Binary files /dev/null and b/docfx/templates/projgraph/public/fonts/jetbrains-mono-latin-400-normal.woff2 differ diff --git a/docfx/templates/projgraph/public/fonts/jetbrains-mono-latin-700-normal.woff2 b/docfx/templates/projgraph/public/fonts/jetbrains-mono-latin-700-normal.woff2 new file mode 100644 index 0000000..3a4e333 Binary files /dev/null and b/docfx/templates/projgraph/public/fonts/jetbrains-mono-latin-700-normal.woff2 differ diff --git a/docfx/templates/projgraph/public/main.css b/docfx/templates/projgraph/public/main.css new file mode 100644 index 0000000..c295890 --- /dev/null +++ b/docfx/templates/projgraph/public/main.css @@ -0,0 +1,1066 @@ +/* + * ProjGraph documentation theme. + * + * Layered on top of docfx's `default` + `modern` templates. Overrides stay on + * Bootstrap custom properties (`--bs-*`) and documented docfx selectors so a + * docfx upgrade shifts as little as possible underneath us. + * + * Colour is semantic here, not decorative: + * cyan - graph structure: nodes, edges, the active rail, executable lines + * steel - every other border, rule, and chrome element + * If something is not part of a graph and not something you can run, it is not + * cyan. + */ + +@font-face { + font-family: 'ProjGraph Sans'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('./fonts/inter-latin-400-normal.woff2') format('woff2'); +} + +@font-face { + font-family: 'ProjGraph Sans'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('./fonts/inter-latin-600-normal.woff2') format('woff2'); +} + +@font-face { + font-family: 'ProjGraph Sans'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('./fonts/inter-latin-700-normal.woff2') format('woff2'); +} + +@font-face { + font-family: 'ProjGraph Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('./fonts/jetbrains-mono-latin-400-normal.woff2') format('woff2'); +} + +@font-face { + font-family: 'ProjGraph Mono'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('./fonts/jetbrains-mono-latin-700-normal.woff2') format('woff2'); +} + +/* ---------------------------------------------------------------- tokens -- */ + +:root { + --pg-mono: 'ProjGraph Mono', ui-monospace, 'SFMono-Regular', 'Cascadia Mono', + Consolas, monospace; + --pg-sans: 'ProjGraph Sans', system-ui, -apple-system, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, sans-serif; + + --pg-radius: 10px; + --pg-rail: 3px; + --pg-measure: 72ch; +} + +[data-bs-theme='dark'] { + --pg-void: #060b16; + --pg-surface: #0d1526; + --pg-surface-raised: #142138; + --pg-hairline: #1c2942; + --pg-hairline-strong: #2c4062; + --pg-ink: #dce7f5; + --pg-ink-dim: #93a8c4; + --pg-mute: #6b809e; + + --pg-cyan: #22d3ee; + --pg-cyan-soft: #67e8f9; + --pg-cyan-wash: rgba(34, 211, 238, 0.12); + + --pg-node: #38bdf8; + --pg-edge: #22d3ee; + --pg-key: #fbbf24; + --pg-warn: #fb7185; + + --bs-body-bg: var(--pg-void); + --bs-body-color: var(--pg-ink); + --bs-body-bg-rgb: 6, 11, 22; + --bs-body-color-rgb: 220, 231, 245; + --bs-emphasis-color: #fff; + --bs-secondary-color: var(--pg-ink-dim); + --bs-border-color: var(--pg-hairline); + --bs-heading-color: #eef5ff; + --bs-code-color: var(--pg-cyan-soft); + --bs-link-color: #5fdcf2; + --bs-link-color-rgb: 95, 220, 242; + --bs-link-hover-color: #9bebfa; + --bs-link-hover-color-rgb: 155, 235, 250; +} + +/* The light theme is designed, not derived: cool paper rather than an inverted + copy of the dark ramp, with the cyan darkened far enough to hold contrast on + white. */ +[data-bs-theme='light'] { + --pg-void: #f5f8fc; + --pg-surface: #fff; + --pg-surface-raised: #eaf1f8; + --pg-hairline: #d6e0ec; + --pg-hairline-strong: #b2c4d8; + --pg-ink: #10192b; + --pg-ink-dim: #465873; + --pg-mute: #6b7e97; + + --pg-cyan: #0e7490; + --pg-cyan-soft: #0891b2; + --pg-cyan-wash: rgba(14, 116, 144, 0.09); + + --pg-node: #0369a1; + --pg-edge: #0e7490; + --pg-key: #a16207; + --pg-warn: #be123c; + + --bs-body-bg: var(--pg-void); + --bs-body-color: var(--pg-ink); + --bs-body-bg-rgb: 245, 248, 252; + --bs-body-color-rgb: 16, 25, 43; + --bs-secondary-color: var(--pg-ink-dim); + --bs-border-color: var(--pg-hairline); + --bs-heading-color: #07101f; + --bs-code-color: #0e7490; + --bs-link-color: #0e7490; + --bs-link-color-rgb: 14, 116, 144; + --bs-link-hover-color: #0b5566; + --bs-link-hover-color-rgb: 11, 85, 102; +} + +body { + font-family: var(--pg-sans); + background-color: var(--pg-void); +} + +/* ------------------------------------------------------------ typography -- */ + +h1, +h2, +h3, +h4, +h5, +h6, +.navbar-brand, +.toc, +.affix, +#breadcrumb { + font-family: var(--pg-mono); +} + +article h1 { + font-size: 2.05rem; + font-weight: 700; + letter-spacing: -0.035em; + margin-bottom: 1.1rem; +} + +article h2 { + font-size: 1.38rem; + letter-spacing: -0.02em; + padding-top: 0.55rem; + margin-top: 2.6rem; + border-top: 1px solid var(--pg-hairline); +} + +article h3 { + font-size: 1.1rem; + letter-spacing: -0.01em; + margin-top: 1.9rem; +} + +article p, +article li { + max-width: var(--pg-measure); + line-height: 1.68; +} + +article > p:first-of-type { + color: var(--pg-ink-dim); + font-size: 1.05rem; + line-height: 1.6; +} + +/* The generated API reference sets its own rhythm; the conceptual rules above + fight it, so they stop at the door. */ +body[data-yaml-mime='ManagedReference'] article h2 { + border-top: 0; + padding-top: 0; + margin-top: 2rem; +} + +/* --------------------------------------------------------------- navbar -- */ + +header .navbar { + /* Plain colour first: engines without color-mix() drop the line below. */ + background-color: var(--pg-void); + background-color: color-mix(in srgb, var(--pg-void) 88%, transparent); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); +} + +header { + border-bottom-color: var(--pg-hairline) !important; +} + +.navbar-brand { + font-weight: 700; + letter-spacing: -0.03em; + color: var(--bs-heading-color); +} + +.navbar-brand > img, +#logo { + height: 1.6rem; + width: auto; + border-radius: 6px; + margin-right: 0.55rem; +} + +.navbar-nav .nav-link { + font-family: var(--pg-mono); + font-size: 0.86rem; + color: var(--pg-ink-dim); +} + +.navbar-nav .nav-link:hover, +.navbar-nav .nav-link.active { + color: var(--pg-cyan); +} + +/* docfx renders the navbar icon links (GitHub, NuGet) from main.js `iconLinks`. */ +form.icons .btn { + color: var(--pg-ink-dim); +} + +form.icons .btn:hover { + color: var(--pg-cyan); +} + +#search input.form-control { + font-family: var(--pg-mono); + font-size: 0.84rem; + background-color: var(--pg-surface); + border-color: var(--pg-hairline); + color: var(--pg-ink); +} + +#search input.form-control:focus { + border-color: var(--pg-cyan); + box-shadow: 0 0 0 3px var(--pg-cyan-wash); +} + +/* --------------------------------------------------- table of contents -- */ + +.toc .nav-link { + font-size: 0.85rem; + color: var(--pg-ink-dim); + border-left: var(--pg-rail) solid transparent; + border-radius: 0; + padding-left: 0.7rem; +} + +.toc .nav-link:hover { + color: var(--pg-ink); + background-color: var(--pg-cyan-wash); +} + +/* The active rail is a graph edge: you are here, and this is the path in. */ +.toc .nav-link.active { + color: var(--pg-cyan); + font-weight: 700; + background-color: var(--pg-cyan-wash); + border-left-color: var(--pg-cyan); +} + +.affix h5 { + font-family: var(--pg-mono); + text-transform: none; + letter-spacing: -0.01em; + font-weight: 700; + font-size: 0.82rem; + color: var(--pg-ink-dim); +} + +.affix ul > li > a { + display: block; + font-size: 0.82rem; + color: var(--pg-mute); + border-left: var(--pg-rail) solid var(--pg-hairline); + padding-left: 0.65rem; +} + +.affix ul > li > a:hover, +.affix ul > li.active > a { + color: var(--pg-cyan); + border-left-color: var(--pg-cyan); + background: none; +} + +/* ----------------------------------------------------------- code blocks -- */ + +code, +pre, +kbd { + font-family: var(--pg-mono); + font-variant-ligatures: none; +} + +:not(pre) > code { + background-color: var(--pg-cyan-wash); + border-radius: 4px; + padding: 0.12em 0.36em; + font-size: 0.87em; +} + +article pre { + background-color: var(--pg-surface); + border: 1px solid var(--pg-hairline); + border-radius: var(--pg-radius); + font-size: 0.845rem; + line-height: 1.6; +} + +/* highlight.js sets a background on `.hljs` itself, which outranks a plain type + selector and punches a grey box out of the navy. Match its specificity. */ +article pre > code, +article pre > code.hljs { + background: none; + padding: 1rem 1.1rem; +} + +/* A cyan rail marks blocks you can actually run, separating a command from a + C# snippet or a config file at a glance. */ +article pre:has(> code.lang-sh), +article pre:has(> code.lang-bash), +article pre:has(> code.lang-console), +article pre:has(> code.lang-powershell) { + border-left: var(--pg-rail) solid var(--pg-cyan); + border-radius: 0 var(--pg-radius) var(--pg-radius) 0; +} + +[data-bs-theme='dark'] .hljs-keyword, +[data-bs-theme='dark'] .hljs-built_in, +[data-bs-theme='dark'] .hljs-literal { + color: #7dd3fc; +} + +[data-bs-theme='dark'] .hljs-string, +[data-bs-theme='dark'] .hljs-attr { + color: #6ee7b7; +} + +[data-bs-theme='dark'] .hljs-title, +[data-bs-theme='dark'] .hljs-name { + color: #c4b5fd; +} + +[data-bs-theme='dark'] .hljs-comment, +[data-bs-theme='dark'] .hljs-meta { + color: var(--pg-mute); +} + +[data-bs-theme='dark'] .hljs-number, +[data-bs-theme='dark'] .hljs-type { + color: #fcd34d; +} + +/* ---------------------------------------------------------------- tables -- */ + +article table { + font-size: 0.9rem; + border-collapse: separate; + border-spacing: 0; +} + +article table > thead > tr > th { + font-family: var(--pg-mono); + font-size: 0.78rem; + font-weight: 700; + color: var(--pg-ink-dim); + border-bottom: 2px solid var(--pg-hairline-strong); +} + +article table > tbody > tr:hover { + background-color: var(--pg-cyan-wash); +} + +/* ------------------------------------------------------------ blockquote -- */ + +article blockquote { + border-left: var(--pg-rail) solid var(--pg-hairline-strong); + background-color: var(--pg-surface); + padding: 0.85rem 1.1rem; + border-radius: 0 var(--pg-radius) var(--pg-radius) 0; + color: var(--pg-ink); +} + +article blockquote > :last-child { + margin-bottom: 0; +} + +/* ------------------------------------------------- rendered diagrams -- */ + +/* Sample pages are mostly Mermaid. Give each diagram room and a surface, so it + reads as output rather than as an illustration dropped into the prose. */ +article pre.mermaid { + background-color: var(--pg-surface); + border: 1px solid var(--pg-hairline); + border-radius: var(--pg-radius); + padding: 1.25rem; + margin: 1.5rem 0; + overflow-x: auto; + text-align: center; +} + +/* ---------------------------------------------------------------- footer -- */ + +footer { + border-top-color: var(--pg-hairline) !important; + background-color: var(--pg-surface); + font-family: var(--pg-mono); + font-size: 0.82rem; +} + +.pg-footer { + display: flex; + flex-wrap: wrap; + gap: 0.4rem 1.4rem; + align-items: baseline; + justify-content: space-between; +} + +.pg-footer nav { + display: flex; + flex-wrap: wrap; + gap: 1.1rem; +} + +.pg-footer a { + color: var(--pg-ink-dim); + text-decoration: none; +} + +.pg-footer a:hover { + color: var(--pg-cyan); + text-decoration: underline; +} + +/* =========================================================== landing page = */ + +body[data-layout='landing'] main.container-xxl { + max-width: 1180px; +} + +body[data-layout='landing'] article { + padding-top: 0.5rem; +} + +/* The landing page sets its own sectioning, so the conceptual h2 rule that + draws a rule above every heading would double up here. */ +body[data-layout='landing'] article h2 { + border-top: 0; + padding-top: 0; +} + +.pg-hero { + padding: 2.4rem 0 0.5rem; +} + +.pg-hero h1 { + font-family: var(--pg-mono); + font-size: clamp(1.85rem, 4.2vw, 2.95rem); + font-weight: 700; + line-height: 1.14; + letter-spacing: -0.045em; + margin: 0 0 1rem; + max-width: 22ch; + overflow-wrap: normal; + word-break: normal; +} + +/* docfx's script injects into headings at camelCase boundaries so long + API type names can wrap. That is right for the API reference and wrong here: + it splits "ProjGraph" across two lines on a narrow screen. Hiding the element + removes the break opportunity; the rule is scoped to the hero so generated + reference headings keep it. */ +.pg-hero h1 wbr { + display: none; +} + +.pg-hero-lede { + color: var(--pg-ink-dim); + font-size: 1.06rem; + line-height: 1.6; + max-width: 60ch; + margin-bottom: 1.5rem; +} + +.pg-cta { + display: flex; + flex-wrap: wrap; + gap: 0.7rem; + align-items: center; + margin-bottom: 2.4rem; +} + +.pg-btn { + font-family: var(--pg-mono); + font-size: 0.88rem; + font-weight: 700; + padding: 0.6rem 1.15rem; + border-radius: var(--pg-radius); + text-decoration: none; + border: 1px solid var(--pg-hairline-strong); + color: var(--pg-ink); + transition: background-color 0.12s ease, border-color 0.12s ease; +} + +.pg-btn:hover { + border-color: var(--pg-cyan); + background-color: var(--pg-cyan-wash); + color: var(--pg-ink); + text-decoration: none; +} + +.pg-btn-primary { + background-color: var(--pg-cyan); + border-color: var(--pg-cyan); + color: #04212b; +} + +.pg-btn-primary:hover { + background-color: var(--pg-cyan-soft); + border-color: var(--pg-cyan-soft); + color: #04212b; +} + +[data-bs-theme='light'] .pg-btn-primary, +[data-bs-theme='light'] .pg-btn-primary:hover { + color: #fff; +} + +/* -- the transform: source on the left, the graph it describes on the right - */ + +.pg-transform { + display: grid; + grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr); + gap: 1rem; + align-items: stretch; + margin-bottom: 0.9rem; +} + +.pg-pane { + background-color: var(--pg-surface); + border: 1px solid var(--pg-hairline); + border-radius: var(--pg-radius); + overflow: hidden; + display: flex; + flex-direction: column; + min-width: 0; +} + +.pg-pane-out { + border-color: var(--pg-hairline-strong); + box-shadow: 0 0 0 1px var(--pg-cyan-wash); +} + +.pg-pane-bar { + display: flex; + align-items: center; + gap: 0.55rem; + padding: 0.45rem 0.8rem; + background-color: var(--pg-surface-raised); + border-bottom: 1px solid var(--pg-hairline); + font-family: var(--pg-mono); + font-size: 0.76rem; + color: var(--pg-ink-dim); +} + +.pg-pane-out .pg-pane-bar { + color: var(--pg-cyan); +} + +.pg-pane-tag { + margin-left: auto; + color: var(--pg-mute); + font-variant-numeric: tabular-nums; +} + +.pg-pane pre { + margin: 0; + padding: 0.85rem 0.95rem; + background: none; + border: 0; + font-family: var(--pg-mono); + font-size: 0.735rem; + line-height: 1.6; + overflow-x: auto; + flex: 1; + white-space: pre; + color: var(--pg-ink-dim); +} + +.pg-pane pre b { + font-weight: 400; + color: var(--pg-ink); +} + +.pg-cs-key { + color: #7dd3fc; +} + +.pg-cs-type { + color: #fcd34d; +} + +.pg-cs-comment { + color: var(--pg-mute); + font-style: italic; +} + +[data-bs-theme='light'] .pg-cs-key { + color: #0369a1; +} + +[data-bs-theme='light'] .pg-cs-type { + color: #a16207; +} + +/* -- the drawn graph ------------------------------------------------------ */ + +.pg-graph { + flex: 1; + display: block; + width: 100%; + height: auto; + padding: 0.6rem 0.5rem; +} + +.pg-graph .pg-g-box { + fill: var(--pg-surface-raised); + stroke: var(--pg-hairline-strong); + stroke-width: 1; +} + +.pg-graph .pg-g-head { + fill: var(--pg-node); + font-family: var(--pg-mono); + font-size: 8px; + font-weight: 700; +} + +.pg-graph .pg-g-field { + fill: var(--pg-ink-dim); + font-family: var(--pg-mono); + font-size: 6.6px; +} + +.pg-graph .pg-g-pk { + fill: var(--pg-key); + font-family: var(--pg-mono); + font-size: 6.6px; +} + +.pg-graph .pg-g-edge { + fill: none; + stroke: var(--pg-edge); + stroke-width: 1.3; + stroke-linecap: round; +} + +.pg-graph .pg-g-card { + fill: var(--pg-edge); + font-family: var(--pg-mono); + font-size: 6px; +} + +.pg-graph .pg-g-node { + fill: var(--pg-edge); +} + +/* The page's one piece of non-user-triggered motion: the graph draws its own + edges, once, on load. The markup ships in its final state, so nothing is + hidden when the animation cannot run. */ +@media (prefers-reduced-motion: no-preference) { + .pg-transform[data-draw='pending'] .pg-g-edge, + .pg-transform[data-draw='running'] .pg-g-edge { + stroke-dasharray: var(--pg-len, 200); + } + + .pg-transform[data-draw='pending'] .pg-g-edge { + stroke-dashoffset: var(--pg-len, 200); + } + + .pg-transform[data-draw='pending'] .pg-g-node, + .pg-transform[data-draw='pending'] .pg-g-card { + opacity: 0; + } + + .pg-transform[data-draw='running'] .pg-g-edge { + stroke-dashoffset: 0; + transition: stroke-dashoffset 0.75s ease-out 0.15s; + } + + .pg-transform[data-draw='running'] .pg-g-node, + .pg-transform[data-draw='running'] .pg-g-card { + opacity: 1; + transition: opacity 0.4s ease 0.8s; + } +} + +.pg-transform-note { + font-size: 0.93rem; + line-height: 1.65; + color: var(--pg-ink-dim); + max-width: 72ch; + margin-bottom: 2.5rem; +} + +.pg-transform-note b { + font-family: var(--pg-mono); + font-weight: 700; + color: var(--pg-cyan); +} + +/* ---- install line ------------------------------------------------------- */ + +.pg-install { + display: flex; + align-items: center; + gap: 0.7rem; + background-color: var(--pg-surface); + border: 1px solid var(--pg-hairline); + border-left: var(--pg-rail) solid var(--pg-cyan); + border-radius: var(--pg-radius); + padding: 0.7rem 0.9rem; + font-family: var(--pg-mono); + font-size: 0.87rem; + margin-bottom: 3rem; + max-width: 46rem; +} + +.pg-install span.pg-prompt { + color: var(--pg-cyan); + user-select: none; +} + +.pg-install code { + background: none; + padding: 0; + color: var(--pg-ink); + flex: 1; + overflow-x: auto; + white-space: nowrap; +} + +.pg-copy { + flex: none; + font-family: var(--pg-mono); + font-size: 0.75rem; + padding: 0.28rem 0.6rem; + border-radius: 6px; + border: 1px solid var(--pg-hairline-strong); + background: none; + color: var(--pg-ink-dim); + cursor: pointer; +} + +.pg-copy:hover { + color: var(--pg-cyan); + border-color: var(--pg-cyan); +} + +.pg-copy[data-copied='true'] { + color: var(--pg-cyan); + border-color: var(--pg-cyan); +} + +/* ---- landing sections --------------------------------------------------- */ + +.pg-section { + margin-bottom: 3.2rem; +} + +.pg-section > h2 { + font-family: var(--pg-mono); + font-size: 1.26rem; + font-weight: 700; + letter-spacing: -0.03em; + border: 0; + padding: 0; + margin: 0 0 0.4rem; +} + +.pg-section > p { + color: var(--pg-ink-dim); + max-width: 66ch; + margin-bottom: 1.5rem; +} + +/* ---- what it draws: the command is the headline ------------------------- */ + +.pg-caps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.4rem 2rem; +} + +/* Four items: two even rows rather than three-and-an-orphan. */ +@media (min-width: 900px) { + .pg-caps { + grid-template-columns: repeat(2, 1fr); + } +} + +.pg-cap { + border-left: var(--pg-rail) solid var(--pg-hairline-strong); + padding-left: 1rem; +} + +.pg-cap:hover { + border-left-color: var(--pg-cyan); +} + +.pg-cap h3 { + font-family: var(--pg-mono); + font-size: 0.88rem; + font-weight: 700; + margin: 0 0 0.4rem; + color: var(--pg-cyan); + overflow-wrap: anywhere; +} + +.pg-cap p { + font-size: 0.9rem; + line-height: 1.6; + color: var(--pg-ink-dim); + margin: 0; +} + +/* ---- two ways to run it ------------------------------------------------- */ + +.pg-runners { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.2rem; +} + +.pg-runner { + border: 1px solid var(--pg-hairline); + border-radius: var(--pg-radius); + background-color: var(--pg-surface); + padding: 1.2rem 1.3rem; + display: flex; + flex-direction: column; +} + +.pg-runner h3 { + font-family: var(--pg-mono); + font-size: 1rem; + font-weight: 700; + margin: 0 0 0.5rem; + color: var(--bs-heading-color); +} + +.pg-runner p { + font-size: 0.9rem; + line-height: 1.6; + color: var(--pg-ink-dim); + max-width: none; +} + +.pg-runner pre { + background-color: var(--pg-void); + border: 1px solid var(--pg-hairline); + border-radius: 8px; + font-size: 0.76rem; + line-height: 1.6; + padding: 0.75rem 0.85rem; + margin: 0 0 0.9rem; + overflow-x: auto; +} + +.pg-runner-link { + font-family: var(--pg-mono); + font-size: 0.83rem; + margin-top: auto; + margin-bottom: 0; + padding-top: 0.6rem; +} + +/* ---- showcase ----------------------------------------------------------- */ + +.pg-shots { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); + gap: 0.9rem; +} + +.pg-shot { + display: block; + border: 1px solid var(--pg-hairline); + border-radius: var(--pg-radius); + background-color: var(--pg-surface); + padding: 1rem 1.05rem; + text-decoration: none; + color: var(--pg-ink); +} + +.pg-shot:hover { + border-color: var(--pg-cyan); + background-color: var(--pg-cyan-wash); + color: var(--pg-ink); + text-decoration: none; +} + +.pg-shot strong { + font-family: var(--pg-mono); + font-size: 0.88rem; + display: block; + margin-bottom: 0.25rem; +} + +.pg-shot span { + font-size: 0.85rem; + color: var(--pg-ink-dim); +} + +/* ---- next steps --------------------------------------------------------- */ + +.pg-next { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); + gap: 0.9rem; +} + +/* Six items: two rows of three rather than four-and-two. */ +@media (min-width: 992px) { + .pg-next { + grid-template-columns: repeat(3, 1fr); + } +} + +.pg-next a { + display: block; + padding: 0.95rem 1.05rem; + border: 1px solid var(--pg-hairline); + border-radius: var(--pg-radius); + background-color: var(--pg-surface); + text-decoration: none; + color: var(--pg-ink); +} + +.pg-next a:hover { + border-color: var(--pg-cyan); + background-color: var(--pg-cyan-wash); + text-decoration: none; +} + +.pg-next strong { + font-family: var(--pg-mono); + font-size: 0.9rem; + display: block; + margin-bottom: 0.22rem; +} + +.pg-next span { + font-size: 0.85rem; + color: var(--pg-ink-dim); +} + +/* -------------------------------------------------------------- responsive */ + +@media (max-width: 860px) { + .pg-transform { + grid-template-columns: 1fr; + } + + .pg-pane pre { + font-size: 0.7rem; + } +} + +/* ------------------------------------------------------------------ focus -- */ + +a:focus-visible, +button:focus-visible, +.pg-btn:focus-visible, +.pg-copy:focus-visible, +input:focus-visible { + outline: 2px solid var(--pg-cyan); + outline-offset: 2px; + border-radius: 4px; +} + +@media print { + .pg-transform, + .pg-caps, + .pg-runners { + break-inside: avoid; + } +} + +/* ------------------------------------------------------------- search -- */ + +.search-results { + padding-top: 1.6rem; +} + +.search-list { + font-family: var(--pg-mono); + font-size: 0.82rem; + color: var(--pg-ink-dim); + padding-bottom: 0.9rem; + margin-bottom: 1.4rem; + border-bottom: 1px solid var(--pg-hairline); +} + +/* docfx anchors these on #search-results, so the selectors are matched to it. */ +#search-results > .sr-items .sr-item { + max-width: 78ch; + padding-left: 1rem; + border-left: var(--pg-rail) solid var(--pg-hairline); + margin-bottom: 1.7rem; +} + +#search-results > .sr-items .sr-item:hover { + border-left-color: var(--pg-cyan); +} + +#search-results > .sr-items .sr-item > .item-title { + font-family: var(--pg-mono); + font-size: 0.98rem; + font-weight: 700; + line-height: 1.4; + letter-spacing: -0.02em; + margin-bottom: 0.2rem; +} + +#search-results > .sr-items .sr-item > .item-title a { + text-decoration: none; + color: var(--bs-heading-color); +} + +#search-results > .sr-items .sr-item > .item-title a:hover { + color: var(--pg-cyan); +} + +#search-results > .sr-items .sr-item > .item-href { + font-family: var(--pg-mono); + font-size: 0.75rem; + color: var(--pg-mute); +} + +#search-results > .sr-items .sr-item > .item-brief { + font-size: 0.89rem; + line-height: 1.6; + color: var(--pg-ink-dim); +} diff --git a/docfx/templates/projgraph/public/main.js b/docfx/templates/projgraph/public/main.js new file mode 100644 index 0000000..a65bc82 --- /dev/null +++ b/docfx/templates/projgraph/public/main.js @@ -0,0 +1,148 @@ +/* + * ProjGraph documentation theme behaviour. + * + * Uses only docfx's documented `main.js` extension points (`defaultTheme`, + * `iconLinks`, `mermaid`, `start`) so a docfx upgrade does not break it. + */ + +const COPY_SHORTCUT = /Mac|iPhone|iPad/.test(navigator.userAgent) + ? '⌘C' + : 'Ctrl+C' + +/** + * Puts a node's text under the user's selection, ready for a manual copy. + * Returns whether the selection was actually made, so the caller can avoid + * telling the user to press a copy shortcut over an empty selection. + */ +function selectContents(node) { + /* getSelection() is null when the window has no associated document. */ + const selection = window.getSelection() + if (!selection) { + return false + } + + const range = document.createRange() + range.selectNodeContents(node) + selection.removeAllRanges() + selection.addRange(range) + return true +} + +/** Copies the adjacent command, then reports the result on the button itself. */ +function wireCopyButtons() { + for (const button of document.querySelectorAll('.pg-copy')) { + const code = button.parentElement?.querySelector('code') + if (!code) { + continue + } + + const label = button.textContent + let revertTimer + + /* Every outcome reverts to the original label, so the button can never be + left showing a stale message. */ + const report = (message, copied, revertAfterMs) => { + clearTimeout(revertTimer) + button.textContent = message + button.dataset.copied = copied + revertTimer = setTimeout(() => { + button.textContent = label + button.dataset.copied = 'false' + }, revertAfterMs) + } + + button.addEventListener('click', async () => { + try { + await navigator.clipboard.writeText(code.textContent.trim()) + report('Copied', 'true', 1600) + } catch { + /* Clipboard access can be refused - over plain HTTP, or by permission. + Select the command first so the shortcut has something to act on, and + fall back to asking for a manual selection when even that is + unavailable. Either way the button reports something. */ + const selected = selectContents(code) + report( + selected ? `Press ${COPY_SHORTCUT}` : 'Select to copy', + 'false', + 4000 + ) + } + }) + } +} + +/* + * The landing page's only non-user-triggered motion: the hero graph draws its + * own edges once, then the cardinality markers arrive. Each edge is measured so + * the dash animation matches its real length instead of a guessed constant. + */ +function drawHeroGraph() { + const transform = document.querySelector('.pg-transform') + if (!transform || window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + return + } + + const edges = transform.querySelectorAll('.pg-g-edge') + if (edges.length === 0) { + return + } + + for (const edge of edges) { + /* getTotalLength() throws on a detached or zero-length node; an edge we + cannot measure simply keeps its static state. */ + try { + edge.style.setProperty('--pg-len', Math.ceil(edge.getTotalLength())) + } catch { + continue + } + } + + transform.dataset.draw = 'pending' + requestAnimationFrame(() => { + requestAnimationFrame(() => { + transform.dataset.draw = 'running' + }) + }) +} + +function enhance() { + wireCopyButtons() + drawHeroGraph() +} + +export default { + defaultTheme: 'dark', + + /* docfx merges this over its own `{ startOnLoad, theme }`, picking the base + theme from the current light/dark setting. Only theme-agnostic values are + set here: anything with a fixed lightness would be wrong in one of the two + themes, so surfaces and text are left to mermaid's own ramp. */ + mermaid: { + fontFamily: + "'ProjGraph Mono', ui-monospace, 'SFMono-Regular', Consolas, monospace", + themeVariables: { + lineColor: '#22a5c4', + }, + }, + + iconLinks: [ + { + icon: 'github', + href: 'https://github.com/HandyS11/ProjGraph', + title: 'Source on GitHub', + }, + { + icon: 'box-seam', + href: 'https://www.nuget.org/packages/ProjGraph.Cli', + title: 'Package on NuGet', + }, + ], + + start: () => { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', enhance, { once: true }) + } else { + enhance() + } + }, +} diff --git a/docfx/toc.yml b/docfx/toc.yml index 3dc1fea..629f9e8 100644 --- a/docfx/toc.yml +++ b/docfx/toc.yml @@ -1,5 +1,7 @@ - name: Guides href: guides/ +- name: Showcase + href: samples/ - name: Developers href: dev/ - name: API Reference