@using Microsoft.JSInterop @implements IAsyncDisposable @inject IJSRuntime JS @inject HttpClient Http
@if (ShowFetchButton) {
}
@code { private readonly string EditorId = $"milkdown-editor-{Guid.NewGuid():N}"; private readonly CancellationTokenSource _cts = new(); private IJSObjectReference? _module; private DotNetObjectReference? _dotNetHelper; [Parameter] public bool ShowFetchButton { get; set; } = true; [Parameter] public string InitialMarkdown { get; set; } = string.Empty; [Parameter] public EventCallback OnSave { get; set; } [Parameter] public string Height { get; set; } = "500px"; [Parameter] public string Width { get; set; } = "100%"; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _dotNetHelper = DotNetObjectReference.Create(this); try { // Import the isolated JavaScript module _module = await JS.InvokeAsync( "import", "./_content/NexusReader.UI.Shared/js/milkdownWrapper.js" ); // Call the initialization function in the wrapper await _module.InvokeVoidAsync("initEditor", EditorId, _dotNetHelper, InitialMarkdown); } catch (Exception ex) { // Log the exception gracefully and do not crash the component Console.WriteLine($"[MarkdownEditor] Error initializing Milkdown editor: {ex.Message}"); } } } public async Task FetchContentAsync() { if (_module is not null) { try { // Retrieve the updated markdown from JS var markdown = await _module.InvokeAsync("getMarkdownContent", EditorId); if (OnSave.HasDelegate) { await OnSave.InvokeAsync(markdown); } } catch (Exception ex) { Console.WriteLine($"[MarkdownEditor] Error fetching markdown content: {ex.Message}"); } } } [JSInvokable] public async Task UploadImageFromJs(string filename, string contentType, byte[] fileBytes) { try { using var content = new MultipartFormDataContent(); var fileContent = new ByteArrayContent(fileBytes); fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType); content.Add(fileContent, "file", filename); var response = await Http.PostAsync("/api/media/upload", content, _cts.Token); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadFromJsonAsync( NexusReader.Application.Common.AppJsonContext.Default.UploadResultDto, _cts.Token); return result?.Url ?? string.Empty; } else { var errorMsg = await response.Content.ReadAsStringAsync(); Console.WriteLine($"[MarkdownEditor] Image upload failed: {response.StatusCode} - {errorMsg}"); return string.Empty; } } catch (Exception ex) { Console.WriteLine($"[MarkdownEditor] Exception during image upload: {ex.Message}"); return string.Empty; } } public async ValueTask DisposeAsync() { try { _cts.Cancel(); _cts.Dispose(); } catch { // Fail silently if cancellation token disposal fails } try { if (_module is not null) { // Clean up the JS editor instance to prevent memory leaks await _module.InvokeVoidAsync("destroyEditor", EditorId); await _module.DisposeAsync(); } } catch (JSDisconnectedException) { // Fail silently during circuit disconnection } catch (ObjectDisposedException) { // Fail silently if JS runtime/module is already disposed } catch (Exception ex) { // Log other unexpected errors Console.WriteLine($"[MarkdownEditor] Unexpected error during JS cleanup: {ex.Message}"); } finally { _dotNetHelper?.Dispose(); } } }