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 @@ + 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..160d346 100644 --- a/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs +++ b/RTXLauncher.Avalonia/ViewModels/AboutViewModel.cs @@ -95,10 +95,12 @@ 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 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 +203,25 @@ 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."); + // 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); 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 @@ - + + +