Summary Update.

This commit is contained in:
2026-06-06 23:38:50 -04:00
parent dd75ca4b06
commit 2916d17868
30 changed files with 1231 additions and 21 deletions

View File

@@ -12,6 +12,12 @@ public static class MarkdownHelper
private static readonly Color MentionBg = Color.FromArgb("#2D2F5C");
private static readonly Color SpoilerBg = Color.FromArgb("#1F1F23");
/// <summary>
/// The entry point. Returns either a single Label (simple inline text) or a
/// VerticalStackLayout (anything with paragraphs, code blocks, or headers).
/// First pass extracts fenced code blocks (verbatim, can span multiple lines), then
/// AppendTextSegment handles per-line headers and the inline parser.
/// </summary>
public static View Render(string markdown, double fontSize = 14)
{
if (string.IsNullOrEmpty(markdown))
@@ -37,6 +43,11 @@ public static class MarkdownHelper
return stack.Children.Count == 1 ? (View)stack.Children[0] : stack;
}
/// <summary>
/// Splits a non-code segment by newline and emits the right view per line. Headers/subtext
/// get their own labels; consecutive normal lines accumulate into a paragraph buffer so
/// they wrap naturally as one paragraph.
/// </summary>
private static void AppendTextSegment(VerticalStackLayout stack, string segment, double fontSize)
{
var paragraphBuffer = new StringBuilder();
@@ -94,6 +105,10 @@ public static class MarkdownHelper
FlushParagraph();
}
/// <summary>
/// Builds the dark-pane code block. If a language is specified, delegates token coloring
/// to SyntaxHighlighter and prepends a small green language label (Discord-style).
/// </summary>
private static View CreateCodeBlock(string language, string code)
{
var label = new Label
@@ -141,6 +156,7 @@ public static class MarkdownHelper
};
}
/// <summary>Bold, larger Label for # / ## / ### lines. Inline markdown still works inside (e.g. `# Hello **world**`).</summary>
private static Label CreateHeaderLabel(string text, double size)
{
var label = new Label
@@ -162,6 +178,7 @@ public static class MarkdownHelper
return label;
}
/// <summary>Smaller, grey Label for "-#" lines (Discord calls it subtext). Inherits inline markdown.</summary>
private static Label CreateSubtextLabel(string text, double size)
{
var label = new Label
@@ -190,6 +207,7 @@ public static class MarkdownHelper
return label;
}
/// <summary>Standard paragraph Label. Runs the inline parser to build a FormattedString of spans.</summary>
private static Label CreateInlineLabel(string text, double fontSize)
{
var label = new Label { FontSize = fontSize, LineBreakMode = LineBreakMode.WordWrap };
@@ -204,6 +222,11 @@ public static class MarkdownHelper
return label;
}
/// <summary>
/// Attaches a TapGestureRecognizer that reveals every spoiler span in the label when
/// tapped once. MAUI Spans can't fire their own gesture events, so per-spoiler reveal
/// would require splitting the line into separate labels — this is the pragmatic compromise.
/// </summary>
private static void WireSpoilerTap(Label label, List<Span> spoilerSpans)
{
if (spoilerSpans.Count == 0) return;
@@ -220,6 +243,12 @@ public static class MarkdownHelper
label.GestureRecognizers.Add(tap);
}
/// <summary>
/// Single-pass character walk. For each markdown sigil (||, @, ~~, __, **, *, `), tries
/// to find a matching closer; if found, emits a styled Span and skips past. Otherwise the
/// char accumulates into a "plain" buffer that's flushed as a plain Span when the next
/// sigil hits or the string ends. Spoiler spans are registered in spoilerSpans for reveal.
/// </summary>
private static void ParseInline(string text, IList<Span> spans, double fontSize, List<Span> spoilerSpans)
{
var plain = new StringBuilder();
@@ -365,8 +394,13 @@ public static class MarkdownHelper
Flush();
}
/// <summary>Safe one-character lookahead. Returns '\0' past end-of-string.</summary>
private static char Peek(string text, int index) => index < text.Length ? text[index] : '\0';
/// <summary>
/// Finds the next single occurrence of marker that is NOT immediately followed by
/// another marker. Used to disambiguate "*italic*" from "**bold**".
/// </summary>
private static int FindClosingSingle(string text, char marker, int start)
{
for (int i = start; i < text.Length; i++)