Class Sealed
public sealed class CodeGroupRenderer : HtmlObjectRenderer<Moka.Docs.Parsing.Markdown.CodeGroupBlock>

Namespace: Moka.Docs.Parsing.Markdown

Renders a code-group block as a tabbed interface.

Inheritance

Inherits from: HtmlObjectRenderer<Moka.Docs.Parsing.Markdown.CodeGroupBlock>

Methods

Type Relationships
classDiagram
                    style CodeGroupRenderer fill:#f9f,stroke:#333,stroke-width:2px
                    CodeGroupRenderer --|> CodeGroupBlock~ : inherits
                
View Source
/// <summary>
///     Renders a code-group block as a tabbed interface.
/// </summary>
public sealed class CodeGroupRenderer : HtmlObjectRenderer<CodeGroupBlock>
{
    /// <inheritdoc/>
    protected override void Write(HtmlRenderer renderer, CodeGroupBlock block)
    {
        string groupId = block.GroupId;
        // Collect code blocks and their titles
        var codeBlocks = new List<(string Title, FencedCodeBlock Code)>();
        foreach (Block child in block)
        {
            if (child is FencedCodeBlock fenced)
            {
                // Try to get title from info string: ```csharp title="C#"
                string info = fenced.Info ?? "";
                string title = ExtractTitle(fenced.Arguments) ?? LanguageDisplayName(info);
                codeBlocks.Add((title, fenced));
            }
        }

        if (codeBlocks.Count == 0)
        {
            // Fallback: just render children normally
            renderer.WriteChildren(block);
            return;
        }

        renderer.EnsureLine();
        renderer.Write($"<div class=\"tabs component-code-group\" data-tab-group=\"{groupId}\">");
        renderer.WriteLine();
        // Tab headers
        renderer.Write("<div class=\"tab-headers\" role=\"tablist\">");
        renderer.WriteLine();
        for (int i = 0; i < codeBlocks.Count; i++)
        {
            string active = i == 0 ? " active" : "";
            string selected = i == 0 ? "true" : "false";
            renderer.Write($"<button class=\"tab-header{active}\" role=\"tab\" aria-selected=\"{selected}\" data-tab-index=\"{i}\">{codeBlocks[i].Title}</button>");
            renderer.WriteLine();
        }

        renderer.Write("</div>");
        renderer.WriteLine();
        // Tab content panels — render each code block in its own panel
        for (int i = 0; i < codeBlocks.Count; i++)
        {
            string activeClass = i == 0 ? " active" : "";
            string hidden = i == 0 ? "" : " hidden";
            renderer.Write($"<div class=\"tab-content{activeClass}\" role=\"tabpanel\"{hidden}>");
            renderer.WriteLine();
            // Render the fenced code block using the default code renderer
            renderer.Write(codeBlocks[i].Code);
            renderer.Write("</div>");
            renderer.WriteLine();
        }

        renderer.Write("</div>");
        renderer.WriteLine();
    }

    /// <summary>
    ///     Extracts title="..." from the arguments string of a fenced code block.
    /// </summary>
    private static string? ExtractTitle(string? arguments)
    {
        if (string.IsNullOrWhiteSpace(arguments))
        {
            return null;
        }

        const string prefix = "title=";
        int idx = arguments.IndexOf(prefix, StringComparison.OrdinalIgnoreCase);
        if (idx < 0)
        {
            return null;
        }

        string rest = arguments[(idx + prefix.Length)..];
        if (rest.Length < 2)
        {
            return null;
        }

        char quote = rest[0];
        if (quote != '"' && quote != '\'')
        {
            return null;
        }

        int endQuote = rest.IndexOf(quote, 1);
        if (endQuote < 0)
        {
            return null;
        }

        return rest[1..endQuote];
    }

    /// <summary>
    ///     Returns a display-friendly language name.
    /// </summary>
    private static string LanguageDisplayName(string? lang)
    {
        if (string.IsNullOrWhiteSpace(lang))
        {
            return "Code";
        }

        return lang.ToLowerInvariant() switch
        {
            "csharp" or "cs" => "C#",
            "fsharp" or "fs" => "F#",
            "javascript" or "js" => "JavaScript",
            "typescript" or "ts" => "TypeScript",
            "python" or "py" => "Python",
            "bash" or "sh" or "shell" => "Shell",
            "xml" => "XML",
            "json" => "JSON",
            "yaml" or "yml" => "YAML",
            "html" => "HTML",
            "css" => "CSS",
            "sql" => "SQL",
            "powershell" or "ps1" => "PowerShell",
            "dockerfile" => "Dockerfile",
            "markdown" or "md" => "Markdown",
            _ => lang
        };
    }
}
Was this page helpful?