Skip to content

Commit e7d2f5c

Browse files
authored
feat(blazorui): add & use BitMarkdownService class to BlazorUI #10221 (#10223)
1 parent 10e1826 commit e7d2f5c

File tree

3 files changed

+112
-84
lines changed

3 files changed

+112
-84
lines changed

src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs

+5-84
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System.Globalization;
2-
using Jint;
3-
4-
namespace Bit.BlazorUI;
1+
namespace Bit.BlazorUI;
52

63
/// <summary>
74
/// BitMarkdownViewer is a SEO friendly Blazor wrapper around the famous markedjs library.
@@ -10,13 +7,12 @@ namespace Bit.BlazorUI;
107
public partial class BitMarkdownViewer : BitComponentBase
118
{
129
private string? _html;
13-
private static string? _markedScriptText;
1410
private readonly CancellationTokenSource _cts = new();
15-
private static readonly SemaphoreSlim _markedScriptReadTextSemaphore = new(1, 1);
1611

1712

1813

1914
[Inject] private IJSRuntime _js { get; set; } = default!;
15+
[Inject] private BitMarkdownService _markdownService { get; set; } = default!;
2016

2117

2218

@@ -32,90 +28,15 @@ public partial class BitMarkdownViewer : BitComponentBase
3228

3329
protected override async Task OnInitializedAsync()
3430
{
35-
if (_js.IsRuntimeInvalid()) // prerendering
36-
{
37-
try
38-
{
39-
await RunJint();
40-
}
41-
catch (FileNotFoundException ex) when (ex.FileName?.StartsWith("Jint") is true)
42-
{
43-
Console.Error.WriteLine("Please install `Jint` nuget package on the server project.");
44-
}
45-
catch (Exception ex)
46-
{
47-
Console.Error.WriteLine(ex.Message);
48-
}
49-
}
50-
else
51-
{
52-
var scriptPath = "_content/Bit.BlazorUI.Extras/marked/marked-15.0.7.js";
53-
if ((await _js.BitMarkdownViewerCheckScriptLoaded(scriptPath)) is false)
54-
{
55-
await _js.BitExtrasInitScripts([scriptPath]);
56-
}
57-
58-
await ParseAndRender();
59-
}
31+
_html = await _markdownService.Parse(Markdown, _cts.Token);
32+
33+
StateHasChanged();
6034

6135
await base.OnInitializedAsync();
6236
}
6337

6438

6539

66-
private async Task RunJint()
67-
{
68-
if (Markdown.HasNoValue()) return;
69-
70-
await Task.Run(async () =>
71-
{
72-
await ReadMarkedScriptText();
73-
if (_markedScriptText.HasNoValue()) return;
74-
75-
using var engine = new Engine(options =>
76-
{
77-
options.Strict();
78-
options.CancellationToken(_cts.Token);
79-
options.Culture(CultureInfo.CurrentUICulture);
80-
}).Execute(_markedScriptText!);
81-
82-
var fn = engine.Evaluate("marked.parse").AsFunctionInstance();
83-
84-
_html = fn.Call(Markdown).AsString();
85-
86-
await InvokeAsync(StateHasChanged);
87-
}, _cts.Token);
88-
}
89-
90-
private async Task<string> ReadMarkedScriptText()
91-
{
92-
if (_markedScriptText is not null) return _markedScriptText;
93-
94-
try
95-
{
96-
await _markedScriptReadTextSemaphore.WaitAsync(_cts.Token);
97-
if (_markedScriptText is not null) return _markedScriptText;
98-
99-
var scriptPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "_content", "Bit.BlazorUI.Extras", "marked", "marked-15.0.7.js");
100-
101-
if (File.Exists(scriptPath) is false)
102-
{
103-
scriptPath = Path.Combine(AppContext.BaseDirectory, "wwwroot", "marked", "marked-15.0.7.js");
104-
}
105-
106-
if (File.Exists(scriptPath) is false)
107-
{
108-
return _markedScriptText = string.Empty;
109-
}
110-
111-
return _markedScriptText = await File.ReadAllTextAsync(scriptPath);
112-
}
113-
finally
114-
{
115-
_markedScriptReadTextSemaphore.Release();
116-
}
117-
}
118-
11940
private async Task OnMarkdownSet()
12041
{
12142
if (IsRendered is false) return;

src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IServiceCollectionExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public static IServiceCollection AddBitBlazorUIExtrasServices(this IServiceColle
2525
}
2626

2727
services.TryAddScoped<BitExtraServices>();
28+
services.TryAddScoped<BitMarkdownService>();
2829

2930
return services;
3031
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Globalization;
2+
using Jint;
3+
4+
namespace Bit.BlazorUI;
5+
6+
/// <summary>
7+
/// A utility service to parse Markdown texts into html strings. Works smoothly in both server and client.
8+
/// </summary>
9+
public class BitMarkdownService(IJSRuntime js)
10+
{
11+
private const string MARKED_FILE = "marked/marked-15.0.7.js";
12+
13+
14+
15+
private static string? _markedScriptText;
16+
private static readonly SemaphoreSlim _markedScriptReadTextSemaphore = new(1, 1);
17+
18+
19+
20+
public async Task<string> Parse(string? markdown, CancellationToken cancellationToken)
21+
{
22+
if (markdown.HasNoValue()) return string.Empty;
23+
24+
var html = string.Empty;
25+
26+
if (js.IsRuntimeInvalid()) // server (prerendering)
27+
{
28+
try
29+
{
30+
html = await Task.Run(async () => await RuntJint(markdown, cancellationToken), cancellationToken);
31+
}
32+
catch (FileNotFoundException ex) when (ex.FileName?.StartsWith("Jint") is true)
33+
{
34+
Console.Error.WriteLine("Please install `Jint` nuget package on the server project.");
35+
}
36+
catch (Exception ex)
37+
{
38+
Console.Error.WriteLine(ex.Message);
39+
}
40+
}
41+
else // client
42+
{
43+
var scriptPath = "_content/Bit.BlazorUI.Extras/marked/marked-15.0.7.js";
44+
if ((await js.BitMarkdownViewerCheckScriptLoaded(scriptPath)) is false)
45+
{
46+
await js.BitExtrasInitScripts([scriptPath]);
47+
}
48+
49+
html = await js.BitMarkdownViewerParse(markdown!);
50+
}
51+
52+
return html;
53+
}
54+
55+
56+
57+
private static async Task<string> RuntJint(string? markdown, CancellationToken cancellationToken)
58+
{
59+
if (markdown.HasNoValue()) return string.Empty;
60+
61+
await ReadMarkedScriptText(cancellationToken);
62+
if (_markedScriptText.HasNoValue()) return string.Empty;
63+
64+
using var engine = new Engine(options =>
65+
{
66+
options.Strict();
67+
options.CancellationToken(cancellationToken);
68+
options.Culture(CultureInfo.CurrentUICulture);
69+
}).Execute(_markedScriptText!);
70+
71+
var fn = engine.Evaluate("marked.parse").AsFunctionInstance();
72+
73+
return fn.Call(markdown).AsString();
74+
}
75+
76+
private static async Task<string> ReadMarkedScriptText(CancellationToken cancellationToken)
77+
{
78+
if (_markedScriptText is not null) return _markedScriptText;
79+
80+
try
81+
{
82+
await _markedScriptReadTextSemaphore.WaitAsync(cancellationToken);
83+
if (_markedScriptText is not null) return _markedScriptText;
84+
85+
//TODO: this script path discovery needs improvement!
86+
var scriptPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "_content", "Bit.BlazorUI.Extras", MARKED_FILE);
87+
88+
if (File.Exists(scriptPath) is false)
89+
{
90+
scriptPath = Path.Combine(AppContext.BaseDirectory, "wwwroot", MARKED_FILE);
91+
}
92+
93+
if (File.Exists(scriptPath) is false)
94+
{
95+
Console.Error.WriteLine("Could not find the marked js script file!");
96+
return _markedScriptText = string.Empty;
97+
}
98+
99+
return _markedScriptText = await File.ReadAllTextAsync(scriptPath, cancellationToken);
100+
}
101+
finally
102+
{
103+
_markedScriptReadTextSemaphore.Release();
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)