From 68fcc01c59b8bd785990889da906b0d2881e728e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 31 Aug 2025 00:03:13 +0000
Subject: [PATCH 1/4] Initial plan
From a2ca103b0a23e5ae7adc96a32addf8494e52965b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 31 Aug 2025 00:07:41 +0000
Subject: [PATCH 2/4] Initial plan: Implement markdown parsing for Avalonia
release notes
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
---
RTXLauncher.Avalonia/RTXLauncher.Avalonia.csproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/RTXLauncher.Avalonia/RTXLauncher.Avalonia.csproj b/RTXLauncher.Avalonia/RTXLauncher.Avalonia.csproj
index ae57df6..314787a 100644
--- a/RTXLauncher.Avalonia/RTXLauncher.Avalonia.csproj
+++ b/RTXLauncher.Avalonia/RTXLauncher.Avalonia.csproj
@@ -46,6 +46,7 @@
+
From 346ab526a9783c0277b715fee8940bb6d49ed0ac Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 31 Aug 2025 00:11:31 +0000
Subject: [PATCH 3/4] Implement markdown formatting for Avalonia release notes
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
---
.../Utilities/MarkdownFormatter.cs | 202 ++++++++++++++++++
.../ViewModels/AboutViewModel.cs | 29 ++-
RTXLauncher.Avalonia/Views/AboutView.axaml | 12 +-
3 files changed, 228 insertions(+), 15 deletions(-)
create mode 100644 RTXLauncher.Avalonia/Utilities/MarkdownFormatter.cs
diff --git a/RTXLauncher.Avalonia/Utilities/MarkdownFormatter.cs b/RTXLauncher.Avalonia/Utilities/MarkdownFormatter.cs
new file mode 100644
index 0000000..e8659b0
--- /dev/null
+++ b/RTXLauncher.Avalonia/Utilities/MarkdownFormatter.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace RTXLauncher.Avalonia.Utilities
+{
+ public class MarkdownFormatter
+ {
+ // Dictionary of prefixes and their color styles (following WinForms color scheme)
+ private static readonly Dictionary PrefixColors = new()
+ {
+ { "Fixed:", "#2F4F4F" }, // DarkSlateGray
+ { "Fixes:", "#2F4F4F" },
+ { "Bug fix:", "#2F4F4F" },
+ { "Added:", "#006400" }, // DarkGreen
+ { "New:", "#006400" },
+ { "What's New:", "#006400" },
+ { "Feature:", "#006400" },
+ { "Removed:", "#8B0000" }, // DarkRed
+ { "Changed:", "#FF8C00" }, // DarkOrange
+ { "What's Changed:", "#FF8C00" },
+ { "Updated:", "#FF8C00" },
+ { "Improved:", "#FF8C00" },
+ { "Technical Details:", "#8B008B" }, // DarkMagenta
+ { "Known Issues:", "#FF0000" }, // Red
+ { "Known Issue:", "#FF0000" },
+ { "Warning:", "#FF0000" },
+ { "Important:", "#FF0000" },
+ { "Note:", "#FF0000" },
+ { "New Contributors:", "#FF00FF" }, // Fuchsia
+ };
+
+ public static string FormatReleaseNotes(string newVersion, string currentVersion, string releaseNotes, bool isUpdate = true)
+ {
+ var markdown = new StringBuilder();
+
+ // Format the header section
+ FormatHeaderSection(markdown, newVersion, currentVersion, isUpdate);
+
+ // Handle missing release notes
+ if (string.IsNullOrWhiteSpace(releaseNotes))
+ {
+ markdown.AppendLine("*No release notes available for this version.*");
+ return markdown.ToString();
+ }
+
+ // Process each line
+ string[] lines = releaseNotes.Replace("\r\n", "\n").Split('\n');
+ bool inCodeBlock = false;
+
+ foreach (string line in lines)
+ {
+ string trimmedLine = line.TrimStart();
+
+ // Skip empty lines (preserve spacing)
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ markdown.AppendLine();
+ continue;
+ }
+
+ // Handle code blocks
+ if (trimmedLine.StartsWith("```"))
+ {
+ inCodeBlock = !inCodeBlock;
+ markdown.AppendLine(line);
+ continue;
+ }
+
+ if (inCodeBlock)
+ {
+ markdown.AppendLine(line);
+ continue;
+ }
+
+ // Parse line components
+ (string content, int headerLevel, bool isBullet, string bulletPrefix) = ParseLineFormat(trimmedLine);
+
+ // Find special prefix if any
+ string matchedPrefix = PrefixColors.Keys.FirstOrDefault(prefix => content.StartsWith(prefix)) ?? string.Empty;
+
+ // Format the line based on its components
+ if (headerLevel > 0)
+ {
+ // Headers - add the appropriate markdown header syntax
+ string headerPrefix = new string('#', headerLevel);
+ if (!string.IsNullOrEmpty(matchedPrefix))
+ {
+ markdown.AppendLine($"{headerPrefix} {matchedPrefix}{content.Substring(matchedPrefix.Length)}");
+ }
+ else
+ {
+ markdown.AppendLine($"{headerPrefix} {content}");
+ }
+ }
+ else if (isBullet)
+ {
+ // Bullet points
+ if (!string.IsNullOrEmpty(matchedPrefix))
+ {
+ markdown.AppendLine($"- {matchedPrefix}{ProcessInlineMarkdown(content.Substring(matchedPrefix.Length))}");
+ }
+ else
+ {
+ markdown.AppendLine($"- {ProcessInlineMarkdown(content)}");
+ }
+ }
+ else if (!string.IsNullOrEmpty(matchedPrefix))
+ {
+ // Prefixed lines
+ markdown.AppendLine($"{matchedPrefix}{ProcessInlineMarkdown(content.Substring(matchedPrefix.Length))}");
+ }
+ else
+ {
+ // Regular text
+ markdown.AppendLine(ProcessInlineMarkdown(line));
+ }
+ }
+
+ return markdown.ToString();
+ }
+
+ private static void FormatHeaderSection(StringBuilder markdown, string newVersion, string currentVersion, bool isUpdate)
+ {
+ if (isUpdate)
+ {
+ markdown.AppendLine("## Update Available!");
+ markdown.AppendLine();
+ markdown.AppendLine($"**New version:** {newVersion}");
+ markdown.AppendLine($"Current version: {currentVersion}");
+ markdown.AppendLine();
+ }
+ else
+ {
+ markdown.AppendLine($"## You're up to date! ({currentVersion})");
+ markdown.AppendLine();
+ }
+ }
+
+ private static (string content, int headerLevel, bool isBullet, string bulletPrefix) ParseLineFormat(string line)
+ {
+ int headerLevel = 0;
+ bool isBullet = false;
+ string bulletPrefix = "";
+ string content = line;
+
+ // Check for headers
+ if (line.StartsWith("### "))
+ {
+ headerLevel = 3;
+ content = line.Substring(4);
+ }
+ else if (line.StartsWith("## "))
+ {
+ headerLevel = 2;
+ content = line.Substring(3);
+ }
+ else if (line.StartsWith("# "))
+ {
+ headerLevel = 1;
+ content = line.Substring(2);
+ }
+
+ // Check for bullets
+ if (line.StartsWith("- ") || line.StartsWith("* "))
+ {
+ isBullet = true;
+ bulletPrefix = line.StartsWith("- ") ? "- " : "* ";
+ content = line.Substring(2);
+ }
+
+ return (content, headerLevel, isBullet, bulletPrefix);
+ }
+
+ private static string ProcessInlineMarkdown(string text)
+ {
+ // Handle the special "Full Changelog" link pattern
+ if (text.Contains("**Full Changelog**:") && text.Contains("compare/"))
+ {
+ // Extract the version comparison (e.g., v1.0.4...v1.0.5)
+ Regex compareRegex = new Regex(@"compare/([^/\s\)]+)");
+ Match compareMatch = compareRegex.Match(text);
+
+ // Extract the URL
+ Regex urlRegex = new Regex(@"(https?://[^\s\)]+)");
+ Match urlMatch = urlRegex.Match(text);
+
+ if (urlMatch.Success)
+ {
+ string url = urlMatch.Groups[1].Value;
+ string versionCompare = compareMatch.Success ? compareMatch.Groups[1].Value : "versions";
+ return $"**Full Changelog**: [{versionCompare}]({url})";
+ }
+ }
+
+ // The markdown library should handle bold (**text**), italic (*text*), and links [text](url) automatically
+ return text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs b/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs
index 92f1be8..fd6479f 100644
--- a/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs
+++ b/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs
@@ -96,9 +96,14 @@ private async Task CheckForUpdates()
if (UpdateAvailable && result.LatestUpdate != null)
{
- ReleaseNotes = $"Update available: {result.LatestUpdate.Version}\n\n" +
- $"Current version: {CurrentVersion}\n\n" +
- (result.LatestUpdate.Release?.Body ?? "No release notes available.");
+ var headerContent = $"Update available: {result.LatestUpdate.Version}\n\n" +
+ $"Current version: {CurrentVersion}\n\n";
+
+ var releaseBody = result.LatestUpdate.Release?.Body ?? "No release notes available.";
+
+ // Use the markdown formatter to process the release notes
+ ReleaseNotes = Utilities.MarkdownFormatter.FormatReleaseNotes(
+ result.LatestUpdate.Version, CurrentVersion, releaseBody, true);
}
else
{
@@ -201,24 +206,30 @@ private void UpdateReleaseNotes(UpdateSource source)
{
if (source.IsStaging)
{
- ReleaseNotes = $"Development Build: {source.Version}\n\n" +
+ var markdown = "**Development Build:** " + source.Version + "\n\n" +
"This is the latest development build from the master branch.\n\n" +
- "Warning: This version may contain experimental features and bugs.";
+ "**Warning:** This version may contain experimental features and bugs.";
+ ReleaseNotes = markdown;
}
else if (source.Release != null)
{
var isNewer = Core.Utilities.VersionUtility.CompareVersions(source.Version, CurrentVersion) > 0;
var status = isNewer ? "đ Newer version available" : "âšī¸ Same or older version";
- ReleaseNotes = $"{source.Name}\n{status}\n\n" +
- $"Published: {source.Release.PublishedAt:yyyy-MM-dd}\n\n" +
- (source.Release.Body ?? "No release notes available.");
+ var headerContent = $"{source.Name}\n{status}\n\n" +
+ $"Published: {source.Release.PublishedAt:yyyy-MM-dd}\n\n";
+
+ var releaseBody = source.Release.Body ?? "No release notes available.";
+
+ // Use the markdown formatter to process the release notes
+ ReleaseNotes = Utilities.MarkdownFormatter.FormatReleaseNotes(
+ source.Version, CurrentVersion, releaseBody, isNewer);
UpdateAvailable = isNewer;
}
else
{
- ReleaseNotes = $"{source.Name}\n\nNo additional information available.";
+ ReleaseNotes = $"**{source.Name}**\n\nNo additional information available.";
}
}
}
\ No newline at end of file
diff --git a/RTXLauncher.Avalonia/Views/AboutView.axaml b/RTXLauncher.Avalonia/Views/AboutView.axaml
index ac69838..d42232c 100644
--- a/RTXLauncher.Avalonia/Views/AboutView.axaml
+++ b/RTXLauncher.Avalonia/Views/AboutView.axaml
@@ -1,6 +1,7 @@
@@ -19,12 +20,11 @@
-
+
+
+
From 7f4a84da1fd3e70d023189b15636cbc277c6c42c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 31 Aug 2025 00:14:13 +0000
Subject: [PATCH 4/4] Complete markdown formatting implementation with
comprehensive testing
Co-authored-by: Xenthio <28588188+Xenthio@users.noreply.github.com>
---
RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs b/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs
index fd6479f..160d346 100644
--- a/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs
+++ b/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs
@@ -95,10 +95,7 @@ private async Task CheckForUpdates()
}
if (UpdateAvailable && result.LatestUpdate != null)
- {
- var headerContent = $"Update available: {result.LatestUpdate.Version}\n\n" +
- $"Current version: {CurrentVersion}\n\n";
-
+ {
var releaseBody = result.LatestUpdate.Release?.Body ?? "No release notes available.";
// Use the markdown formatter to process the release notes
@@ -214,14 +211,9 @@ private void UpdateReleaseNotes(UpdateSource source)
else if (source.Release != null)
{
var isNewer = Core.Utilities.VersionUtility.CompareVersions(source.Version, CurrentVersion) > 0;
- var status = isNewer ? "đ Newer version available" : "âšī¸ Same or older version";
-
- var headerContent = $"{source.Name}\n{status}\n\n" +
- $"Published: {source.Release.PublishedAt:yyyy-MM-dd}\n\n";
-
- var releaseBody = source.Release.Body ?? "No release notes available.";
// Use the markdown formatter to process the release notes
+ var releaseBody = source.Release.Body ?? "No release notes available.";
ReleaseNotes = Utilities.MarkdownFormatter.FormatReleaseNotes(
source.Version, CurrentVersion, releaseBody, isNewer);