Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" PrivateAssets="all" />
<PackageReference Include="Jint" Version="4.10.0" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Bit.BlazorUI.Markdown.Rendering;
using Bit.BlazorUI.Markdown.Syntax;

namespace Bit.BlazorUI;

/// <summary>
/// BitMarkdownViewer is a native, SEO friendly Blazor component that renders Markdown
/// to HTML entirely in C#. There is no JavaScript interop and no third-party packages.
/// </summary>
/// <remarks>
/// <para>
/// By default the component understands only the basic CommonMark core. Richer flavors
/// (GitHub tables, strikethrough, task lists, autolinks, emoji, ...) are opt-in: supply
/// a <see cref="Pipeline"/> built with the desired extensions (for example
/// <see cref="BitMarkdownPipelines.GitHub"/>).
/// </para>
/// <para>
/// Parsing produces an AST which is walked with a <see cref="Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder"/>,
/// so the output is real DOM rather than an <c>innerHTML</c> blob. Raw HTML in the source
/// is treated as text and link / image URLs are sanitized, keeping the output safe from
/// script injection by default.
/// </para>
/// </remarks>
public partial class BitMarkdownViewer : BitComponentBase
{
private DocumentNode _document = new();
private string? _parsedSource;
private BitMarkdownPipeline? _parsedWith;



/// <summary>
/// The Markdown string value to render as html elements.
/// </summary>
[Parameter] public string? Markdown { get; set; }

/// <summary>
/// The processing pipeline (flavor set). Defaults to <see cref="BitMarkdownPipeline.Basic"/>,
/// i.e. the basic CommonMark core with no extensions.
/// </summary>
[Parameter] public BitMarkdownPipeline? Pipeline { get; set; }



protected override string RootElementClass => "bit-mdv";

private BitMarkdownPipeline EffectivePipeline => Pipeline ?? BitMarkdownPipeline.Basic;

protected override void OnParametersSet()
{
var pipeline = EffectivePipeline;

// Re-parse only when the source or the pipeline reference changes.
if (_parsedSource != Markdown || ReferenceEquals(_parsedWith, pipeline) is false)
{
_document = pipeline.Parse(Markdown);
_parsedSource = Markdown;
_parsedWith = pipeline;
}

base.OnParametersSet();
}

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var renderer = EffectivePipeline.CreateRenderer();

builder.OpenElement(renderer.NextSeq(), "div");

builder.AddMultipleAttributes(renderer.NextSeq(), HtmlAttributes);
builder.AddAttribute(renderer.NextSeq(), "id", _Id);
builder.AddAttribute(renderer.NextSeq(), "style", StyleBuilder.Value);
builder.AddAttribute(renderer.NextSeq(), "class", ClassBuilder.Value);
if (Dir is not null)
{
builder.AddAttribute(renderer.NextSeq(), "dir", Dir.Value.ToString().ToLower());
}
builder.AddElementReferenceCapture(renderer.NextSeq(), v => RootElement = v);

renderer.WriteNodes(builder, _document.Children);

builder.CloseElement();
}
Comment thread
msynk marked this conversation as resolved.
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Text;
using Bit.BlazorUI.Markdown.Parsing;
using Bit.BlazorUI.Markdown.Syntax;

namespace Bit.BlazorUI.Markdown.Extensions;

/// <summary>
/// Assigns a URL-friendly <c>id</c> (GitHub-style slug) to every heading, ensuring
/// uniqueness within the document so headings can be deep-linked.
/// </summary>
public sealed class AutoIdentifierAstProcessor : AstProcessor
{
public override void Process(DocumentNode document, BitMarkdownPipeline pipeline)
{
var used = new Dictionary<string, int>();
foreach (var heading in AstHelper.Descendants(document).OfType<HeadingNode>())
{
string baseSlug = Slugify(InlineHelpers.PlainText(heading.Inlines));
if (baseSlug.Length == 0) baseSlug = "section";

string slug = baseSlug;
if (used.TryGetValue(baseSlug, out int count))
{
used[baseSlug] = ++count;
slug = $"{baseSlug}-{count}";
}
else
{
used[baseSlug] = 0;
}
heading.Id = slug;
}
}

private static string Slugify(string text)
{
var sb = new StringBuilder(text.Length);
bool lastDash = false;
foreach (char c in text.Trim().ToLowerInvariant())
{
if (char.IsLetterOrDigit(c))
{
sb.Append(c);
lastDash = false;
}
else if (c is ' ' or '-' or '_')
{
if (!lastDash && sb.Length > 0)
{
sb.Append('-');
lastDash = true;
}
}
// other punctuation is dropped
}
if (lastDash && sb.Length > 0) sb.Length--;
return sb.ToString();
}
}

/// <summary>Enables automatic heading <c>id</c> slugs.</summary>
public sealed class AutoIdentifierExtension : IBitMarkdownExtension
{
public void Setup(BitMarkdownPipelineBuilder builder)
=> builder.AstProcessors.Add(new AutoIdentifierAstProcessor());
}
Loading
Loading