oliverbooth.dev/OliverBooth.Extensions.Markdig/Markdown/Callout/CalloutRenderer.cs

120 lines
3.7 KiB
C#

using HtmlAgilityPack;
using Humanizer;
using Markdig;
using Markdig.Renderers;
using Markdig.Renderers.Html;
namespace OliverBooth.Extensions.Markdig.Markdown.Callout;
/// <summary>
/// Represents an HTML renderer which renders a <see cref="CalloutBlock" />.
/// </summary>
internal sealed class CalloutRenderer : HtmlObjectRenderer<CalloutBlock>
{
private readonly MarkdownPipeline _pipeline;
private static readonly Dictionary<string, string> CalloutTypes = new()
{
["NOTE"] = "pencil",
["ABSTRACT"] = "clipboard-list",
["INFO"] = "info",
["TODO"] = "circle-check",
["TIP"] = "flame",
["IMPORTANT"] = "flame",
["SUCCESS"] = "check",
["QUESTION"] = "circle-help",
["WARNING"] = "triangle-alert",
["FAILURE"] = "x",
["DANGER"] = "zap",
["BUG"] = "bug",
["EXAMPLE"] = "list",
["CITE"] = "quote",
["UPDATE"] = "calendar-check",
};
public CalloutRenderer(MarkdownPipeline pipeline)
{
_pipeline = pipeline;
}
/// <inheritdoc />
protected override void Write(HtmlRenderer renderer, CalloutBlock block)
{
renderer.EnsureLine();
if (renderer.EnableHtmlForBlock)
{
RenderAsHtml(renderer, block, _pipeline);
}
else
{
RenderAsText(renderer, block);
}
renderer.EnsureLine();
}
private static void RenderAsHtml(HtmlRenderer renderer, CalloutBlock block, MarkdownPipeline pipeline)
{
string title = block.Title.Text;
ReadOnlySpan<char> type = block.Type.AsSpan();
Span<char> upperType = stackalloc char[type.Length];
type.ToUpperInvariant(upperType);
if (!CalloutTypes.TryGetValue(upperType.ToString(), out string? lucideClass))
{
lucideClass = "pencil";
}
var typeString = type.ToString().ToLowerInvariant();
renderer.Write($"<div class=\"callout\" data-callout=\"{typeString}\"");
if (block.Foldable)
{
renderer.Write(" data-callout-fold=\"true\"");
}
renderer.Write('>');
renderer.Write("<div class=\"callout-title\"><i data-lucide=\"");
renderer.Write(lucideClass);
renderer.Write("\"></i> ");
string calloutTitle = title.Length == 0 ? typeString.Humanize(LetterCasing.Sentence) : title;
WriteTitle(renderer, pipeline, calloutTitle);
if (block.Foldable)
{
renderer.Write("<span class=\"callout-fold\"><i data-lucide=\"chevron-down\"></i></span>");
}
renderer.WriteLine("</div>");
renderer.Write("<div class=\"callout-content\">");
renderer.WriteChildren(block);
renderer.WriteLine("</div>");
renderer.WriteLine("</div>");
renderer.EnsureLine();
}
private static void WriteTitle(TextRendererBase renderer, MarkdownPipeline pipeline, string calloutTitle)
{
string html = global::Markdig.Markdown.ToHtml(calloutTitle, pipeline);
var document = new HtmlDocument();
document.LoadHtml(html);
if (document.DocumentNode.FirstChild is { Name: "p" } child)
{
// ugly hack to remove <p> tag generated by Markdig
document.DocumentNode.InnerHtml = child.InnerHtml;
}
document.Save(renderer.Writer);
}
private static void RenderAsText(HtmlRenderer renderer, CalloutBlock block)
{
string title = block.Title.Text;
ReadOnlySpan<char> type = block.Type.AsSpan();
renderer.WriteLine(title.Length == 0 ? type.ToString().ToUpperInvariant() : title.ToUpperInvariant());
renderer.WriteChildren(block);
renderer.EnsureLine();
}
}