public/New-HelpPage.ps1
|
function New-HelpPage { <# .SYNOPSIS Builds the complete HTML page for a single command. .DESCRIPTION Orchestrates all ConvertTo-* renderers to produce a full self-contained HTML page for one command, including the themed CSS variables, a page header, a sidebar with navigation links to other commands in the module, and all content sections such as synopsis, syntax, aliases, description, examples, parameters, inputs, outputs, notes, and related links. .PARAMETER Help The PlatyPS CommandHelp model object for the command to render. .PARAMETER AllCommands An array of command name strings used to build the sidebar navigation links. Defaults to an empty array when omitted. .PARAMETER Theme A hashtable of CSS variable names to values, as returned by Resolve-HtmlTheme. When empty or omitted, the built-in default theme is used. .EXAMPLE ```powershell $importParams = @{ Path = '.\docs\MyModule\Get-Widget.md' } $help = Import-MarkdownCommandHelp @importParams $pageParams = @{ Help = $help AllCommands = @('Get-Widget', 'Set-Widget') Theme = Resolve-HtmlTheme } New-HelpPage @pageParams ``` Returns the complete HTML string for the Get-Widget command page with sidebar navigation and the default theme applied. #> [CmdletBinding(HelpUri = 'https://steviecoaster.github.io/PlatyPS.Hosting/PlatyPS.Hosting/New-HelpPage/')] param( [Parameter(Mandatory)] [Microsoft.PowerShell.PlatyPS.Model.CommandHelp] $Help, [Parameter()] [string[]] $AllCommands = @(), [Parameter()] [hashtable] $Theme = @{} ) $title = ConvertTo-HtmlEncoded $Help.Title $module = ConvertTo-HtmlEncoded $Help.ModuleName $dateStamp = if ($Help.Metadata -and $Help.Metadata['ms.date']) { ConvertTo-HtmlEncoded $Help.Metadata['ms.date'] } else { (Get-Date -Format 'MM/dd/yyyy') } $onlineUri = if ($Help.Metadata -and $Help.Metadata['HelpUri']) { $Help.Metadata['HelpUri'] } else { '' } $sidebarLinks = foreach ($name in $AllCommands | Sort-Object) { $encodedName = ConvertTo-HtmlEncoded $name if ($name -eq $Help.Title) { " <li class='current'>$encodedName</li>" } else { " <li><a href='$encodedName.html'>$encodedName</a></li>" } } $body = @( ConvertTo-Synopsis $Help ConvertTo-Syntax $Help ConvertTo-Aliases $Help ConvertTo-Description $Help ConvertTo-Examples $Help ConvertTo-Parameters $Help ConvertTo-InputsOutputs -SectionTitle 'INPUTS' -SectionId 'inputs' -Items $Help.Inputs ConvertTo-InputsOutputs -SectionTitle 'OUTPUTS' -SectionId 'outputs' -Items $Help.Outputs ConvertTo-Notes $Help ConvertTo-RelatedLinks $Help ) -join "`n" # Build :root CSS-variable block from the resolved theme hashtable. # If $Theme is empty (direct call without Export-HtmlCommandHelp), fall back to defaults. $effectiveTheme = if ($Theme.Count -gt 0) { $Theme } else { Resolve-HtmlTheme } $cssVars = ($effectiveTheme.GetEnumerator() | ForEach-Object { " --$($_.Key): $($_.Value);" }) -join "`n" @" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>$title - PowerShell Help</title> <style> :root { $cssVars } /* ---- Reset & base ---- */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: var(--font-body); font-size: 16px; line-height: 1.6; color: var(--color-body-fg); background: var(--color-body-bg); } a { color: var(--color-link); text-decoration: none; } a:hover { text-decoration: underline; } code { font-family: var(--font-code); background: var(--color-code-bg); padding: 0.1em 0.35em; border-radius: 3px; font-size: 0.9em; } pre { background: var(--color-pre-bg); color: var(--color-pre-fg); padding: 1rem 1.25rem; border-radius: 6px; overflow-x: auto; margin: 0.75rem 0; } pre code { background: none; padding: 0; font-size: 0.875rem; color: inherit; } p { margin: 0.5rem 0; } ul { padding-left: 1.5rem; } li { margin: 0.25rem 0; } table { border-collapse: collapse; width: 100%; margin: 0.5rem 0; } th, td { border: 1px solid var(--color-border); padding: 0.4rem 0.7rem; text-align: left; } th { background: var(--color-th-bg); font-weight: 600; width: 160px; } /* ---- Header ---- */ .page-header { background: var(--color-header-bg); color: var(--color-header-fg); padding: 1.25rem 2rem; display: flex; align-items: center; gap: 1.5rem; } .page-header .header-text { flex: 1; } .page-header .module-name { font-size: 0.875rem; opacity: 0.75; } .page-header h1 { font-size: 1.75rem; font-weight: 600; margin: 0.25rem 0 0; } .page-header .meta { font-size: 0.8rem; opacity: 0.6; margin-top: 0.4rem; } .page-header .home-btn { color: var(--color-header-fg); border: 1px solid var(--color-home-btn-border); padding: 0.4rem 0.9rem; border-radius: 4px; font-size: 0.875rem; white-space: nowrap; text-decoration: none; } .page-header .home-btn:hover { background: var(--color-home-btn-hover-bg); color: var(--color-header-fg); text-decoration: none; } /* ---- Two-column layout ---- */ .layout { display: flex; align-items: flex-start; max-width: 1200px; margin: 0 auto; padding: 1.5rem; gap: 2rem; } /* ---- Sidebar ---- */ .sidebar { width: 200px; flex-shrink: 0; position: sticky; top: 1rem; } .sidebar h2 { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-sidebar-label); margin-bottom: 0.5rem; } .sidebar ul { list-style: none; padding: 0; margin: 0; } .sidebar li { margin: 0; } .sidebar li a { display: block; padding: 0.25rem 0.5rem; font-size: 0.85rem; font-family: var(--font-code); color: var(--color-link); border-radius: 3px; } .sidebar li a:hover { background: var(--color-sidebar-hover-bg); text-decoration: none; } .sidebar li.current { padding: 0.25rem 0.5rem; font-size: 0.85rem; font-family: var(--font-code); font-weight: 700; color: var(--color-sidebar-current-fg); background: var(--color-sidebar-current-bg); border-radius: 3px; border-left: 3px solid var(--color-accent); } /* ---- Main content ---- */ main { flex: 1; min-width: 0; } section { margin-bottom: 2rem; } section h2 { font-size: 1.2rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 2px solid var(--color-accent); padding-bottom: 0.3rem; margin-bottom: 0.75rem; color: var(--color-accent-dark); } section h3 { font-size: 1rem; font-weight: 600; margin: 1rem 0 0.4rem; color: #333; } /* ---- Parameters ---- */ .parameter { border: 1px solid var(--color-border); border-radius: 6px; padding: 0.75rem 1rem; margin-bottom: 1rem; background: var(--color-table-bg); } .parameter h3 { color: var(--color-accent); font-family: var(--font-code); font-size: 1rem; } .badges { margin: 0.3rem 0 0.6rem; display: flex; gap: 0.4rem; flex-wrap: wrap; } .badge { font-size: 0.72rem; padding: 0.15em 0.55em; border-radius: 3px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; } .badge.required { background: var(--color-badge-required-bg); color: #fff; } .badge.position { background: var(--color-badge-position-bg); color: #fff; } .badge.dontshow { background: var(--color-badge-dontshow-bg); color: #fff; } .param-meta { font-size: 0.875rem; } /* ---- Examples ---- */ .example { border-left: 4px solid var(--color-accent); padding-left: 1rem; margin-bottom: 1.25rem; } .example h3 { font-size: 0.95rem; color: var(--color-accent); } /* ---- IO types ---- */ .io-type { margin-bottom: 0.75rem; } /* ---- Page footer ---- */ footer { text-align: center; font-size: 0.8rem; color: var(--color-footer-fg); border-top: 1px solid var(--color-border); padding: 1rem; margin-top: 3rem; } </style> </head> <body> <header class="page-header"> <a href="index.html" class="home-btn">⌂ $module</a> <div class="header-text"> <div class="module-name">$module</div> <h1>$title</h1> <div class="meta">Last updated: $dateStamp$(if ($onlineUri) { " | <a href='$onlineUri' style='color:#7ac2ff'>Online version</a>" })</div> </div> </header> <div class="layout"> <nav class="sidebar"> <h2>Commands</h2> <ul> $($sidebarLinks -join "`n") </ul> </nav> <main> $body </main> </div> <footer> Generated by PlatyPS.Hosting — $(Get-Date -Format 'yyyy-MM-dd HH:mm') </footer> </body> </html> "@ } |