From 8d0aa5249cdc31d78e6f740ba838bc6d624a6d6c Mon Sep 17 00:00:00 2001 From: bluepilledgreat <97983689+bluepilledgreat@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:10:43 +0000 Subject: [PATCH 1/4] more robust markdowntextblock --- .../UI/Elements/Controls/MarkdownTextBlock.cs | 109 ++++++++++++++---- 1 file changed, 89 insertions(+), 20 deletions(-) diff --git a/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs b/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs index 6e1e0d7..bd97b8f 100644 --- a/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs +++ b/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs @@ -6,6 +6,7 @@ using System.Windows; using Markdig.Syntax; using Markdig.Syntax.Inlines; using Markdig; +using System.Windows.Media; namespace Bloxstrap.UI.Elements.Controls { @@ -19,15 +20,97 @@ namespace Bloxstrap.UI.Elements.Controls { public static readonly DependencyProperty MarkdownTextProperty = DependencyProperty.Register(nameof(MarkdownText), typeof(string), typeof(MarkdownTextBlock), - new FrameworkPropertyMetadata(string.Empty,FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnTextMarkdownChanged)); + new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnTextMarkdownChanged)); [Localizability(LocalizationCategory.Text)] public string MarkdownText { - get => Inlines.ToString() ?? ""; + get => (string)GetValue(MarkdownTextProperty); set => SetValue(MarkdownTextProperty, value); } + /// Span, skip + private static (Span, int) GetInlineUntilEndTagDetected(Markdig.Syntax.Inlines.Inline? inline, string tagName) + { + string endTag = $"<{tagName}/>"; // TODO: better way of doing this + + var span = new Span(); + + int skip = 0; + var current = inline; + while (current is Markdig.Syntax.Inlines.Inline currentInline) + { + skip++; + + if (currentInline is HtmlInline html) + { + if (html.Tag == endTag) + return (span, skip); + } + + (var childInline, int childSkip) = GetWpfInlineFromMarkdownInline(currentInline); + if (childInline != null) + span.Inlines.Add(childInline); + + skip += childSkip; + + current = currentInline.NextSibling; + } + + throw new Exception("End tag not detected"); + } + + /// Inline, skip + private static (System.Windows.Documents.Inline?, int) GetWpfInlineFromMarkdownInline(Markdig.Syntax.Inlines.Inline? inline) + { + if (inline is LiteralInline literalInline) + { + return (new Run(literalInline.ToString()), 0); + } + else if (inline is LinkInline linkInline) + { + string? url = linkInline.Url; + var textInline = linkInline.FirstChild; + + if (string.IsNullOrEmpty(url)) + { + return GetWpfInlineFromMarkdownInline(textInline); + } + + (var childInline, int skip) = GetWpfInlineFromMarkdownInline(textInline); + + return (new Hyperlink(childInline) + { + Command = GlobalViewModel.OpenWebpageCommand, + CommandParameter = url + }, skip); + } + else if (inline is HtmlInline htmlInline) + { + string? tag = htmlInline.Tag; // TODO: parse tag + var nextInline = htmlInline.NextSibling; + + if (tag == "") + { + (var span, int skip) = GetInlineUntilEndTagDetected(nextInline, "highlight"); + span.Background = new SolidColorBrush(Color.FromArgb(50,255,255,255)); + return (span, skip); + } + } + + return (null, 0); + } + + /// Skip + private int AddMarkdownInline(Markdig.Syntax.Inlines.Inline? inline) + { + (var wpfInline, int skip) = GetWpfInlineFromMarkdownInline(inline); + if (wpfInline != null) + Inlines.Add(wpfInline); + + return skip; + } + private static void OnTextMarkdownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { if (dependencyObject is not MarkdownTextBlock markdownTextBlock) @@ -43,26 +126,12 @@ namespace Bloxstrap.UI.Elements.Controls if (document.FirstOrDefault() is not ParagraphBlock paragraphBlock || paragraphBlock.Inline == null) return; - foreach (var inline in paragraphBlock.Inline) + for (int i = 0; i < paragraphBlock.Inline.Count(); i++) { - if (inline is LiteralInline literalInline) - { - markdownTextBlock.Inlines.Add(new Run(literalInline.ToString())); - } - else if (inline is LinkInline linkInline) - { - string? url = linkInline.Url; - string? text = linkInline.FirstChild?.ToString(); + var inline = paragraphBlock.Inline.ElementAt(i); - if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(text)) - continue; - - markdownTextBlock.Inlines.Add(new Hyperlink(new Run(text)) - { - Command = GlobalViewModel.OpenWebpageCommand, - CommandParameter = url - }); - } + int skip = markdownTextBlock.AddMarkdownInline(inline); + i += skip; } } } From dd70d729f10dd1527c6e28b9d747c740c0ca6a3e Mon Sep 17 00:00:00 2001 From: bluepilledgreat <97983689+bluepilledgreat@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:13:42 +0000 Subject: [PATCH 2/4] oops --- Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs b/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs index bd97b8f..c99b236 100644 --- a/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs +++ b/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs @@ -32,7 +32,7 @@ namespace Bloxstrap.UI.Elements.Controls /// Span, skip private static (Span, int) GetInlineUntilEndTagDetected(Markdig.Syntax.Inlines.Inline? inline, string tagName) { - string endTag = $"<{tagName}/>"; // TODO: better way of doing this + string endTag = $""; // TODO: better way of doing this var span = new Span(); From 70a39376c8db3a126e6d067d137e541e9147ea96 Mon Sep 17 00:00:00 2001 From: bluepilledgreat <97983689+bluepilledgreat@users.noreply.github.com> Date: Sat, 3 Feb 2024 00:33:31 +0000 Subject: [PATCH 3/4] bold, italic, and better marked text support --- .../UI/Elements/Controls/MarkdownTextBlock.cs | 105 ++++++++---------- 1 file changed, 47 insertions(+), 58 deletions(-) diff --git a/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs b/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs index c99b236..f571ce2 100644 --- a/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs +++ b/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs @@ -18,6 +18,8 @@ namespace Bloxstrap.UI.Elements.Controls [Localizability(LocalizationCategory.Text)] class MarkdownTextBlock : TextBlock { + private static MarkdownPipeline _markdownPipeline; + public static readonly DependencyProperty MarkdownTextProperty = DependencyProperty.Register(nameof(MarkdownText), typeof(string), typeof(MarkdownTextBlock), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnTextMarkdownChanged)); @@ -29,43 +31,39 @@ namespace Bloxstrap.UI.Elements.Controls set => SetValue(MarkdownTextProperty, value); } - /// Span, skip - private static (Span, int) GetInlineUntilEndTagDetected(Markdig.Syntax.Inlines.Inline? inline, string tagName) - { - string endTag = $""; // TODO: better way of doing this - - var span = new Span(); - - int skip = 0; - var current = inline; - while (current is Markdig.Syntax.Inlines.Inline currentInline) - { - skip++; - - if (currentInline is HtmlInline html) - { - if (html.Tag == endTag) - return (span, skip); - } - - (var childInline, int childSkip) = GetWpfInlineFromMarkdownInline(currentInline); - if (childInline != null) - span.Inlines.Add(childInline); - - skip += childSkip; - - current = currentInline.NextSibling; - } - - throw new Exception("End tag not detected"); - } - - /// Inline, skip - private static (System.Windows.Documents.Inline?, int) GetWpfInlineFromMarkdownInline(Markdig.Syntax.Inlines.Inline? inline) + private static System.Windows.Documents.Inline? GetWpfInlineFromMarkdownInline(Markdig.Syntax.Inlines.Inline? inline) { if (inline is LiteralInline literalInline) { - return (new Run(literalInline.ToString()), 0); + return new Run(literalInline.ToString()); + } + else if (inline is EmphasisInline emphasisInline) + { + switch (emphasisInline.DelimiterChar) + { + case '*': + case '_': + { + if (emphasisInline.DelimiterCount == 1) // 1 = italic + { + var childInline = new Italic(GetWpfInlineFromMarkdownInline(emphasisInline.FirstChild)); + return childInline; + } + else // 2 = bold + { + var childInline = new Bold(GetWpfInlineFromMarkdownInline(emphasisInline.FirstChild)); + return childInline; + } + } + + case '=': // marked + { + var childInline = new Span(GetWpfInlineFromMarkdownInline(emphasisInline.FirstChild)); + childInline.Background = new SolidColorBrush(Color.FromArgb(50, 255, 255, 255)); // TODO: better colour? + return childInline; + } + } + } else if (inline is LinkInline linkInline) { @@ -77,38 +75,23 @@ namespace Bloxstrap.UI.Elements.Controls return GetWpfInlineFromMarkdownInline(textInline); } - (var childInline, int skip) = GetWpfInlineFromMarkdownInline(textInline); + var childInline = GetWpfInlineFromMarkdownInline(textInline); - return (new Hyperlink(childInline) + return new Hyperlink(childInline) { Command = GlobalViewModel.OpenWebpageCommand, CommandParameter = url - }, skip); - } - else if (inline is HtmlInline htmlInline) - { - string? tag = htmlInline.Tag; // TODO: parse tag - var nextInline = htmlInline.NextSibling; - - if (tag == "") - { - (var span, int skip) = GetInlineUntilEndTagDetected(nextInline, "highlight"); - span.Background = new SolidColorBrush(Color.FromArgb(50,255,255,255)); - return (span, skip); - } + }; } - return (null, 0); + return null; } - /// Skip - private int AddMarkdownInline(Markdig.Syntax.Inlines.Inline? inline) + private void AddMarkdownInline(Markdig.Syntax.Inlines.Inline? inline) { - (var wpfInline, int skip) = GetWpfInlineFromMarkdownInline(inline); + var wpfInline = GetWpfInlineFromMarkdownInline(inline); if (wpfInline != null) Inlines.Add(wpfInline); - - return skip; } private static void OnTextMarkdownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) @@ -119,7 +102,7 @@ namespace Bloxstrap.UI.Elements.Controls if (dependencyPropertyChangedEventArgs.NewValue is not string rawDocument) return; - MarkdownDocument document = Markdown.Parse(rawDocument); + MarkdownDocument document = Markdown.Parse(rawDocument, _markdownPipeline); markdownTextBlock.Inlines.Clear(); @@ -130,9 +113,15 @@ namespace Bloxstrap.UI.Elements.Controls { var inline = paragraphBlock.Inline.ElementAt(i); - int skip = markdownTextBlock.AddMarkdownInline(inline); - i += skip; + markdownTextBlock.AddMarkdownInline(inline); } } + + static MarkdownTextBlock() + { + _markdownPipeline = new MarkdownPipelineBuilder() + .UseEmphasisExtras(Markdig.Extensions.EmphasisExtras.EmphasisExtraOptions.Marked) // enable '==' support + .Build(); + } } } From a97089491a12735ddf6ec4562c4c97b925e17d28 Mon Sep 17 00:00:00 2001 From: bluepilledgreat <97983689+bluepilledgreat@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:07:42 +0000 Subject: [PATCH 4/4] remove old comment --- Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs b/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs index f571ce2..48c732e 100644 --- a/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs +++ b/Bloxstrap/UI/Elements/Controls/MarkdownTextBlock.cs @@ -11,8 +11,7 @@ using System.Windows.Media; namespace Bloxstrap.UI.Elements.Controls { /// - /// TextBlock with markdown support.
- /// Only supports text and urls. + /// TextBlock with markdown support. ///
[ContentProperty("MarkdownText")] [Localizability(LocalizationCategory.Text)]