Files

103 lines
4.2 KiB
PowerShell

# Render parsed Markdown into an Avalonia TextBlock.
#
# Mirror of UI/WPF/Extensions/MarkdownRenderWPF.ps1. The parse is shared via
# Internal/MarkdownSimple.ps1 (R10); only the inline construction differs, so the
# Avalonia types stay inside UI/Avalonia (R12).
#
# Translation notes vs the WPF version:
# - Inlines live in Avalonia.Controls.Documents, not System.Windows.Documents.
# - No Bold element; Run.FontWeight carries it, which is what WPF uses too.
# - Run has no text constructor overload, so Text is set as a property.
# - No Hyperlink inline. Links use the pattern this repo already uses for inline
# links in Welcome.axaml: a Button with Classes="link" (styled in
# Themes/Styles.axaml) wrapped in an InlineUIContainer.
function Set-AvaloniaMarkdownText {
[CmdletBinding()]
param(
[Parameter(Mandatory)] $TextBlock,
[string]$Markdown
)
if (-not $TextBlock) { return }
if ($TextBlock.Inlines) { $TextBlock.Inlines.Clear() }
if ([string]::IsNullOrEmpty($Markdown)) { return }
$baseSize = if ($TextBlock.FontSize -gt 0) { [double]$TextBlock.FontSize } else { 12.0 }
$style = Get-SimpleMarkdownStyle
$codeBrush = [Avalonia.Media.SolidColorBrush]::new([Avalonia.Media.Color]::Parse($style.CodeBackground))
$codeFont = [Avalonia.Media.FontFamily]::new($style.CodeFontFamily)
foreach ($block in (ConvertFrom-SimpleMarkdown $Markdown)) {
if ($block.Kind -eq 'Blank') {
$TextBlock.Inlines.Add([Avalonia.Controls.Documents.LineBreak]::new())
continue
}
$lead = switch ($block.Kind) {
'ListItem' { (' ' * ($block.Indent * 4)) + $block.Marker + ' ' }
'Quote' { $style.QuoteMarker }
default { $null }
}
if ($lead) {
$leadRun = [Avalonia.Controls.Documents.Run]::new()
$leadRun.Text = $lead
$TextBlock.Inlines.Add($leadRun)
}
foreach ($inline in $block.Inlines) {
if ($inline.LineBreak) {
$TextBlock.Inlines.Add([Avalonia.Controls.Documents.LineBreak]::new())
if ($block.Kind -eq 'ListItem') {
$pad = [Avalonia.Controls.Documents.Run]::new()
$pad.Text = ' ' * (($block.Indent * 4) + $block.Marker.Length + 1)
$TextBlock.Inlines.Add($pad)
}
continue
}
if ($inline.Url) {
# Captured by value into the handler: $inline is the loop variable
# and would otherwise be whatever the loop ended on by click time.
$linkUrl = [string]$inline.Url
$button = [Avalonia.Controls.Button]::new()
$button.Content = [string]$inline.Text
$button.Classes.Add('link')
$button.Add_Click({
param($clickSender, $clickArgs)
Open-ExternalUri ([string]$clickSender.Tag)
})
# Tag carries the URL so the handler reads it off the sender rather
# than closing over a loop variable.
$button.Tag = $linkUrl
$container = [Avalonia.Controls.Documents.InlineUIContainer]::new()
$container.Child = $button
$TextBlock.Inlines.Add($container)
continue
}
$run = [Avalonia.Controls.Documents.Run]::new()
$run.Text = [string]$inline.Text
if ($inline.Bold -or $block.Kind -eq 'Heading') {
$run.FontWeight = [Avalonia.Media.FontWeight]::Bold
}
if ($block.Kind -eq 'Heading') {
$run.FontSize = Get-SimpleMarkdownHeadingSize -Level $block.Level -BaseSize $baseSize
}
if ($inline.Code -or $block.Kind -eq 'CodeBlock') {
$run.FontFamily = $codeFont
$run.Background = $codeBrush
}
if ($block.Kind -eq 'Quote') {
$run.FontStyle = [Avalonia.Media.FontStyle]::Italic
}
$TextBlock.Inlines.Add($run)
}
$TextBlock.Inlines.Add([Avalonia.Controls.Documents.LineBreak]::new())
}
}