feat(editor): align selection popup and all editor control elements styling with Reader (#81)
## Summary of Changes This pull request aligns all major interactive editor control elements in the Milkdown Crepe editor with the premium `SelectionAiPanel` / `IntelligenceToolbar` glassmorphism design. ### Changes: 1. **Selection Bubble Menu Unification:** Relocated the selection menu overrides from `Creator.razor.css` to `app.css` to resolve scoping bugs. Themed to match the Reader's selection popup 1:1. 2. **Editor Controls Theming:** Themed table cell drag handles, table actions popups, line insertion handles & add buttons, Notion-style paragraph drag handles, and slash commands menus with glassmorphic backgrounds, perimeter borders, hover transitions, and active accent states. 3. **Visibility Lifecycle Fixes:** Excluded `.cell-handle` and `.milkdown-block-handle` from explicit `display: none !important` rules when hidden, preserving their dimensions for correct JS positioning calculations and preventing handles from jumping/sliding. 4. **Table Margin Clipping Fix:** Set `overflow: visible !important` on `.tableWrapper` to allow table controls to draw cleanly into the editor canvas's padding zone without boundary clipping. Resolves #82. --------- Co-authored-by: Marek Jasiński <jasins.marek@gmail.com> Reviewed-on: #81 Co-authored-by: Antigravity <antigravity@google.com> Co-committed-by: Antigravity <antigravity@google.com>
This commit was merged in pull request #81.
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
@using Microsoft.JSInterop
|
||||
@implements IAsyncDisposable
|
||||
@inject IJSRuntime JS
|
||||
@inject HttpClient Http
|
||||
|
||||
<div class="markdown-editor-container" style="height: @Height; width: @Width;">
|
||||
<div id="@EditorId" class="milkdown-editor-wrapper"></div>
|
||||
@if (ShowFetchButton)
|
||||
{
|
||||
<div class="editor-actions">
|
||||
<button type="button" @onclick="FetchContentAsync" class="nexus-btn">
|
||||
Fetch Markdown Content
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private readonly string EditorId = $"milkdown-editor-{Guid.NewGuid():N}";
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
private IJSObjectReference? _module;
|
||||
private DotNetObjectReference<MarkdownEditor>? _dotNetHelper;
|
||||
|
||||
[Parameter]
|
||||
public bool ShowFetchButton { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public string InitialMarkdown { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> 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<IJSObjectReference>(
|
||||
"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<string>("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<string> 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.DTOs.Media.UploadResultDto>(
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user