feat: add markdown editor component with extensible toolbar system
Release NuGet Packages / pack-and-push (release) Successful in 3m28s

- Add SbMarkdownEditor component with EasyMDE and CodeMirror integration
  - Implement IMdToolbarContributor interface for extensible toolbar customization
  - Add MdToolbarService and MdToolbarOptions for toolbar management
  - Include vendor libraries (CodeMirror, EasyMDE, Mermaid, Highlight.js)
  - Add markdown editor interop and JavaScript integration
  - Refactor SbMarkdownViewer to support both embedded and standalone modes
  - Add demo page and localization keys for markdown editor
  - Normalize whitespace and line endings across all component files
This commit is contained in:
2026-06-22 12:36:34 +03:30
parent 87a3894d92
commit f9ba4f2980
46 changed files with 25768 additions and 60 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# SbMarkdownViewer
Renders markdown content as HTML using the Markdig library. Supports advanced markdown extensions including tables, task lists, and code blocks.
Renders markdown content as HTML using client-side marked.js (offline). Supports mermaid diagrams, syntax highlighting, and alert callouts.
## Parameters
@@ -17,7 +17,7 @@ Renders markdown content as HTML using the Markdig library. Supports advanced ma
## Markdown Features
The component uses Markdig with advanced extensions enabled, supporting:
The component uses marked.js with GFM support, loaded offline from SufiBlazor vendor assets:
- **Standard Markdown**: Headings, paragraphs, lists, links, images, emphasis
- **Extended Syntax**: Tables, task lists, strikethrough
+189
View File
@@ -0,0 +1,189 @@
# SbMarkdownEditor
A markdown editor built on EasyMDE (CodeMirror 5 + marked.js) with live preview, offline mermaid/highlight.js rendering, source mode, AI diff review, and toolbar contributors.
## Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| Value | string | `""` | Markdown content (two-way bindable) |
| ValueChanged | EventCallback\<string\> | - | Fired when markdown changes |
| ValueHtml | string? | null | Rendered HTML from preview (optional two-way bind) |
| ValueHtmlChanged | EventCallback\<string?\> | - | Fired when preview HTML changes |
| ReadOnly | bool | false | Read-only editor |
| Disabled | bool | false | Disabled state |
| Placeholder | string? | null | Placeholder text |
| EnablePreview | bool | true | Enable live preview (ignored in source mode) |
| EnableMermaid | bool | true | Render ` ```mermaid ` blocks in preview |
| EnableHighlight | bool | true | Syntax-highlight fenced code blocks |
| HighlightTheme | string | `"github"` | highlight.js theme (`github`, `github-dark`, …) |
| EditorMode | SbMarkdownEditorMode | Markdown | `Markdown` or `Source` (raw HTML / templates) |
| UseToolbarContributors | bool | false | Include items from `IMdToolbarContributor` services |
| HideToolbar | bool | false | Hide the Blazor-rendered toolbar |
| ToolbarItems | IReadOnlyList\<SbMarkdownToolbarItem\>? | null | Fixed custom toolbar (overrides defaults) |
| MinHeight | string? | `"200px"` | Minimum editor height |
| MaxHeight | string? | null | Maximum editor height |
| FallbackRows | int | 12 | Textarea rows when JS assets fail to load |
| IsDiffReview | bool | false | Merge-style diff review mode |
| OriginalValue | string | `""` | Left/original pane in diff mode |
| SuggestedValue | string | `""` | Right/modified pane in diff mode |
| SuggestedValueChanged | EventCallback\<string\> | - | Fired when user edits suggested side |
| OnApplyChanges / OnDiscardChanges | EventCallback | - | Host callbacks for diff actions |
| OnShortcut | EventCallback\<string\> | - | Keyboard shortcuts (`save`, `preview`) |
| RightToLeft | bool | false | RTL layout |
| Class / Style | string? | null | Container styling |
## Events
| Event | Description |
|-------|-------------|
| ValueChanged | Markdown text changed |
| ValueHtmlChanged | Preview HTML changed (when preview enabled) |
| SuggestedValueChanged | Modified side changed in diff mode |
| OnShortcut | `Ctrl/Cmd+S``save`, `Ctrl/Cmd+P``preview` |
## CSS Classes
- `sb-markdown-editor` — Root container
- `sb-markdown-editor__toolbar` — Custom toolbar
- `sb-markdown-editor__toolbar-btn` — Toolbar button
- `sb-markdown-editor__textarea` — EasyMDE host textarea
- `sb-markdown-editor__diff-host` — CodeMirror MergeView host
- `sb-markdown-editor--diff` — Diff review mode
- `sb-markdown-editor--source` — Source / raw mode
## Preview Features
Offline assets under `_content/SufiChain.SufiBlazor/vendor/`:
- **EasyMDE** — Editing, side-by-side preview, fullscreen
- **marked.js** — GFM markdown parsing
- **highlight.js** — Fenced code block syntax highlighting
- **mermaid** — Diagram rendering in preview
- **CodeMirror MergeView** — Diff review (red/green merge UI)
Alert callouts via fenced blocks: `note`, `tip`, `warn`, `alert`.
## Examples
### Basic editor with toolbar
```razor
<SbMarkdownEditor @bind-Value="_content"
MinHeight="320px"
UseToolbarContributors="true"
EnableMermaid="true"
EnableHighlight="true" />
```
### Mermaid & syntax highlighting
Toggle **Preview** or **Side by Side** in the toolbar to render diagrams and code:
````markdown
```csharp
public class Example { }
```
```mermaid
flowchart LR
A --> B
```
````
### Source mode (HTML email templates)
```razor
<SbMarkdownEditor @bind-Value="_htmlTemplate"
EditorMode="SbMarkdownEditorMode.Source"
EnablePreview="false"
UseToolbarContributors="true" />
```
### Diff review (AI copilot)
```razor
<SbMarkdownEditor IsDiffReview="true"
OriginalValue="@_original"
SuggestedValue="@_suggested"
SuggestedValueChanged="@((v) => _suggested = v)" />
<SbStack Direction="SbStackDirection.Row" Gap="2">
<SbButton OnClick="ApplySuggested">Apply</SbButton>
<SbButton Variant="SbButtonVariant.Outlined" OnClick="DiscardSuggested">Discard</SbButton>
</SbStack>
```
### Without toolbar
```razor
<SbMarkdownEditor @bind-Value="_note"
HideToolbar="true"
EnablePreview="false"
MinHeight="180px" />
```
## Toolbar Extensibility
Set `UseToolbarContributors="true"` and register contributors with `AddMdToolbarContributor<T>()`.
### Implementing a contributor
```csharp
public class MyMdToolbarContributor : IMdToolbarContributor
{
public int Order => 150;
public Task ConfigureToolbarAsync(MdToolbarContext context)
{
context.Items.Add(new MdToolbarContributedItem
{
Id = "insert-snippet",
Group = "insert",
Order = 20,
Icon = "📝",
Tooltip = "Insert snippet",
OnClickAsync = async ctx =>
{
await ctx.InsertTextAsync?.Invoke("**snippet**")!;
}
});
return Task.CompletedTask;
}
}
```
Register in your module:
```csharp
context.Services.AddMdToolbarContributor<MyMdToolbarContributor>();
```
### Action context helpers
- `InsertTextAsync(text)` — Insert at cursor
- `InsertImageMarkdownAsync(url, alt)` — `![alt](url)`
- `InsertLinkMarkdownAsync(url, text)` — `[text](url)`
- `GetValueAsync()` / `GetSelectionAsync()`
## File Manager integration
Use `SufiChain.SufiAbp.FileManager.MarkdownEditor` for gallery image and file attachment toolbar buttons. See **FileManagerMarkdownEditorIntegration** documentation.
```razor
<FileGalleryHost />
<SbMarkdownEditor @bind-Value="_content"
UseToolbarContributors="true" />
```
## Related components
- **SbMarkdownViewer** — Read-only client-side markdown rendering (same marked/mermaid/highlight stack)
- **SbRichTextEditor** — HTML rich text editor (Quill.js)
## Notes
- Requires interactive render mode (Blazor Server / WebAssembly).
- Vendor bundles are loaded on demand via `sufiblazor-markdown-editor.js`.
- KomTheme registers `SufiBlazor.MarkdownEditor` bundle for EasyMDE CSS/JS preloading.