Files

108 lines
4.3 KiB
C#

using Markdig;
using System.Text.RegularExpressions;
namespace WebApp.Services;
/// <summary>
/// Helper class for rendering markdown to HTML.
/// </summary>
public static class MarkdownHelper
{
private static readonly MarkdownPipeline _pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
// Compiled regex patterns for stripping markdown
private static readonly Regex MarkdownLinkRegex =
new(@"\[([^\]]+)\]\([^\)]+\)", RegexOptions.Compiled);
private static readonly Regex BoldRegex =
new(@"\*\*([^\*]+)\*\*", RegexOptions.Compiled);
private static readonly Regex ItalicRegex =
new(@"\*([^\*]+)\*", RegexOptions.Compiled);
private static readonly Regex InlineCodeRegex =
new(@"`([^`]+)`", RegexOptions.Compiled);
private static readonly Regex HeaderRegex =
new(@"#+\s+", RegexOptions.Compiled);
private static readonly Regex NewlineRegex =
new(@"\n+", RegexOptions.Compiled);
private static readonly Regex HtmlTagRegex =
new(@"<[^>]+>", RegexOptions.Compiled);
/// <summary>
/// Converts markdown text to HTML.
/// </summary>
/// <param name="markdown">The markdown text to convert.</param>
/// <returns>HTML string ready for rendering.</returns>
public static string ToHtml(string? markdown)
{
if (string.IsNullOrWhiteSpace(markdown))
return string.Empty;
var html = Markdown.ToHtml(markdown, _pipeline);
// Add target="_blank" and rel="noopener noreferrer" to all links
html = Regex.Replace(html, @"<a\s+([^>]*?)href=""([^""]*?)""([^>]*?)>",
match =>
{
var beforeHref = match.Groups[1].Value;
var href = match.Groups[2].Value;
var afterHref = match.Groups[3].Value;
// Check if target is already present
if (Regex.IsMatch(beforeHref + afterHref, @"target\s*=", RegexOptions.IgnoreCase))
{
return match.Value; // Return unchanged if target already exists
}
// Add target="_blank" and rel="noopener noreferrer"
return $"<a {beforeHref}href=\"{href}\" target=\"_blank\" rel=\"noopener noreferrer\"{afterHref}>";
},
RegexOptions.IgnoreCase);
return html;
}
/// <summary>
/// Strips markdown formatting from content and returns plain text for preview.
/// </summary>
/// <param name="content">The markdown content to strip.</param>
/// <param name="maxLength">Maximum length of the preview. If 0 or negative, no truncation is performed.</param>
/// <returns>Plain text preview with markdown formatting removed.</returns>
public static string StripMarkdownPreview(string? content, int maxLength = 0)
{
if (string.IsNullOrWhiteSpace(content))
return string.Empty;
// Strip markdown formatting for preview using compiled regex
var plainText = MarkdownLinkRegex.Replace(content, "$1"); // Replace markdown links with link text
plainText = BoldRegex.Replace(plainText, "$1"); // Remove bold
plainText = ItalicRegex.Replace(plainText, "$1"); // Remove italic
plainText = InlineCodeRegex.Replace(plainText, "$1"); // Remove inline code
plainText = HeaderRegex.Replace(plainText, ""); // Remove headers
plainText = NewlineRegex.Replace(plainText, " "); // Replace newlines with spaces
plainText = HtmlTagRegex.Replace(plainText, ""); // Remove HTML tags
plainText = plainText.Trim();
// Truncate if maxLength is specified and content exceeds it
if (maxLength > 0 && plainText.Length > maxLength)
{
return plainText.Substring(0, maxLength - 3) + "...";
}
return plainText;
}
/// <summary>
/// Gets the CSS class name for a note's color based on its ID.
/// Uses modulo 3 to cycle through 3 pastel color classes.
/// </summary>
/// <param name="noteId">The note ID.</param>
/// <returns>CSS class name (e.g., "note-color-0", "note-color-1", "note-color-2"), or empty string if noteId is 0.</returns>
public static string GetNoteColorClass(int noteId)
{
if (noteId == 0) return string.Empty;
return $"note-color-{noteId % 3}";
}
}