mirror of https://github.com/abpframework/abp.git
committed by
GitHub
9 changed files with 216 additions and 0 deletions
@ -0,0 +1,34 @@ |
|||||
|
using System; |
||||
|
using Markdig; |
||||
|
using Markdig.Renderers; |
||||
|
using Markdig.Renderers.Html; |
||||
|
using Volo.Docs.Markdown.Renderers; |
||||
|
|
||||
|
namespace Volo.Docs.Markdown.Extensions |
||||
|
{ |
||||
|
public class HighlightedCodeBlockExtension : IMarkdownExtension |
||||
|
{ |
||||
|
public void Setup(MarkdownPipelineBuilder pipeline) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) |
||||
|
{ |
||||
|
if (renderer == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(renderer)); |
||||
|
} |
||||
|
|
||||
|
if (renderer is TextRendererBase<HtmlRenderer> htmlRenderer) |
||||
|
{ |
||||
|
var codeBlockRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>(); |
||||
|
if (codeBlockRenderer != null) |
||||
|
{ |
||||
|
htmlRenderer.ObjectRenderers.Remove(codeBlockRenderer); |
||||
|
} |
||||
|
|
||||
|
htmlRenderer.ObjectRenderers.AddIfNotAlready(new HighlightedCodeBlockRenderer()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Markdig; |
||||
|
|
||||
|
namespace Volo.Docs.Markdown.Extensions |
||||
|
{ |
||||
|
public static class MarkdownPipelineBuilderExtensions |
||||
|
{ |
||||
|
public static MarkdownPipelineBuilder UseHighlightedCodeBlocks(this MarkdownPipelineBuilder pipeline) |
||||
|
{ |
||||
|
pipeline.Extensions.AddIfNotAlready<HighlightedCodeBlockExtension>(); |
||||
|
return pipeline; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,150 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Text.RegularExpressions; |
||||
|
using Markdig.Parsers; |
||||
|
using Markdig.Renderers; |
||||
|
using Markdig.Renderers.Html; |
||||
|
using Markdig.Syntax; |
||||
|
|
||||
|
namespace Volo.Docs.Markdown.Renderers |
||||
|
{ |
||||
|
public class HighlightedCodeBlockRenderer : HtmlObjectRenderer<CodeBlock> |
||||
|
{ |
||||
|
private const string Pattern = @"\{([^}]+)\}"; |
||||
|
public bool OutputAttributesOnPre { get; set; } |
||||
|
|
||||
|
public HashSet<string> BlocksAsDiv { get; } |
||||
|
|
||||
|
public HighlightedCodeBlockRenderer() |
||||
|
{ |
||||
|
BlocksAsDiv = new HashSet<string>(StringComparer.OrdinalIgnoreCase); |
||||
|
} |
||||
|
|
||||
|
protected override void Write(HtmlRenderer renderer, CodeBlock obj) |
||||
|
{ |
||||
|
renderer.EnsureLine(); |
||||
|
|
||||
|
var fencedCodeBlock = obj as FencedCodeBlock; |
||||
|
if (fencedCodeBlock?.Info != null && BlocksAsDiv.Contains(fencedCodeBlock.Info)) |
||||
|
{ |
||||
|
var infoPrefix = (obj.Parser as FencedCodeBlockParser)?.InfoPrefix ?? |
||||
|
FencedCodeBlockParser.DefaultInfoPrefix; |
||||
|
|
||||
|
if (renderer.EnableHtmlForBlock) |
||||
|
{ |
||||
|
renderer.Write("<div") |
||||
|
.WriteAttributes(obj.TryGetAttributes(), |
||||
|
cls => cls.StartsWith(infoPrefix, StringComparison.Ordinal) ? cls.Substring(infoPrefix.Length) : cls) |
||||
|
.Write('>'); |
||||
|
} |
||||
|
|
||||
|
renderer.WriteLeafRawLines(obj, true, true, true); |
||||
|
|
||||
|
if (renderer.EnableHtmlForBlock) |
||||
|
{ |
||||
|
renderer.WriteLine("</div>"); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (renderer.EnableHtmlForBlock) |
||||
|
{ |
||||
|
renderer.Write("<pre "); |
||||
|
|
||||
|
if (OutputAttributesOnPre) |
||||
|
{ |
||||
|
renderer.WriteAttributes(obj); |
||||
|
} |
||||
|
|
||||
|
WriteHighlightedCodeLines(renderer, fencedCodeBlock); |
||||
|
|
||||
|
if (!OutputAttributesOnPre) |
||||
|
{ |
||||
|
renderer.WriteAttributes(obj); |
||||
|
} |
||||
|
|
||||
|
renderer.Write('>'); |
||||
|
} |
||||
|
|
||||
|
renderer.WriteLeafRawLines(obj, true, true); |
||||
|
|
||||
|
if (renderer.EnableHtmlForBlock) |
||||
|
{ |
||||
|
renderer.WriteLine("</code></pre>"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
renderer.EnsureLine(); |
||||
|
} |
||||
|
|
||||
|
private void WriteHighlightedCodeLines(HtmlRenderer renderer, FencedCodeBlock fencedCodeBlock) |
||||
|
{ |
||||
|
var highlightedLines = GetHighlightedLines(fencedCodeBlock); |
||||
|
if (highlightedLines.Any()) |
||||
|
{ |
||||
|
renderer.WriteAttributes(new HtmlAttributes |
||||
|
{ |
||||
|
Classes = new List<string> {"line-numbers"} //prevents adding line-numbers for highlighted lines
|
||||
|
}); |
||||
|
|
||||
|
var lines = string.Join(",", highlightedLines); |
||||
|
renderer.Write($"data-line={lines}><code"); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
renderer.Write("><code"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private HashSet<int> GetHighlightedLines(FencedCodeBlock fencedCodeBlock) |
||||
|
{ |
||||
|
var highlightedLines = new HashSet<int>(); |
||||
|
if (string.IsNullOrWhiteSpace(fencedCodeBlock?.Arguments) || !Regex.IsMatch(pattern: Pattern, input: fencedCodeBlock.Arguments)) |
||||
|
{ |
||||
|
return highlightedLines; |
||||
|
} |
||||
|
|
||||
|
var match = Regex.Match(fencedCodeBlock.Arguments, Pattern); |
||||
|
var groups = match.Groups; |
||||
|
if (groups.Count < 2 || string.IsNullOrWhiteSpace(groups[1].Value)) |
||||
|
{ |
||||
|
return highlightedLines; |
||||
|
} |
||||
|
|
||||
|
var lines = groups[1].Value.Split(","); |
||||
|
foreach (var line in lines) |
||||
|
{ |
||||
|
if (line.Contains("-")) |
||||
|
{ |
||||
|
var numbers = line.Split("-"); |
||||
|
if (numbers.Length > 2) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (!int.TryParse(numbers[0], out var minLineNumber) || |
||||
|
!int.TryParse(numbers[1], out var maxLineNumber)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
for (var lineNumber = minLineNumber; lineNumber < maxLineNumber + 1; lineNumber++) |
||||
|
{ |
||||
|
highlightedLines.Add(lineNumber); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (int.TryParse(line, out var lineNumber)) |
||||
|
{ |
||||
|
highlightedLines.Add(lineNumber); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return highlightedLines; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
.code-toolbar .line-highlight { |
||||
|
margin-top: 1.5em !important; |
||||
|
background: rgba(233, 237, 241, 0.34) !important; |
||||
|
padding: 1px !important; } |
||||
@ -0,0 +1 @@ |
|||||
|
.code-toolbar .line-highlight{margin-top:1.5em !important;background:rgba(233,237,241,.34) !important;padding:1px !important;} |
||||
@ -0,0 +1,7 @@ |
|||||
|
.code-toolbar { |
||||
|
.line-highlight { |
||||
|
margin-top: 1.5em !important; |
||||
|
background: rgba(233, 237, 241, 0.34) !important; |
||||
|
padding: 1px !important; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue