Conversation
There was a problem hiding this comment.
Pull request overview
This PR replaces the Blazorise-based markdown editor with a custom MarkdownTextArea implementation (toolbar, preview, image upload) and wires up the required static assets (CSS/JS) and dependencies.
Changes:
- Replaced the Blazorise markdown component with a custom toolbar-driven
<textarea>+ preview mode using JS interop. - Added new editor static assets (
markdown-editor.jsandmarkdown-editor.css) and included them inApp.razor. - Removed Blazorise package references and the related service registration, and added unit tests covering the new editor UI/behaviors.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/LinkDotNet.Blog.UnitTests/Web/Features/Components/MarkdownTextAreaTests.cs | Adds bUnit coverage for rendering, toolbar buttons, preview toggling, and insert fallbacks. |
| src/LinkDotNet.Blog.Web/wwwroot/js/markdown-editor.js | Implements JS helpers for insert operations, undo/redo, highlighting, and keyboard shortcuts. |
| src/LinkDotNet.Blog.Web/wwwroot/css/markdown-editor.css | Provides styling for the new editor container, toolbar, preview, and table popover. |
| src/LinkDotNet.Blog.Web/Features/Components/MarkdownTextArea.razor | Replaces Blazorise editor with custom markup + JS interop for editing actions and uploads. |
| src/LinkDotNet.Blog.Web/App.razor | Includes new editor CSS/JS and Bootstrap Icons CDN. |
| src/LinkDotNet.Blog.Web/ServiceExtensions.cs | Removes Blazorise service registration extension. |
| src/LinkDotNet.Blog.Web/Program.cs | Removes the Blazorise registration call from service setup. |
| src/LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj | Drops Blazorise package references. |
| Directory.Packages.props | Removes centralized Blazorise package version entries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using var memoryStream = new MemoryStream(); | ||
| await file.OpenReadStream().CopyToAsync(memoryStream); | ||
| memoryStream.Position = 0; |
There was a problem hiding this comment.
file.OpenReadStream() uses a small default max size (and will throw for larger files). For image uploads this can easily break in production. Pass an explicit maxAllowedSize (ideally from configuration) and handle the oversize exception to show a friendly error.
| </div> | ||
| <script src="js/markdown-editor.js"></script> | ||
| <script src="_framework/blazor.web.js"></script> | ||
| <script async src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" integrity="sha512-EBLzUL8XLl+va/zAsmXwS7Z2B1F9HUHkZwyS/VKwh3S7T/U0nF4BaU29EP/ZSf6zgiIxYAnKLu6bJ8dqpmX5uw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> |
There was a problem hiding this comment.
highlight.js is loaded with async, but preview rendering triggers highlighting immediately via JS interop. If hljs hasn’t loaded yet, markdownEditor.highlightCodeBlocks() will no-op and code blocks won’t be highlighted for that preview session. Consider loading highlight.js with defer (or without async), or implement a retry/ready mechanism before attempting to highlight.
| <script async src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" integrity="sha512-EBLzUL8XLl+va/zAsmXwS7Z2B1F9HUHkZwyS/VKwh3S7T/U0nF4BaU29EP/ZSf6zgiIxYAnKLu6bJ8dqpmX5uw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> | |
| <script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" integrity="sha512-EBLzUL8XLl+va/zAsmXwS7Z2B1F9HUHkZwyS/VKwh3S7T/U0nF4BaU29EP/ZSf6zgiIxYAnKLu6bJ8dqpmX5uw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> |
src/LinkDotNet.Blog.Web/Features/Components/MarkdownTextArea.razor
Outdated
Show resolved
Hide resolved
| previewHtml = await PreviewFunction(Value); | ||
| StateHasChanged(); | ||
| await InvokeAsync(async () => | ||
| { | ||
| await JSRuntime.InvokeVoidAsync("markdownEditor.highlightCodeBlocks"); | ||
| }); |
There was a problem hiding this comment.
UpdatePreview() calls StateHasChanged() and then immediately invokes markdownEditor.highlightCodeBlocks. In Blazor, the DOM update from StateHasChanged() is not guaranteed to have completed before the JS call runs, so highlighting may miss newly rendered code blocks. A more reliable approach is to set a flag and perform the highlighting from OnAfterRenderAsync after the preview render completes.
| return { | ||
| canUndo: textareaElement.value && textareaElement.value.length > 0, | ||
| canRedo: false |
There was a problem hiding this comment.
getUndoRedoState always returns canRedo: false, which means the Redo toolbar button will remain disabled even if document.execCommand('redo') could work. Consider deriving these values from document.queryCommandEnabled('undo'/'redo') (or maintaining your own undo stack) so the UI state matches actual behavior.
| return { | |
| canUndo: textareaElement.value && textareaElement.value.length > 0, | |
| canRedo: false | |
| if (!textareaElement) { | |
| return { | |
| canUndo: false, | |
| canRedo: false | |
| }; | |
| } | |
| // Ensure the correct element is the active editing target | |
| textareaElement.focus(); | |
| let canUndo = false; | |
| let canRedo = false; | |
| if (typeof document.queryCommandEnabled === 'function') { | |
| try { | |
| canUndo = document.queryCommandEnabled('undo'); | |
| canRedo = document.queryCommandEnabled('redo'); | |
| } catch (e) { | |
| // If the command query fails, fall back to the value-based check below. | |
| } | |
| } | |
| // Preserve existing behavior as a fallback for undo if the command query is unavailable | |
| if (!canUndo && textareaElement.value && textareaElement.value.length > 0) { | |
| canUndo = true; | |
| } | |
| return { | |
| canUndo: canUndo, | |
| canRedo: canRedo |
src/LinkDotNet.Blog.Web/Features/Components/MarkdownTextArea.razor
Outdated
Show resolved
Hide resolved
| protected override async Task OnAfterRenderAsync(bool firstRender) | ||
| { | ||
| if (firstRender) | ||
| { |
There was a problem hiding this comment.
OnAfterRenderAsync only calls markdownEditor.setupKeyboardShortcuts on firstRender. Because the <textarea> is conditionally rendered (removed in preview mode and re-created when switching back), keyboard shortcuts won’t be re-attached after toggling Preview → Editor. Consider either keeping the textarea in the DOM (hide/show), or re-running the JS setup whenever returning to editor / when textAreaRef changes.
src/LinkDotNet.Blog.Web/Features/Components/MarkdownTextArea.razor
Outdated
Show resolved
Hide resolved
72f6ccb to
a1a630a
Compare
fix: Add aria-labels Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> fix: Check for mac properly Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> fix: undo/redo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> fix: proper handling Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
a1a630a to
a53eb93
Compare
|
Closes #508 |
No description provided.