Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MarkdownViewer extra component (#10111) #10211

Merged
Merged
3 changes: 3 additions & 0 deletions src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Jint" Version="4.2.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Condition="'$(TargetFramework)' == 'net8.0'" Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0" />
<PackageReference Condition="'$(TargetFramework)' == 'net9.0'" Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,20 +130,18 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var scripts = new List<string> { "_content/Bit.BlazorUI.Extras/chart.js/chartjs-2.9.4.js" };
await _js.BitExtrasInitScripts(["_content/Bit.BlazorUI.Extras/chart.js/chartjs-2.9.4.js"]);

if (IsDateAdapterRequired && DateAdapterScripts is null)
{
scripts.Add("_content/Bit.BlazorUI.Extras/chart.js/chartjs-2.9.4-adapter.js");
await _js.BitExtrasInitScripts(["_content/Bit.BlazorUI.Extras/chart.js/chartjs-2.9.4-adapter.js"]);
}

if (DateAdapterScripts is not null)
{
scripts.AddRange(DateAdapterScripts);
await _js.BitExtrasInitScripts(DateAdapterScripts);
}

await _js.BitChartJsInitChartJs(scripts);

if (Config is not null)
{
await _js.BitChartJsSetupChart(Config);
Expand Down
33 changes: 0 additions & 33 deletions src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ namespace BitBlazorUI {
}

export class BitChart {
private static _initPromise?: Promise<unknown>;
private static _bitCharts = new Map<string, Chart>();

public static getChartJs(canvasId: string) {
Expand All @@ -31,38 +30,6 @@ namespace BitBlazorUI {
return BitChart._bitCharts.get(canvasId)!;
}

public static async initChartJs(scripts: string[]) {
if (BitChart._initPromise) {
await BitChart._initPromise;
}

const allScripts = Array.from(document.scripts).map(s => s.src);
const notAppenedScripts = scripts.filter(s => !allScripts.find(as => as.endsWith(s)));

if (notAppenedScripts.length == 0) return Promise.resolve();

const promise = new Promise(async (resolve: any, reject: any) => {
try {
for (let url of notAppenedScripts) await addScript(url);
resolve();
} catch (e: any) {
reject(e);
}
});
BitChart._initPromise = promise;
return promise;

async function addScript(url: string) {
return new Promise((res, rej) => {
const script = document.createElement('script');
script.src = url;
script.onload = res;
script.onerror = rej;
document.body.appendChild(script);
})
}
}

public static removeChart(canvasId: string) {
if (!BitChart._bitCharts.has(canvasId)) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ internal static class BitChartJsInterop
Converters = { new IsoDateTimeConverter() }
};

public static ValueTask BitChartJsInitChartJs(this IJSRuntime jsRuntime, IEnumerable<string> scripts)
{
return jsRuntime.InvokeVoid("BitBlazorUI.BitChart.initChartJs", scripts);
}

public static ValueTask BitChartJsRemoveChart(this IJSRuntime jsRuntime, string? canvasId)
{
return jsRuntime.InvokeVoid("BitBlazorUI.BitChart.removeChart", canvasId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Bit.BlazorUI;
// a fork from the Blazor QuickGrid at https://github.com/dotnet/aspnetcore/tree/main/src/Components/QuickGrid

namespace Bit.BlazorUI;

/// <summary>
/// BitDataGrid is a robust way to display an information-rich collection of items, and allow people to sort, and filter the content.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@namespace Bit.BlazorUI
@inherits BitComponentBase

<div @ref="RootElement"
@attributes="HtmlAttributes"
id="@_Id"
style="@StyleBuilder.Value"
class="@ClassBuilder.Value"
dir="@Dir?.ToString().ToLower()">
@((MarkupString)(_html ?? ""))
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System.Globalization;
using Jint;

namespace Bit.BlazorUI;

/// <summary>
/// BitMarkdownViewer is a SEO friendly Blazor wrapper around the famous markedjs library.
/// <see href="https://github.com/markedjs/marked"/>
/// </summary>
public partial class BitMarkdownViewer : BitComponentBase
{
private string? _html;
private static string? _markedScriptText;
private readonly CancellationTokenSource _cts = new();
private static readonly SemaphoreSlim _markedScriptReadTextSemaphore = new(1, 1);



[Inject] private IJSRuntime _js { get; set; } = default!;



/// <summary>
/// The Markdown string value to render as an html element.
/// </summary>
[Parameter, CallOnSet(nameof(OnMarkdownSet))]
public string? Markdown { get; set; }



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

protected override async Task OnInitializedAsync()
{
if (_js.IsRuntimeInvalid()) // prerendering
{
try
{
await RunJint();
}
catch (FileNotFoundException ex) when (ex.FileName?.StartsWith("Jint") is true)
{
Console.Error.WriteLine("Please install `Jint` nuget package on the server project.");
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
}
}
else
{
var scriptPath = "_content/Bit.BlazorUI.Extras/marked/marked-15.0.7.js";
if ((await _js.BitMarkdownViewerCheckScriptLoaded(scriptPath)) is false)
{
await _js.BitExtrasInitScripts([scriptPath]);
}

await ParseAndRender();
}

await base.OnInitializedAsync();
}



private async Task RunJint()
{
if (Markdown.HasNoValue()) return;

await Task.Run(async () =>
{
await ReadMarkedScriptText();
if (_markedScriptText.HasNoValue()) return;

using var engine = new Engine(options =>
{
options.Strict();
options.CancellationToken(_cts.Token);
options.Culture(CultureInfo.CurrentUICulture);
}).Execute(_markedScriptText!);

var fn = engine.Evaluate("marked.parse").AsFunctionInstance();

_html = fn.Call(Markdown).AsString();

await InvokeAsync(StateHasChanged);
}, _cts.Token);
}

private async Task<string> ReadMarkedScriptText()
{
if (_markedScriptText is not null) return _markedScriptText;

try
{
await _markedScriptReadTextSemaphore.WaitAsync(_cts.Token);
if (_markedScriptText is not null) return _markedScriptText;

var scriptPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "_content", "Bit.BlazorUI.Extras", "marked", "marked-15.0.7.js");

if (File.Exists(scriptPath) is false)
{
scriptPath = Path.Combine(AppContext.BaseDirectory, "wwwroot", "marked", "marked-15.0.7.js");
}

if (File.Exists(scriptPath) is false)
{
return _markedScriptText = string.Empty;
}

return _markedScriptText = await File.ReadAllTextAsync(scriptPath);
}
finally
{
_markedScriptReadTextSemaphore.Release();
}
}

private async Task OnMarkdownSet()
{
if (IsRendered is false) return;

await ParseAndRender();
}

private async Task ParseAndRender()
{
if (Markdown.HasNoValue()) return;

_html = await _js.BitMarkdownViewerParse(Markdown!);

StateHasChanged();
}



protected override async ValueTask DisposeAsync(bool disposing)
{
if (IsDisposed || disposing is false) return;

_cts.Cancel();
_cts.Dispose();

await base.DisposeAsync(disposing);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import '../../../Bit.BlazorUI/Styles/functions.scss';

.bit-mdv {
all: revert;

* {
all: revert;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace BitBlazorUI {
export class MarkdownViewer {
public static checkScriptLoaded(script: string) {
return window.marked !== undefined;
}

public static parse(md: string) {
const html = marked.parse(md, { async: false });
return html;
}

public static async parseAsync(md: string) {
const html = await marked.parse(md, { async: true });
return html;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Bit.BlazorUI;

internal static class BitMarkdownViewerJsRuntimeExtensions
{
public static ValueTask<bool> BitMarkdownViewerCheckScriptLoaded(this IJSRuntime jsRuntime, string script)
{
return jsRuntime.FastInvoke<bool>("BitBlazorUI.MarkdownViewer.checkScriptLoaded", script);
}

public static ValueTask<string> BitMarkdownViewerParse(this IJSRuntime jsRuntime, string markdown)
{
return OperatingSystem.IsBrowser()
? jsRuntime.FastInvoke<string>("BitBlazorUI.MarkdownViewer.parse", markdown)
: jsRuntime.Invoke<string>("BitBlazorUI.MarkdownViewer.parseAsync", markdown);
}
}
Loading
Loading