|
11 | 11 | using Markdig.Extensions.CustomContainers; |
12 | 12 | using ServiceStack.IO; |
13 | 13 | using ServiceStack.Text; |
| 14 | +using Markdig.Extensions.Diagrams; |
14 | 15 |
|
15 | 16 | namespace MyApp; |
16 | 17 |
|
@@ -112,6 +113,7 @@ public virtual MarkdownPipeline CreatePipeline() |
112 | 113 | .UseAdvancedExtensions() |
113 | 114 | .UseAutoLinkHeadings() |
114 | 115 | .UseHeadingsMap() |
| 116 | + .UseCustomDiagramExtension() |
115 | 117 | .UseCustomContainers(MarkdigConfig.Instance.ConfigureContainers); |
116 | 118 | MarkdigConfig.Instance.ConfigurePipeline?.Invoke(builder); |
117 | 119 |
|
@@ -374,6 +376,45 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) |
374 | 376 | } |
375 | 377 | } |
376 | 378 |
|
| 379 | +public class MermaidBlockRenderer(CodeBlockRenderer? underlyingRenderer = null) : HtmlObjectRenderer<CodeBlock> |
| 380 | +{ |
| 381 | + private readonly CodeBlockRenderer underlyingRenderer = underlyingRenderer ?? new CodeBlockRenderer(); |
| 382 | + protected override void Write(HtmlRenderer renderer, CodeBlock obj) |
| 383 | + { |
| 384 | + if (obj is not FencedCodeBlock fencedCodeBlock || obj.Parser is not FencedCodeBlockParser parser) |
| 385 | + { |
| 386 | + underlyingRenderer.Write(renderer, obj); |
| 387 | + return; |
| 388 | + } |
| 389 | + |
| 390 | + var attributes = obj.TryGetAttributes() ?? new HtmlAttributes(); |
| 391 | + attributes.AddClass("mermaid not-prose"); |
| 392 | + var txt = GetContent(obj); |
| 393 | + renderer |
| 394 | + .Write("<pre") |
| 395 | + .WriteAttributes(attributes) |
| 396 | + .Write(">") |
| 397 | + .Write(txt) |
| 398 | + .WriteLine("</pre>"); |
| 399 | + } |
| 400 | + private static string GetContent(LeafBlock obj) |
| 401 | + { |
| 402 | + var code = new StringBuilder(); |
| 403 | + foreach (var line in obj.Lines.Lines) |
| 404 | + { |
| 405 | + var slice = line.Slice; |
| 406 | + if (slice.Text == null) |
| 407 | + continue; |
| 408 | + |
| 409 | + var lineText = slice.Text.Substring(slice.Start, slice.Length); |
| 410 | + code.AppendLine(); |
| 411 | + code.Append(lineText); |
| 412 | + } |
| 413 | + |
| 414 | + return code.ToString(); |
| 415 | + } |
| 416 | +} |
| 417 | + |
377 | 418 | public class FilesCodeBlockRenderer(CodeBlockRenderer? underlyingRenderer = null) : HtmlObjectRenderer<CodeBlock> |
378 | 419 | { |
379 | 420 | private readonly CodeBlockRenderer underlyingRenderer = underlyingRenderer ?? new CodeBlockRenderer(); |
@@ -614,55 +655,6 @@ protected override void Write(HtmlRenderer renderer, CustomContainer obj) |
614 | 655 | } |
615 | 656 | } |
616 | 657 |
|
617 | | -public class MermaidContainerRenderer : HtmlObjectRenderer<CustomContainer> |
618 | | -{ |
619 | | - protected override void Write(HtmlRenderer renderer, CustomContainer obj) |
620 | | - { |
621 | | - renderer.EnsureLine(); |
622 | | - if (renderer.EnableHtmlForBlock) |
623 | | - { |
624 | | - renderer.Write("<div class=\"mermaid-diagram\">"); |
625 | | - renderer.WriteLine("<pre class=\"mermaid\">"); |
626 | | - } |
627 | | - |
628 | | - // Write the Mermaid diagram content |
629 | | - if (obj.FirstOrDefault() is LeafBlock leafBlock) |
630 | | - { |
631 | | - // There has to be an official API to resolve the original text from a renderer? |
632 | | - string? FindOriginalText(ContainerBlock? block) |
633 | | - { |
634 | | - if (block != null) |
635 | | - { |
636 | | - if (block.FirstOrDefault(x => x is LeafBlock { Lines.Count: > 0 }) is LeafBlock first) |
637 | | - return first.Lines.Lines[0].Slice.Text; |
638 | | - return FindOriginalText(block.Parent); |
639 | | - } |
640 | | - |
641 | | - return null; |
642 | | - } |
643 | | - |
644 | | - var originalSource = leafBlock.Lines.Count > 0 |
645 | | - ? leafBlock.Lines.Lines[0].Slice.Text |
646 | | - : FindOriginalText(obj.Parent); |
647 | | - if (originalSource == null) |
648 | | - { |
649 | | - HostContext.Resolve<ILogger<PreContainerRenderer>>().LogError("Could not find original Text"); |
650 | | - renderer.WriteLine($"Could not find original Text"); |
651 | | - } |
652 | | - else |
653 | | - { |
654 | | - renderer.WriteEscape(originalSource.AsSpan().Slice(leafBlock.Span.Start, leafBlock.Span.Length)); |
655 | | - } |
656 | | - } |
657 | | - |
658 | | - if (renderer.EnableHtmlForBlock) |
659 | | - { |
660 | | - renderer.WriteLine("</pre>"); |
661 | | - renderer.WriteLine("</div>"); |
662 | | - } |
663 | | - } |
664 | | -} |
665 | | - |
666 | 658 | public class IncludeContainerInlineRenderer : HtmlObjectRenderer<CustomContainerInline> |
667 | 659 | { |
668 | 660 | protected override void Write(HtmlRenderer renderer, CustomContainerInline obj) |
@@ -812,7 +804,8 @@ public void AddBuiltInContainers(string[]? exclude = null) |
812 | 804 | { |
813 | 805 | CodeBlocks = new() |
814 | 806 | { |
815 | | - ["files"] = origRenderer => new FilesCodeBlockRenderer(origRenderer) |
| 807 | + ["files"] = origRenderer => new FilesCodeBlockRenderer(origRenderer), |
| 808 | + ["mermaid"] = origRenderer => new MermaidBlockRenderer(origRenderer), |
816 | 809 | }; |
817 | 810 | BlockContainers = new() |
818 | 811 | { |
@@ -846,7 +839,6 @@ public void AddBuiltInContainers(string[]? exclude = null) |
846 | 839 | }, |
847 | 840 | ["pre"] = new PreContainerRenderer(), |
848 | 841 | ["youtube"] = new YouTubeContainerRenderer(), |
849 | | - ["mermaid"] = new MermaidContainerRenderer(), |
850 | 842 | }; |
851 | 843 | InlineContainers = new() |
852 | 844 | { |
@@ -915,6 +907,24 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) |
915 | 907 | } |
916 | 908 | } |
917 | 909 |
|
| 910 | +public class CustomDiagramExtension : IMarkdownExtension |
| 911 | +{ |
| 912 | + public void Setup(MarkdownPipelineBuilder pipeline) |
| 913 | + { |
| 914 | + } |
| 915 | + |
| 916 | + public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) |
| 917 | + { |
| 918 | + if (renderer is HtmlRenderer htmlRenderer) |
| 919 | + { |
| 920 | + var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>()!; |
| 921 | + // TODO: Add other well known diagram languages |
| 922 | + //codeRenderer.BlocksAsDiv.Add("mermaid"); |
| 923 | + codeRenderer.BlocksAsDiv.Add("nomnoml"); |
| 924 | + } |
| 925 | + } |
| 926 | +} |
| 927 | + |
918 | 928 | public class HeadingsMapExtension : IMarkdownExtension |
919 | 929 | { |
920 | 930 | public void Setup(MarkdownPipelineBuilder pipeline) |
@@ -1004,6 +1014,12 @@ public static MarkdownPipelineBuilder UseCustomContainers(this MarkdownPipelineB |
1004 | 1014 | pipeline.Extensions.AddIfNotAlready(ext); |
1005 | 1015 | return pipeline; |
1006 | 1016 | } |
| 1017 | + |
| 1018 | + public static MarkdownPipelineBuilder UseCustomDiagramExtension(this MarkdownPipelineBuilder pipeline) |
| 1019 | + { |
| 1020 | + pipeline.Extensions.ReplaceOrAdd<DiagramExtension>(new CustomDiagramExtension()); |
| 1021 | + return pipeline; |
| 1022 | + } |
1007 | 1023 | } |
1008 | 1024 |
|
1009 | 1025 | public class DocumentMap |
|
0 commit comments