diff --git a/Directory.Packages.props b/Directory.Packages.props
index 4d8a7c56..01c1b6fc 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -61,12 +61,10 @@
-
-
diff --git a/Marechai.App/Marechai.App.csproj b/Marechai.App/Marechai.App.csproj
index 6bc168f5..44d585d3 100644
--- a/Marechai.App/Marechai.App.csproj
+++ b/Marechai.App/Marechai.App.csproj
@@ -64,6 +64,7 @@
+
@@ -102,12 +103,10 @@
-
-
diff --git a/Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs b/Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs
index 09532bbd..92d67f9d 100644
--- a/Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs
+++ b/Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs
@@ -103,7 +103,7 @@ public partial class CompanyDetailViewModel : ObservableObject, IRegionAware
private CompanyDto? _soldToCompany;
[ObservableProperty]
- private string _descriptionHtml = string.Empty;
+ private string _descriptionMarkdown = string.Empty;
[ObservableProperty]
private ObservableCollection _people = [];
@@ -194,7 +194,7 @@ public partial class CompanyDetailViewModel : ObservableObject, IRegionAware
///
/// Gets whether a description is available
///
- public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionHtml);
+ public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionMarkdown);
///
/// Gets whether company has multiple logos
@@ -235,7 +235,7 @@ public partial class CompanyDetailViewModel : ObservableObject, IRegionAware
OnPropertyChanged(nameof(HasLogoContent));
}
- partial void OnDescriptionHtmlChanged(string value)
+ partial void OnDescriptionMarkdownChanged(string value)
{
OnPropertyChanged(nameof(HasDescription));
}
@@ -830,7 +830,7 @@ public partial class CompanyDetailViewModel : ObservableObject, IRegionAware
string langCode = GetIso639CodeFromCulture();
CompanyDescriptionDto? desc = await _companyDetailService.GetDescriptionAsync(CompanyId, langCode);
- DescriptionHtml = desc?.Html ?? desc?.Markdown ?? string.Empty;
+ DescriptionMarkdown = desc?.Markdown ?? string.Empty;
}
catch(Exception ex)
{
diff --git a/Marechai.App/Presentation/ViewModels/GpuDetailViewModel.cs b/Marechai.App/Presentation/ViewModels/GpuDetailViewModel.cs
index 49ed8f45..ed4a864a 100644
--- a/Marechai.App/Presentation/ViewModels/GpuDetailViewModel.cs
+++ b/Marechai.App/Presentation/ViewModels/GpuDetailViewModel.cs
@@ -77,7 +77,7 @@ public partial class GpuDetailViewModel : ObservableObject, IRegionAware
private bool _hasSmartphones;
[ObservableProperty]
- private string _descriptionHtml = string.Empty;
+ private string _descriptionMarkdown = string.Empty;
[ObservableProperty]
private Visibility _showDescription = Visibility.Collapsed;
@@ -97,9 +97,9 @@ public partial class GpuDetailViewModel : ObservableObject, IRegionAware
///
/// Gets whether a description is available
///
- public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionHtml);
+ public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionMarkdown);
- partial void OnDescriptionHtmlChanged(string value)
+ partial void OnDescriptionMarkdownChanged(string value)
{
OnPropertyChanged(nameof(HasDescription));
}
@@ -330,7 +330,7 @@ public partial class GpuDetailViewModel : ObservableObject, IRegionAware
string langCode = GetIso639CodeFromCulture();
GpuDescriptionDto? desc = await _gpusService.GetDescriptionAsync(GpuId, langCode);
- DescriptionHtml = desc?.Html ?? desc?.Markdown ?? string.Empty;
+ DescriptionMarkdown = desc?.Markdown ?? string.Empty;
}
catch(Exception ex)
{
diff --git a/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs b/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs
index 85b6842c..476b77c4 100644
--- a/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs
+++ b/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs
@@ -62,7 +62,7 @@ public partial class MachineViewViewModel : ObservableObject, IRegionAware
private string _companyName = string.Empty;
[ObservableProperty]
- private string _descriptionHtml = string.Empty;
+ private string _descriptionMarkdown = string.Empty;
[ObservableProperty]
private string _errorMessage = string.Empty;
@@ -145,9 +145,9 @@ public partial class MachineViewViewModel : ObservableObject, IRegionAware
///
/// Gets whether a description is available
///
- public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionHtml);
+ public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionMarkdown);
- partial void OnDescriptionHtmlChanged(string value)
+ partial void OnDescriptionMarkdownChanged(string value)
{
OnPropertyChanged(nameof(HasDescription));
}
@@ -662,7 +662,7 @@ public partial class MachineViewViewModel : ObservableObject, IRegionAware
string langCode = GetIso639CodeFromCulture();
MachineDescriptionDto? desc = await _computersService.GetDescriptionAsync(machineId, langCode);
- DescriptionHtml = desc?.Html ?? desc?.Markdown ?? string.Empty;
+ DescriptionMarkdown = desc?.Markdown ?? string.Empty;
}
catch(Exception ex)
{
diff --git a/Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs b/Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs
index 67a212e2..2c69ea50 100644
--- a/Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs
+++ b/Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs
@@ -68,7 +68,7 @@ public partial class ProcessorDetailViewModel : ObservableObject, IRegionAware
private bool _hasSmartphones;
[ObservableProperty]
- private string _descriptionHtml = string.Empty;
+ private string _descriptionMarkdown = string.Empty;
[ObservableProperty]
private Visibility _showDescription = Visibility.Collapsed;
@@ -76,9 +76,9 @@ public partial class ProcessorDetailViewModel : ObservableObject, IRegionAware
///
/// Gets whether a description is available
///
- public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionHtml);
+ public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionMarkdown);
- partial void OnDescriptionHtmlChanged(string value)
+ partial void OnDescriptionMarkdownChanged(string value)
{
OnPropertyChanged(nameof(HasDescription));
}
@@ -270,7 +270,7 @@ public partial class ProcessorDetailViewModel : ObservableObject, IRegionAware
string langCode = GetIso639CodeFromCulture();
ProcessorDescriptionDto? desc = await _processorsService.GetDescriptionAsync(ProcessorId, langCode);
- DescriptionHtml = desc?.Html ?? desc?.Markdown ?? string.Empty;
+ DescriptionMarkdown = desc?.Markdown ?? string.Empty;
}
catch(Exception ex)
{
diff --git a/Marechai.App/Presentation/ViewModels/SoftwareViewViewModel.cs b/Marechai.App/Presentation/ViewModels/SoftwareViewViewModel.cs
index 6e79284c..a4ce1f77 100644
--- a/Marechai.App/Presentation/ViewModels/SoftwareViewViewModel.cs
+++ b/Marechai.App/Presentation/ViewModels/SoftwareViewViewModel.cs
@@ -227,7 +227,7 @@ public partial class SoftwareViewViewModel : ObservableObject, IRegionAware
private Visibility _showCredits = Visibility.Collapsed;
[ObservableProperty]
- private string _descriptionHtml = string.Empty;
+ private string _descriptionMarkdown = string.Empty;
[ObservableProperty]
private Visibility _showDescription = Visibility.Collapsed;
@@ -235,12 +235,12 @@ public partial class SoftwareViewViewModel : ObservableObject, IRegionAware
[ObservableProperty]
private ImageSource? _heroImageSource;
- public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionHtml);
+ public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionMarkdown);
public bool HasHeroImage => HeroImageSource is not null;
public bool HasMarechaiScore => MarechaiScore.HasValue;
public bool HasMarechaiScoreRankText => !string.IsNullOrWhiteSpace(MarechaiScoreRankText);
- partial void OnDescriptionHtmlChanged(string value)
+ partial void OnDescriptionMarkdownChanged(string value)
{
OnPropertyChanged(nameof(HasDescription));
}
@@ -707,7 +707,7 @@ public partial class SoftwareViewViewModel : ObservableObject, IRegionAware
{
string langCode = GetIso639CodeFromCulture();
SoftwareDescriptionDto? desc = await _browsingService.GetDescriptionAsync(softwareId, langCode);
- DescriptionHtml = desc?.Html ?? desc?.Markdown ?? string.Empty;
+ DescriptionMarkdown = desc?.Markdown ?? string.Empty;
}
catch(Exception ex)
{
diff --git a/Marechai.App/Presentation/ViewModels/SoundSynthDetailViewModel.cs b/Marechai.App/Presentation/ViewModels/SoundSynthDetailViewModel.cs
index f1233e42..7f5e3a2e 100644
--- a/Marechai.App/Presentation/ViewModels/SoundSynthDetailViewModel.cs
+++ b/Marechai.App/Presentation/ViewModels/SoundSynthDetailViewModel.cs
@@ -70,7 +70,7 @@ public partial class SoundSynthDetailViewModel : ObservableObject, IRegionAware
private bool _hasSmartphones;
[ObservableProperty]
- private string _descriptionHtml = string.Empty;
+ private string _descriptionMarkdown = string.Empty;
[ObservableProperty]
private string _displayName = string.Empty;
@@ -93,9 +93,9 @@ public partial class SoundSynthDetailViewModel : ObservableObject, IRegionAware
///
/// Gets whether a description is available
///
- public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionHtml);
+ public bool HasDescription => !string.IsNullOrWhiteSpace(DescriptionMarkdown);
- partial void OnDescriptionHtmlChanged(string value)
+ partial void OnDescriptionMarkdownChanged(string value)
{
OnPropertyChanged(nameof(HasDescription));
}
@@ -290,7 +290,7 @@ public partial class SoundSynthDetailViewModel : ObservableObject, IRegionAware
string langCode = GetIso639CodeFromCulture();
SoundSynthDescriptionDto? desc = await _soundSynthsService.GetDescriptionAsync(SoundSynthId, langCode);
- DescriptionHtml = desc?.Html ?? desc?.Markdown ?? string.Empty;
+ DescriptionMarkdown = desc?.Markdown ?? string.Empty;
}
catch(Exception ex)
{
diff --git a/Marechai.App/Presentation/Views/CompanyDetailPage.xaml b/Marechai.App/Presentation/Views/CompanyDetailPage.xaml
index d75ee668..39d30d0f 100644
--- a/Marechai.App/Presentation/Views/CompanyDetailPage.xaml
+++ b/Marechai.App/Presentation/Views/CompanyDetailPage.xaml
@@ -4,6 +4,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="using:Marechai.App.Presentation.Converters"
+ xmlns:local="using:Marechai.App.Presentation.Views"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
NavigationCacheMode="Required"
@@ -856,8 +857,7 @@
HorizontalScrollBarVisibility="Disabled">
-
+
diff --git a/Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs b/Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs
index 7180546f..2be1a86b 100644
--- a/Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs
+++ b/Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs
@@ -1,7 +1,6 @@
#nullable enable
using System;
-using System.ComponentModel;
using Windows.System;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
@@ -14,56 +13,6 @@ public sealed partial class CompanyDetailPage : Page
public CompanyDetailPage()
{
InitializeComponent();
- DataContextChanged += OnDataContextChanged;
- }
-
- private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
- {
- if(args.NewValue is CompanyDetailViewModel vm)
- vm.PropertyChanged += OnViewModelPropertyChanged;
- }
-
- private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if(e.PropertyName == nameof(CompanyDetailViewModel.DescriptionHtml) &&
- sender is CompanyDetailViewModel vm &&
- !string.IsNullOrWhiteSpace(vm.DescriptionHtml))
- {
- bool isDark = ActualTheme == ElementTheme.Dark;
- var html = WrapInHtmlDocument(vm.DescriptionHtml, isDark);
- DescriptionWebView.NavigateToString(html);
- }
- }
-
- private static string WrapInHtmlDocument(string bodyHtml, bool isDark)
- {
- string textColor = isDark ? "#e0e0e0" : "#1a1a1a";
- string bgColor = isDark ? "#1e1e1e" : "#ffffff";
- string linkColor = isDark ? "#6cb4ee" : "#0063b1";
-
- return $$"""
-
-
-
-
-
-
-
- {{bodyHtml}}
-
- """;
}
private async void OnTwitterClick(object sender, RoutedEventArgs e)
diff --git a/Marechai.App/Presentation/Views/GpuDetailPage.xaml b/Marechai.App/Presentation/Views/GpuDetailPage.xaml
index 4185a093..04d655ab 100644
--- a/Marechai.App/Presentation/Views/GpuDetailPage.xaml
+++ b/Marechai.App/Presentation/Views/GpuDetailPage.xaml
@@ -4,6 +4,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="using:Marechai.App.Presentation.Converters"
+ xmlns:local="using:Marechai.App.Presentation.Views"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
NavigationCacheMode="Required"
@@ -256,8 +257,7 @@
-
+
diff --git a/Marechai.App/Presentation/Views/GpuDetailPage.xaml.cs b/Marechai.App/Presentation/Views/GpuDetailPage.xaml.cs
index fa855a7d..77534383 100644
--- a/Marechai.App/Presentation/Views/GpuDetailPage.xaml.cs
+++ b/Marechai.App/Presentation/Views/GpuDetailPage.xaml.cs
@@ -1,6 +1,5 @@
#nullable enable
-using System.ComponentModel;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -12,56 +11,6 @@ public sealed partial class GpuDetailPage : Page
public GpuDetailPage()
{
InitializeComponent();
- DataContextChanged += OnDataContextChanged;
- }
-
- private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
- {
- if(args.NewValue is GpuDetailViewModel vm)
- vm.PropertyChanged += OnViewModelPropertyChanged;
- }
-
- private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if(e.PropertyName == nameof(GpuDetailViewModel.DescriptionHtml) &&
- sender is GpuDetailViewModel vm &&
- !string.IsNullOrWhiteSpace(vm.DescriptionHtml))
- {
- bool isDark = ActualTheme == ElementTheme.Dark;
- var html = WrapInHtmlDocument(vm.DescriptionHtml, isDark);
- DescriptionWebView.NavigateToString(html);
- }
- }
-
- private static string WrapInHtmlDocument(string bodyHtml, bool isDark)
- {
- string textColor = isDark ? "#e0e0e0" : "#1a1a1a";
- string bgColor = isDark ? "#1e1e1e" : "#ffffff";
- string linkColor = isDark ? "#6cb4ee" : "#0063b1";
-
- return $$"""
-
-
-
-
-
-
-
- {{bodyHtml}}
-
- """;
}
private void ComputersSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
diff --git a/Marechai.App/Presentation/Views/MachineViewPage.xaml b/Marechai.App/Presentation/Views/MachineViewPage.xaml
index 49301a14..9c21c4a9 100644
--- a/Marechai.App/Presentation/Views/MachineViewPage.xaml
+++ b/Marechai.App/Presentation/Views/MachineViewPage.xaml
@@ -7,6 +7,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="using:Marechai.App.Presentation.Converters"
xmlns:wctui="using:CommunityToolkit.WinUI.UI.Controls"
+ xmlns:local="using:Marechai.App.Presentation.Views"
xmlns:components="using:Marechai.App.Presentation.Components"
NavigationCacheMode="Disabled"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
@@ -396,8 +397,7 @@
-
+
diff --git a/Marechai.App/Presentation/Views/MachineViewPage.xaml.cs b/Marechai.App/Presentation/Views/MachineViewPage.xaml.cs
index c666d977..e698877f 100644
--- a/Marechai.App/Presentation/Views/MachineViewPage.xaml.cs
+++ b/Marechai.App/Presentation/Views/MachineViewPage.xaml.cs
@@ -1,8 +1,5 @@
#nullable enable
-using System.ComponentModel;
-using Marechai.App.Presentation.ViewModels;
-using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Marechai.App.Presentation.Views;
@@ -12,55 +9,5 @@ public sealed partial class MachineViewPage : Page
public MachineViewPage()
{
InitializeComponent();
- DataContextChanged += OnDataContextChanged;
}
-
- private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
- {
- if(args.NewValue is MachineViewViewModel vm)
- vm.PropertyChanged += OnViewModelPropertyChanged;
- }
-
- private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if(e.PropertyName == nameof(MachineViewViewModel.DescriptionHtml) &&
- sender is MachineViewViewModel vm &&
- !string.IsNullOrWhiteSpace(vm.DescriptionHtml))
- {
- bool isDark = ActualTheme == ElementTheme.Dark;
- var html = WrapInHtmlDocument(vm.DescriptionHtml, isDark);
- DescriptionWebView.NavigateToString(html);
- }
- }
-
- private static string WrapInHtmlDocument(string bodyHtml, bool isDark)
- {
- string textColor = isDark ? "#e0e0e0" : "#1a1a1a";
- string bgColor = isDark ? "#1e1e1e" : "#ffffff";
- string linkColor = isDark ? "#6cb4ee" : "#0063b1";
-
- return $$"""
-
-
-
-
-
-
-
- {{bodyHtml}}
-
- """;
- }
-}
\ No newline at end of file
+}
diff --git a/Marechai.App/Presentation/Views/MarkdownRenderer.cs b/Marechai.App/Presentation/Views/MarkdownRenderer.cs
new file mode 100644
index 00000000..6d32e83e
--- /dev/null
+++ b/Marechai.App/Presentation/Views/MarkdownRenderer.cs
@@ -0,0 +1,337 @@
+#nullable enable
+
+using System;
+using System.Linq;
+using Markdig;
+using Markdig.Extensions.Footnotes;
+using Markdig.Extensions.Tables;
+using Markdig.Syntax;
+using Markdig.Syntax.Inlines;
+using Microsoft.UI.Text;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Documents;
+using Microsoft.UI.Xaml.Media;
+using Windows.System;
+using Block = Markdig.Syntax.Block;
+using Markdown = Markdig.Markdown;
+
+namespace Marechai.App.Presentation.Views;
+
+///
+/// Renders markdown directly into native XAML elements (TextBlock/Span/Hyperlink/Image trees) instead of going
+/// through HTML and an embedded WebView. Uno's WebView2 doesn't reliably composite on every desktop backend
+/// (it renders nothing on Linux/X11), so this avoids depending on a native embedded browser at all - it works
+/// identically on every Uno target. DisableHtml() means literal HTML in the source is never interpreted,
+/// just shown as plain text, so there's no markup-injection surface to sanitize.
+///
+internal static class MarkdownRenderer
+{
+ static readonly MarkdownPipeline _pipeline = new MarkdownPipelineBuilder()
+ .UseAdvancedExtensions()
+ .DisableHtml()
+ .Build();
+
+ public static UIElement Render(string? markdown)
+ {
+ var root = new StackPanel { Spacing = 8 };
+
+ if(string.IsNullOrWhiteSpace(markdown)) return root;
+
+ MarkdownDocument document = Markdown.Parse(markdown, _pipeline);
+
+ foreach(Block block in document) AppendBlock(root, block);
+
+ return root;
+ }
+
+ static void AppendBlock(Panel container, Block block)
+ {
+ switch(block)
+ {
+ case HeadingBlock heading:
+ container.Children.Add(BuildHeading(heading));
+ break;
+
+ case QuoteBlock quote:
+ container.Children.Add(BuildQuote(quote));
+ break;
+
+ case ListBlock list:
+ container.Children.Add(BuildList(list));
+ break;
+
+ case FencedCodeBlock or CodeBlock:
+ container.Children.Add(BuildCodeBlock((LeafBlock)block));
+ break;
+
+ case ThematicBreakBlock:
+ container.Children.Add(new Border
+ {
+ Height = 1,
+ Opacity = 0.4,
+ Margin = new Thickness(0, 4, 0, 4),
+ BorderThickness = new Thickness(1),
+ BorderBrush = ThemeBrush("TextFillColorSecondaryBrush")
+ });
+
+ break;
+
+ case Table table:
+ container.Children.Add(BuildTable(table));
+ break;
+
+ case FootnoteGroup footnotes:
+ container.Children.Add(BuildFootnotes(footnotes));
+ break;
+
+ case LeafBlock { Inline: not null } leaf:
+ container.Children.Add(BuildParagraph(leaf.Inline));
+ break;
+ }
+ }
+
+ static TextBlock BuildHeading(HeadingBlock heading)
+ {
+ var textBlock = new TextBlock
+ {
+ TextWrapping = TextWrapping.Wrap,
+ FontWeight = FontWeights.SemiBold,
+ FontSize = heading.Level switch
+ {
+ 1 => 24,
+ 2 => 20,
+ 3 => 18,
+ 4 => 16,
+ 5 => 15,
+ _ => 14
+ }
+ };
+
+ if(heading.Inline is not null) AppendInlines(textBlock.Inlines, heading.Inline);
+
+ return textBlock;
+ }
+
+ static TextBlock BuildParagraph(ContainerInline? inline)
+ {
+ var textBlock = new TextBlock { TextWrapping = TextWrapping.Wrap, FontSize = 14 };
+
+ if(inline is not null) AppendInlines(textBlock.Inlines, inline);
+
+ return textBlock;
+ }
+
+ static Border BuildQuote(QuoteBlock quote)
+ {
+ var inner = new StackPanel { Spacing = 4 };
+
+ foreach(Block child in quote) AppendBlock(inner, child);
+
+ return new Border
+ {
+ BorderBrush = ThemeBrush("TextFillColorSecondaryBrush"),
+ BorderThickness = new Thickness(3, 0, 0, 0),
+ Padding = new Thickness(8, 0, 0, 0),
+ Child = inner
+ };
+ }
+
+ static StackPanel BuildList(ListBlock list)
+ {
+ var panel = new StackPanel { Spacing = 4 };
+ int index = list.IsOrdered && int.TryParse(list.OrderedStart, out int start) ? start : 1;
+
+ foreach(Block item in list)
+ {
+ if(item is not ListItemBlock listItem) continue;
+
+ var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 4 };
+
+ row.Children.Add(new TextBlock
+ {
+ Text = list.IsOrdered ? $"{index}." : "•", FontSize = 14, Margin = new Thickness(8, 0, 0, 0)
+ });
+
+ var content = new StackPanel { Spacing = 4 };
+
+ foreach(Block child in listItem) AppendBlock(content, child);
+
+ row.Children.Add(content);
+ panel.Children.Add(row);
+ index++;
+ }
+
+ return panel;
+ }
+
+ static Border BuildCodeBlock(LeafBlock code)
+ {
+ var textBlock = new TextBlock
+ {
+ Text = code.Lines.ToString(),
+ FontFamily = new FontFamily("Consolas"),
+ FontSize = 13,
+ TextWrapping = TextWrapping.NoWrap
+ };
+
+ return new Border
+ {
+ Background = ThemeBrush("CardBackgroundFillColorDefaultBrush"),
+ Padding = new Thickness(8),
+ CornerRadius = new CornerRadius(4),
+ Child = new ScrollViewer
+ {
+ Content = textBlock,
+ HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
+ VerticalScrollBarVisibility = ScrollBarVisibility.Disabled
+ }
+ };
+ }
+
+ static Grid BuildTable(Table table)
+ {
+ var grid = new Grid();
+
+ for(int c = 0; c < table.ColumnDefinitions.Count; c++) grid.ColumnDefinitions.Add(new ColumnDefinition());
+
+ int rowIndex = 0;
+
+ foreach(TableRow row in table.OfType())
+ {
+ grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
+ int columnIndex = 0;
+
+ foreach(TableCell cell in row.OfType())
+ {
+ var textBlock = new TextBlock
+ {
+ TextWrapping = TextWrapping.Wrap,
+ Margin = new Thickness(4),
+ FontWeight = row.IsHeader ? FontWeights.SemiBold : FontWeights.Normal
+ };
+
+ foreach(Block cellBlock in cell)
+ if(cellBlock is ParagraphBlock { Inline: not null } paragraph)
+ AppendInlines(textBlock.Inlines, paragraph.Inline);
+
+ Grid.SetRow(textBlock, rowIndex);
+ Grid.SetColumn(textBlock, columnIndex);
+ grid.Children.Add(textBlock);
+ columnIndex++;
+ }
+
+ rowIndex++;
+ }
+
+ return grid;
+ }
+
+ static StackPanel BuildFootnotes(FootnoteGroup group)
+ {
+ var panel = new StackPanel { Spacing = 4, Margin = new Thickness(0, 8, 0, 0) };
+
+ panel.Children.Add(new Border
+ {
+ Height = 1,
+ Opacity = 0.4,
+ Margin = new Thickness(0, 0, 0, 4),
+ BorderThickness = new Thickness(1),
+ BorderBrush = ThemeBrush("TextFillColorSecondaryBrush")
+ });
+
+ foreach(Block block in group)
+ {
+ if(block is not Footnote footnote) continue;
+
+ var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 4 };
+
+ row.Children.Add(new TextBlock
+ {
+ Text = $"{footnote.Order}.", FontSize = 12, Opacity = 0.7, Margin = new Thickness(8, 0, 0, 0)
+ });
+
+ var content = new StackPanel { Spacing = 4 };
+
+ foreach(Block child in footnote) AppendBlock(content, child);
+
+ row.Children.Add(content);
+ panel.Children.Add(row);
+ }
+
+ return panel;
+ }
+
+ static void AppendInlines(InlineCollection target, ContainerInline container)
+ {
+ foreach(Markdig.Syntax.Inlines.Inline inline in container) AppendInline(target, inline);
+ }
+
+ static void AppendInline(InlineCollection target, Markdig.Syntax.Inlines.Inline inline)
+ {
+ switch(inline)
+ {
+ case LiteralInline literal:
+ target.Add(new Run { Text = literal.Content.ToString() });
+ break;
+
+ case LineBreakInline:
+ target.Add(new LineBreak());
+ break;
+
+ case EmphasisInline emphasis:
+ var span = new Span();
+
+ if(emphasis.DelimiterCount >= 2) span.FontWeight = FontWeights.Bold;
+ else span.FontStyle = Windows.UI.Text.FontStyle.Italic;
+
+ AppendInlines(span.Inlines, emphasis);
+ target.Add(span);
+ break;
+
+ case CodeInline code:
+ target.Add(new Run { Text = code.Content, FontFamily = new FontFamily("Consolas") });
+ break;
+
+ case LinkInline { IsImage: true, Url: not null } image:
+ target.Add(new InlineUIContainer
+ {
+ Child = new Image
+ {
+ Source = new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(new Uri(image.Url)),
+ MaxWidth = 480,
+ MaxHeight = 360,
+ Stretch = Stretch.Uniform
+ }
+ });
+
+ break;
+
+ case LinkInline link:
+ var hyperlink = new Hyperlink();
+ AppendInlines(hyperlink.Inlines, link);
+
+ if(Uri.TryCreate(link.Url, UriKind.Absolute, out Uri? uri))
+ hyperlink.Click += async (_, _) => await Launcher.LaunchUriAsync(uri);
+
+ target.Add(hyperlink);
+ break;
+
+ case FootnoteLink footnoteLink:
+ target.Add(new Span
+ {
+ FontSize = 11,
+ Inlines = { new Run { Text = $"[{footnoteLink.Footnote?.Order}]" } }
+ });
+
+ break;
+
+ case ContainerInline containerInline:
+ AppendInlines(target, containerInline);
+ break;
+ }
+ }
+
+ static Brush ThemeBrush(string resourceKey) =>
+ (Brush)Application.Current.Resources[resourceKey];
+}
diff --git a/Marechai.App/Presentation/Views/MessageMarkdownView.xaml b/Marechai.App/Presentation/Views/MessageMarkdownView.xaml
index a9cfe638..4064344e 100644
--- a/Marechai.App/Presentation/Views/MessageMarkdownView.xaml
+++ b/Marechai.App/Presentation/Views/MessageMarkdownView.xaml
@@ -2,13 +2,7 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
+
diff --git a/Marechai.App/Presentation/Views/MessageMarkdownView.xaml.cs b/Marechai.App/Presentation/Views/MessageMarkdownView.xaml.cs
index 28830509..967886df 100644
--- a/Marechai.App/Presentation/Views/MessageMarkdownView.xaml.cs
+++ b/Marechai.App/Presentation/Views/MessageMarkdownView.xaml.cs
@@ -1,10 +1,7 @@
#nullable enable
-using System;
-using CommunityToolkit.WinUI.UI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using Windows.System;
namespace Marechai.App.Presentation.Views;
@@ -27,16 +24,9 @@ public sealed partial class MessageMarkdownView : UserControl
static void OnMarkdownChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- if(d is MessageMarkdownView view)
- view.MarkdownBlock.Text = e.NewValue as string ?? string.Empty;
- }
+ if(d is not MessageMarkdownView view) return;
- async void MarkdownBlock_OnLinkClicked(object sender, LinkClickedEventArgs e)
- {
- if(string.IsNullOrWhiteSpace(e.Link)) return;
-
- if(Uri.TryCreate(e.Link, UriKind.Absolute, out Uri? uri) &&
- (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps || uri.Scheme == Uri.UriSchemeMailto))
- await Launcher.LaunchUriAsync(uri);
+ view.RootGrid.Children.Clear();
+ view.RootGrid.Children.Add(MarkdownRenderer.Render(e.NewValue as string));
}
}
diff --git a/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml b/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml
index f6602256..15a55a03 100644
--- a/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml
+++ b/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml
@@ -4,6 +4,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="using:Marechai.App.Presentation.Converters"
+ xmlns:local="using:Marechai.App.Presentation.Views"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
NavigationCacheMode="Required"
@@ -422,8 +423,7 @@
-
+
diff --git a/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs b/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs
index 9184ae33..e361fe42 100644
--- a/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs
+++ b/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs
@@ -1,6 +1,5 @@
#nullable enable
-using System.ComponentModel;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -12,56 +11,6 @@ public sealed partial class ProcessorDetailPage : Page
public ProcessorDetailPage()
{
InitializeComponent();
- DataContextChanged += OnDataContextChanged;
- }
-
- private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
- {
- if(args.NewValue is ProcessorDetailViewModel vm)
- vm.PropertyChanged += OnViewModelPropertyChanged;
- }
-
- private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if(e.PropertyName == nameof(ProcessorDetailViewModel.DescriptionHtml) &&
- sender is ProcessorDetailViewModel vm &&
- !string.IsNullOrWhiteSpace(vm.DescriptionHtml))
- {
- bool isDark = ActualTheme == ElementTheme.Dark;
- var html = WrapInHtmlDocument(vm.DescriptionHtml, isDark);
- DescriptionWebView.NavigateToString(html);
- }
- }
-
- private static string WrapInHtmlDocument(string bodyHtml, bool isDark)
- {
- string textColor = isDark ? "#e0e0e0" : "#1a1a1a";
- string bgColor = isDark ? "#1e1e1e" : "#ffffff";
- string linkColor = isDark ? "#6cb4ee" : "#0063b1";
-
- return $$"""
-
-
-
-
-
-
-
- {{bodyHtml}}
-
- """;
}
private void ComputersSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
diff --git a/Marechai.App/Presentation/Views/SoftwareViewPage.xaml b/Marechai.App/Presentation/Views/SoftwareViewPage.xaml
index 3067ccba..17c5b93d 100644
--- a/Marechai.App/Presentation/Views/SoftwareViewPage.xaml
+++ b/Marechai.App/Presentation/Views/SoftwareViewPage.xaml
@@ -7,6 +7,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="using:Marechai.App.Presentation.Converters"
xmlns:components="using:Marechai.App.Presentation.Components"
+ xmlns:local="using:Marechai.App.Presentation.Views"
xmlns:wctui="using:CommunityToolkit.WinUI.UI.Controls"
NavigationCacheMode="Disabled"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
@@ -234,8 +235,7 @@
-
+
diff --git a/Marechai.App/Presentation/Views/SoftwareViewPage.xaml.cs b/Marechai.App/Presentation/Views/SoftwareViewPage.xaml.cs
index 92739a78..ab30b1be 100644
--- a/Marechai.App/Presentation/Views/SoftwareViewPage.xaml.cs
+++ b/Marechai.App/Presentation/Views/SoftwareViewPage.xaml.cs
@@ -14,13 +14,6 @@ public sealed partial class SoftwareViewPage : Page
public SoftwareViewPage()
{
InitializeComponent();
- DataContextChanged += OnDataContextChanged;
- }
-
- private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
- {
- if(args.NewValue is SoftwareViewViewModel vm)
- vm.PropertyChanged += OnViewModelPropertyChanged;
}
private async void OpenReviewDialog_Click(object sender, RoutedEventArgs e)
@@ -143,46 +136,4 @@ public sealed partial class SoftwareViewPage : Page
vm.ClearReviewFeedbackCommand.Execute(null);
}
- private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if(e.PropertyName == nameof(SoftwareViewViewModel.DescriptionHtml) &&
- sender is SoftwareViewViewModel vm &&
- !string.IsNullOrWhiteSpace(vm.DescriptionHtml))
- {
- bool isDark = ActualTheme == ElementTheme.Dark;
- var html = WrapInHtmlDocument(vm.DescriptionHtml, isDark);
- DescriptionWebView.NavigateToString(html);
- }
- }
-
- private static string WrapInHtmlDocument(string bodyHtml, bool isDark)
- {
- string textColor = isDark ? "#e0e0e0" : "#1a1a1a";
- string bgColor = isDark ? "#1e1e1e" : "#ffffff";
- string linkColor = isDark ? "#6cb4ee" : "#0063b1";
-
- return $$"""
-
-
-
-
-
-
-
- {{bodyHtml}}
-
- """;
- }
}
diff --git a/Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml b/Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml
index f186260a..491640e9 100644
--- a/Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml
+++ b/Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml
@@ -4,6 +4,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="using:Marechai.App.Presentation.Converters"
+ xmlns:local="using:Marechai.App.Presentation.Views"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
NavigationCacheMode="Required"
@@ -194,8 +195,7 @@
-
+
diff --git a/Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml.cs b/Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml.cs
index c7bef977..9794e8d0 100644
--- a/Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml.cs
+++ b/Marechai.App/Presentation/Views/SoundSynthDetailPage.xaml.cs
@@ -1,6 +1,5 @@
#nullable enable
-using System.ComponentModel;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -12,56 +11,6 @@ public sealed partial class SoundSynthDetailPage : Page
public SoundSynthDetailPage()
{
InitializeComponent();
- DataContextChanged += OnDataContextChanged;
- }
-
- private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
- {
- if(args.NewValue is SoundSynthDetailViewModel vm)
- vm.PropertyChanged += OnViewModelPropertyChanged;
- }
-
- private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if(e.PropertyName == nameof(SoundSynthDetailViewModel.DescriptionHtml) &&
- sender is SoundSynthDetailViewModel vm &&
- !string.IsNullOrWhiteSpace(vm.DescriptionHtml))
- {
- bool isDark = ActualTheme == ElementTheme.Dark;
- var html = WrapInHtmlDocument(vm.DescriptionHtml, isDark);
- DescriptionWebView.NavigateToString(html);
- }
- }
-
- private static string WrapInHtmlDocument(string bodyHtml, bool isDark)
- {
- string textColor = isDark ? "#e0e0e0" : "#1a1a1a";
- string bgColor = isDark ? "#1e1e1e" : "#ffffff";
- string linkColor = isDark ? "#6cb4ee" : "#0063b1";
-
- return $$"""
-
-
-
-
-
-
-
- {{bodyHtml}}
-
- """;
}
private void ComputersSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)