private/Controls/ConvertTo-FormattedTextBlock.ps1
|
<#
.SYNOPSIS Converts markdown-formatted text to a WPF TextBlock with styled inlines. #> function ConvertTo-FormattedTextBlock { param( [Parameter(Mandatory)] [string]$Text, [double]$FontSize = 12, [System.Windows.Media.Brush]$Foreground ) $textBlock = [System.Windows.Controls.TextBlock]@{ TextWrapping = [System.Windows.TextWrapping]::Wrap FontSize = $FontSize } if ($Foreground) { $textBlock.Foreground = $Foreground } # Parse markdown and build inlines # Patterns: **bold**, *italic*, `code` # Italic pattern is restrictive to avoid matching PowerShell wildcards like *Property* # Requires space/start before opening * and space/end after closing * $remaining = $Text while ($remaining.Length -gt 0) { # Find the next markdown pattern $boldMatch = [regex]::Match($remaining, '\*\*([^*]+)\*\*') $italicMatch = [regex]::Match($remaining, '(?<=^|[\s(])\*([^*\s][^*]*[^*\s])\*(?=[\s.,;:!?)]|$)') $codeMatch = [regex]::Match($remaining, '`([^`]+)`') # Find which comes first $nextMatch = $null $matchType = $null $candidates = @() if ($boldMatch.Success) { $candidates += @{ Match = $boldMatch; Type = 'Bold' } } if ($italicMatch.Success) { $candidates += @{ Match = $italicMatch; Type = 'Italic' } } if ($codeMatch.Success) { $candidates += @{ Match = $codeMatch; Type = 'Code' } } if ($candidates.Count -gt 0) { $first = $candidates | Sort-Object { $_.Match.Index } | Select-Object -First 1 $nextMatch = $first.Match $matchType = $first.Type } if (!$nextMatch) { # No more markdown - add remaining as plain text if ($remaining.Length -gt 0) { $run = [System.Windows.Documents.Run]::new($remaining) [void]$textBlock.Inlines.Add($run) } break } # Add text before the match if ($nextMatch.Index -gt 0) { $before = $remaining.Substring(0, $nextMatch.Index) $run = [System.Windows.Documents.Run]::new($before) [void]$textBlock.Inlines.Add($run) } $innerText = $nextMatch.Groups[1].Value switch ($matchType) { 'Bold' { $bold = [System.Windows.Documents.Bold]::new() $bold.Inlines.Add([System.Windows.Documents.Run]::new($innerText)) [void]$textBlock.Inlines.Add($bold) } 'Italic' { $italic = [System.Windows.Documents.Italic]::new() $italic.Inlines.Add([System.Windows.Documents.Run]::new($innerText)) [void]$textBlock.Inlines.Add($italic) } 'Code' { # Style code with monospace font and subtle background $run = [System.Windows.Documents.Run]::new($innerText) $run.FontFamily = [System.Windows.Media.FontFamily]::new('Consolas') # Use a border with the run for background effect (not directly supported) # For simplicity, just use monospace font [void]$textBlock.Inlines.Add($run) } } # Move past the match $remaining = $remaining.Substring($nextMatch.Index + $nextMatch.Length) } return $textBlock } |