@@ -8237,6 +8237,298 @@ $script:FeatherIconCache[$icon].OuterXml
82378237
82388238 </Script >
82398239 </ScriptMethod >
8240+ <ScriptMethod >
8241+ <Name >Help.html</Name >
8242+ <Script >
8243+ < #
8244+ .SYNOPSIS
8245+ Views Help as html
8246+ .DESCRIPTION
8247+ Views the help for Commands as html.
8248+ .NOTES
8249+ This _can_ -InvokeExample.
8250+
8251+ You should not -InvokeExamples of commands you do not know and trust will output as html.
8252+ #>
8253+ param(
8254+ # The command
8255+ [PSObject]
8256+ $Command,
8257+
8258+ # If set, will invoke examples.
8259+ # This can be as dangerous as your examples.
8260+ [switch]
8261+ $InvokeExample,
8262+
8263+ # If set, will not show the command name
8264+ [switch]
8265+ $HideName
8266+ )
8267+
8268+ # Try to get command help
8269+ $CommandHelp = Get-Help -Name $Command -ErrorAction Ignore
8270+ # If we cannot get help, return
8271+ if (-not $CommandHelp) { return }
8272+
8273+ # If the help was a string
8274+ if ($CommandHelp -is [string]) {
8275+ # return preformatted text.
8276+ return "< pre> $([Web.HttpUtility]::HtmlEncode($CommandHelp))< /pre> "
8277+ }
8278+
8279+ # Get the core parts of help we need.
8280+ $synopsis = $CommandHelp.Synopsis
8281+ $description = $CommandHelp.description.text -join [Environment]::NewLine
8282+ $notes = $CommandHelp.alertSet.alert.text -join [Environment]::NewLine
8283+
8284+ filter looksLikeMarkdown {
8285+ if (
8286+ # If it's got a markdown-like link
8287+ $_ -match '\[[^\]]+\]\(' -or
8288+ # Or any of the lines start with markdown special characters
8289+ $_ -split '(?> \r\n|\n)' -match '\s{0,3}[\#*~`]'
8290+ ) {
8291+ # it's probably markdown
8292+ $_
8293+ }
8294+ }
8295+
8296+ filter looksLikeTags {
8297+ if ($_ -match '^\s{0,}< ') {
8298+ $_
8299+ }
8300+ }
8301+
8302+ # Display the command name, synopsis, and description
8303+ if (-not $HideName) {
8304+ "< h1> $([Web.HttpUtility]::HtmlEncode(
8305+ $CommandHelp.Name -replace '(?:.+?/){0,}' -replace '\.ps1$' -replace '\..+?$'
8306+ ))< /h1> "
8307+ }
8308+
8309+ if ($synopsis) {
8310+ if ($synopsis | looksLikeMarkdown) {
8311+ (ConvertFrom-Markdown -InputObject $synopsis).Html
8312+ }
8313+ elseif ($synopsis | looksLikeTags) {
8314+ $synopsis
8315+ } else {
8316+ if ($page -is [Collections.IDictionary] -and -not $page.Title) {
8317+ $page.Title = $([Web.HttpUtility]::HtmlEncode($synopsis))
8318+ }
8319+ "< h2> $([Web.HttpUtility]::HtmlEncode($synopsis))< /h2> "
8320+ }
8321+ }
8322+
8323+ if ($description) {
8324+ if ($description | lookslikeMarkdown) {
8325+ $markdown = (ConvertFrom-Markdown -InputObject $description).Html
8326+ if ($page -is [Collections.IDictionary] -and -not $page.Description) {
8327+ $xmarkdown = "< xhtml> $markdown< /xhtml> "
8328+ $page.Description = $xmarkdown.xhtml.innerText
8329+ }
8330+
8331+ }
8332+ elseif ($description | looksLikeTags) {
8333+ $description
8334+ }
8335+ else {
8336+ if ($page -is [Collections.IDictionary] -and -not $page.Description) {
8337+ $page.Description = $description
8338+ }
8339+ "< h3> $([Web.HttpUtility]::HtmlEncode($description))< /h3> "
8340+ }
8341+ }
8342+
8343+ if ($notes) {
8344+ # If there were notes, convert them from markdown
8345+ (ConvertFrom-Markdown -InputObject $notes).Html
8346+ }
8347+
8348+ #region Grid Styles
8349+ "< style> "
8350+ ".example-grid {
8351+ width: 90vw;
8352+ margin-left: 5vw;
8353+ margin-right: 5vw;
8354+ text-align: center;
8355+ }"
8356+ ".example {
8357+ text-align: center;
8358+ }"
8359+ ".example-code {
8360+ text-align: center;
8361+ font-size: .9rem;
8362+ margin-left: auto;
8363+ margin-right: auto;
8364+ }"
8365+
8366+ "code { text-align: left}"
8367+
8368+ ".example-menu { text-align: right; margin-top: -.5rem; margin-bottom: -.5rem }"
8369+ ".copy-button { float: right; }"
8370+
8371+ ".example-outputs {
8372+ display: flex;
8373+ flex-direction: column;
8374+ align-items: center;
8375+ justify-content: center;
8376+ gap: 1rem;
8377+ margin: 0.8rem;
8378+ padding: 0.2rem;
8379+ width: 90vw;
8380+ }"
8381+ ".example-output { text-align: center }"
8382+ "
8383+ pre {
8384+ position: relative;
8385+ }
8386+ "
8387+ "< /style> "
8388+ #endregion Grid Styles
8389+
8390+ $exampleCount = @($CommandHelp.examples.example).Length
8391+ $progress = [Ordered]@{id=Get-Random}
8392+ $progress.Activity = "$($command) examples"
8393+ # Create a grid for examples
8394+ "< div class='example-grid'> "
8395+ $exampleNumber = 0
8396+ # Walk over each example
8397+ foreach ($example in $CommandHelp.examples.example) {
8398+ $exampleNumber++
8399+
8400+ # Combine the code and remarks
8401+ $exampleLines =
8402+ @(
8403+ $example.Code
8404+ foreach ($remark in $example.Remarks.text) {
8405+ if (-not $remark) { continue }
8406+ $remark
8407+ }
8408+ ) -join ([Environment]::NewLine) -split '(?> \r\n|\n)' # and split into lines
8409+
8410+ # Anything until the first non-comment line is a markdown predicate to the example
8411+ $nonCommentLine = $false
8412+ $markdownLines = @()
8413+
8414+ $progress.PercentComplete = $exampleNumber * 100 / $exampleCount
8415+ $progress.Activity = "$($command) examples $exampleNumber"
8416+ $progress.Status = "$($exampleLines[0])"
8417+ Write-Progress @progress
8418+
8419+ # Go thru each line in the example as part of a loop
8420+ $codeBlock = @(foreach ($exampleLine in $exampleLines) {
8421+ # Any comments until the first uncommentedLine are markdown
8422+ if ($exampleLine -match '^\#' -and -not $nonCommentLine) {
8423+ $markdownLines += $exampleLine -replace '^\#' -replace '^\s+'
8424+ } else {
8425+ $nonCommentLine = $true
8426+ $exampleLine
8427+ }
8428+ }) -join [Environment]::NewLine
8429+
8430+ # Join all of our markdown lines together
8431+ $Markdown = $markdownLines -join [Environment]::NewLine
8432+
8433+ # and start our example div
8434+ "< div class='example'> "
8435+ # If we had markdown, output it
8436+ if ($markdown) {
8437+ (ConvertFrom-Markdown -InputObject $Markdown).Html
8438+ }
8439+ # followed by our sample code
8440+ "< div class='example-code'> "
8441+ "< pre> "
8442+ "< code class='language-powershell'> " +
8443+ [Web.HttpUtility]::HtmlEncode($codeBlock) +
8444+ "< /code> "
8445+ "< /pre> "
8446+ "< /div> "
8447+
8448+ # If we do not want to invoke examples, we can continue to the next example.
8449+ if (-not $InvokeExample) {
8450+ "< /div> "
8451+ "< hr/> "
8452+ continue
8453+ }
8454+ # Otherwise, try to make our example a script block
8455+ $exampleCode =
8456+ try {
8457+ [scriptblock]::Create($codeBlock)
8458+ } catch {
8459+ Write-Warning "Unable to convert $($example.code) to a script"
8460+ continue
8461+ }
8462+
8463+ if (-not $global:ExampleOutputCache) {
8464+ $global:ExampleOutputCache = [Ordered]@{}
8465+ }
8466+ if (-not $global:ExampleOutputCache[$codeBlock]) {
8467+ $global:ExampleOutputCache[$codeBlock] = @(. $exampleCode)
8468+ }
8469+ # then run it and capture the output
8470+ $exampleOutputs = $global:ExampleOutputCache[$codeBlock]
8471+
8472+ # Keep track of our example output count
8473+ $exampleOutputNumber = 0
8474+ # and start walking thru the list
8475+ "< div class='example-outputs'> "
8476+ foreach ($exampleOutput in $exampleOutputs) {
8477+ $exampleOutputNumber++
8478+ # Each output goes in a div
8479+ "< div class='example-output'> "
8480+ # if the output was a file
8481+ if ($exampleOutput -is [IO.FileInfo]) {
8482+ # and that file is SVG
8483+ if ($exampleOutput.Extension -eq '.svg') {
8484+ # include it inline.
8485+ Get-Content $exampleOutput.FullName -Raw
8486+ }
8487+ } else {
8488+ # If the output was a turtle object
8489+ if ($exampleOutput.pstypenames -contains 'Turtle') {
8490+ # set it's ID
8491+ $exampleOutput.ID = "$($Command)-Example-$exampleCounter"
8492+ if ($exampleOutputs.Length -gt 1) {
8493+ # If we have more than out output,
8494+ # attach our example counter
8495+ $exampleOutput.ID += "-$($exampleOutputNumber)"
8496+ }
8497+ }
8498+ # Include our example output inline
8499+ if ($exampleOutput.ToHtml.Invoke) {
8500+ $exampleOutput.ToHtml()
8501+ } else {
8502+ "$exampleOutput"
8503+ }
8504+ }
8505+ "< /div> "
8506+ }
8507+ "< /div> "
8508+ "< hr/> "
8509+ "< /div> "
8510+ }
8511+ "< /div> "
8512+
8513+ $progress.Remove('PercentComplete')
8514+ $progress['Completed'] = $true
8515+ Write-Progress @progress
8516+
8517+ @"
8518+ < script>
8519+ document.querySelectorAll('pre > code').forEach(element => {
8520+ const copyCodeButton = document.createElement('div')
8521+ copyCodeButton.classList.add('copy-button')
8522+ copyCodeButton.onclick = () => navigator.clipboard.writeText(element.innerText)
8523+ copyCodeButton.innerHTML = ``$(
8524+ Format-OpenPackage -View FeatherIcon.svg -Option @{icon='clipboard'}
8525+ )``
8526+ element.parentNode.prepend(copyCodeButton)
8527+ });
8528+ < /script>
8529+ "@
8530+ </Script >
8531+ </ScriptMethod >
82408532 <ScriptMethod >
82418533 <Name >Help.md</Name >
82428534 <Script >
0 commit comments