feat: enhance SbMarkdownEditor with source language support and toolbar item loading
- Add SourceLanguage and IncludeDefaultToolbarItems parameters to SbMarkdownEditor - Refactor toolbar item loading logic into a separate method for better readability - Update JavaScript interop to handle source language in editor initialization - Improve error handling in destroyEditor method to prevent crashes on disconnection
This commit is contained in:
@@ -25,7 +25,9 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
||||
[Parameter] public bool EnableHighlight { get; set; } = true;
|
||||
[Parameter] public string HighlightTheme { get; set; } = "github";
|
||||
[Parameter] public SbMarkdownEditorMode EditorMode { get; set; } = SbMarkdownEditorMode.Markdown;
|
||||
[Parameter] public string? SourceLanguage { get; set; }
|
||||
[Parameter] public bool UseToolbarContributors { get; set; }
|
||||
[Parameter] public bool IncludeDefaultToolbarItems { get; set; } = true;
|
||||
[Parameter] public bool HideToolbar { get; set; }
|
||||
[Parameter] public IReadOnlyList<SbMarkdownToolbarItem>? ToolbarItems { get; set; }
|
||||
[Parameter] public EventCallback<string> OnShortcut { get; set; }
|
||||
@@ -52,6 +54,8 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
||||
private string? _lastRenderedSuggested;
|
||||
private bool _lastRenderedDiffMode;
|
||||
private SbMarkdownEditorMode _lastEditorMode;
|
||||
private string? _lastSourceLanguage;
|
||||
private bool _lastIncludeDefaultToolbarItems;
|
||||
private List<SbMarkdownToolbarItem> _toolbarItems = new();
|
||||
private bool _useFallback;
|
||||
|
||||
@@ -64,18 +68,7 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
||||
_interop = new SbMarkdownEditorInterop(JSRuntime);
|
||||
_dotNetRef = DotNetObjectReference.Create(this);
|
||||
|
||||
if (!IsDiffReview && UseToolbarContributors)
|
||||
{
|
||||
_toolbarItems = await ToolbarService.GetToolbarItemsAsync(_editorId, includeContributors: true);
|
||||
}
|
||||
else if (!IsDiffReview && ToolbarItems != null)
|
||||
{
|
||||
_toolbarItems = ToolbarItems.ToList();
|
||||
}
|
||||
else if (!IsDiffReview)
|
||||
{
|
||||
_toolbarItems = await ToolbarService.GetToolbarItemsAsync(_editorId, includeDefaults: true, includeContributors: false);
|
||||
}
|
||||
await LoadToolbarItemsAsync();
|
||||
|
||||
await InitializeEditorAsync();
|
||||
_lastRenderedValue = Value;
|
||||
@@ -83,6 +76,8 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
||||
_lastRenderedSuggested = SuggestedValue;
|
||||
_lastRenderedDiffMode = IsDiffReview;
|
||||
_lastEditorMode = EditorMode;
|
||||
_lastSourceLanguage = SourceLanguage;
|
||||
_lastIncludeDefaultToolbarItems = IncludeDefaultToolbarItems;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
return;
|
||||
}
|
||||
@@ -107,11 +102,48 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
||||
_lastRenderedSuggested = SuggestedValue;
|
||||
}
|
||||
}
|
||||
else if (Value != _lastRenderedValue || EditorMode != _lastEditorMode)
|
||||
else if (Value != _lastRenderedValue ||
|
||||
EditorMode != _lastEditorMode ||
|
||||
SourceLanguage != _lastSourceLanguage ||
|
||||
IncludeDefaultToolbarItems != _lastIncludeDefaultToolbarItems)
|
||||
{
|
||||
await _interop.SetValueAsync(_editorId, Value);
|
||||
_lastRenderedValue = Value;
|
||||
_lastEditorMode = EditorMode;
|
||||
if (EditorMode != _lastEditorMode ||
|
||||
SourceLanguage != _lastSourceLanguage ||
|
||||
IncludeDefaultToolbarItems != _lastIncludeDefaultToolbarItems)
|
||||
{
|
||||
await ReinitializeEditorAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await _interop.SetValueAsync(_editorId, Value);
|
||||
_lastRenderedValue = Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadToolbarItemsAsync()
|
||||
{
|
||||
if (IsDiffReview)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var editorId = GetEditorId();
|
||||
|
||||
if (UseToolbarContributors)
|
||||
{
|
||||
_toolbarItems = await ToolbarService.GetToolbarItemsAsync(
|
||||
editorId,
|
||||
includeDefaults: IncludeDefaultToolbarItems,
|
||||
includeContributors: true);
|
||||
}
|
||||
else if (ToolbarItems != null)
|
||||
{
|
||||
_toolbarItems = ToolbarItems.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_toolbarItems = await ToolbarService.GetToolbarItemsAsync(editorId, includeDefaults: true, includeContributors: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,12 +173,13 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
||||
|
||||
_editorId = await _interop.InitEditorAsync(_textarea, _dotNetRef!, new SbMarkdownEditorInitOptions
|
||||
{
|
||||
EditorId = _elementId,
|
||||
EditorId = GetEditorId(),
|
||||
Value = Value,
|
||||
Placeholder = Placeholder,
|
||||
ReadOnly = ReadOnly,
|
||||
Direction = RightToLeft ? "rtl" : "ltr",
|
||||
EditorMode = EditorMode == SbMarkdownEditorMode.Source ? "source" : "markdown",
|
||||
SourceLanguage = SourceLanguage,
|
||||
EnablePreview = EnablePreview && EditorMode != SbMarkdownEditorMode.Source,
|
||||
EnableMermaid = EnableMermaid,
|
||||
EnableHighlight = EnableHighlight,
|
||||
@@ -177,14 +210,23 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
||||
IsReady = false;
|
||||
_useFallback = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await LoadToolbarItemsAsync();
|
||||
await InitializeEditorAsync();
|
||||
_lastRenderedDiffMode = IsDiffReview;
|
||||
_lastRenderedValue = Value;
|
||||
_lastRenderedOriginal = OriginalValue;
|
||||
_lastRenderedSuggested = SuggestedValue;
|
||||
_lastEditorMode = EditorMode;
|
||||
_lastSourceLanguage = SourceLanguage;
|
||||
_lastIncludeDefaultToolbarItems = IncludeDefaultToolbarItems;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private string GetEditorId() =>
|
||||
string.Equals(SourceLanguage, "html", StringComparison.OrdinalIgnoreCase)
|
||||
? $"{_elementId}-html"
|
||||
: _elementId;
|
||||
|
||||
[JSInvokable]
|
||||
public async Task OnEditorChangeAsync(string value, string html)
|
||||
{
|
||||
|
||||
@@ -88,7 +88,19 @@ public class SbMarkdownEditorInterop : IAsyncDisposable
|
||||
{
|
||||
if (_module != null)
|
||||
{
|
||||
await _module.InvokeVoidAsync("destroyEditor", editorId);
|
||||
try
|
||||
{
|
||||
await _module.InvokeVoidAsync("destroyEditor", editorId);
|
||||
}
|
||||
catch (JSDisconnectedException)
|
||||
{
|
||||
}
|
||||
catch (JSException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +147,7 @@ public class SbMarkdownEditorInitOptions
|
||||
public bool ReadOnly { get; set; }
|
||||
public string? Direction { get; set; }
|
||||
public string EditorMode { get; set; } = "markdown";
|
||||
public string? SourceLanguage { get; set; }
|
||||
public bool EnablePreview { get; set; } = true;
|
||||
public bool EnableMermaid { get; set; } = true;
|
||||
public bool EnableHighlight { get; set; } = true;
|
||||
|
||||
@@ -11,6 +11,7 @@ let easyMdeLoadPromise = null;
|
||||
let markedLoadPromise = null;
|
||||
let assetsLoadPromise = null;
|
||||
let diffAssetsLoadPromise = null;
|
||||
let codeAssetsLoadPromise = null;
|
||||
|
||||
function getContentBasePath() {
|
||||
const scripts = document.querySelectorAll('script[src*="sufiblazor-markdown-editor.js"]');
|
||||
@@ -131,6 +132,27 @@ async function ensureDiffAssets() {
|
||||
return diffAssetsLoadPromise;
|
||||
}
|
||||
|
||||
async function ensureCodeAssets() {
|
||||
if (typeof CodeMirror !== 'undefined' && CodeMirror.modes?.htmlmixed) {
|
||||
return true;
|
||||
}
|
||||
if (codeAssetsLoadPromise) {
|
||||
return codeAssetsLoadPromise;
|
||||
}
|
||||
codeAssetsLoadPromise = (async () => {
|
||||
const base = getContentBasePath();
|
||||
loadCss(`${base}/vendor/codemirror/codemirror.css`);
|
||||
await loadScript(`${base}/vendor/codemirror/codemirror.js`);
|
||||
await loadScript(`${base}/vendor/codemirror/xml.js`);
|
||||
await loadScript(`${base}/vendor/codemirror/javascript.js`);
|
||||
await loadScript(`${base}/vendor/codemirror/css.js`);
|
||||
await loadScript(`${base}/vendor/codemirror/htmlmixed.js`);
|
||||
await loadScript(`${base}/vendor/codemirror/markdown.js`);
|
||||
return typeof CodeMirror !== 'undefined';
|
||||
})();
|
||||
return codeAssetsLoadPromise;
|
||||
}
|
||||
|
||||
function configureMarked() {
|
||||
if (!marked || window.__sbMarkedConfigured) {
|
||||
return;
|
||||
@@ -268,7 +290,9 @@ export async function initEditor(textarea, dotNetRef, options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!sourceMode) {
|
||||
if (sourceMode) {
|
||||
await ensureCodeAssets();
|
||||
} else {
|
||||
await ensureAssets({
|
||||
enableMermaid: options.enableMermaid,
|
||||
enableHighlight: options.enableHighlight,
|
||||
@@ -307,6 +331,12 @@ export async function initEditor(textarea, dotNetRef, options) {
|
||||
|
||||
const easyMDE = new EasyMDE(easyOptions);
|
||||
|
||||
if (sourceMode) {
|
||||
const language = (options.sourceLanguage || '').toLowerCase();
|
||||
easyMDE.codemirror.setOption('mode', language === 'html' ? 'htmlmixed' : 'markdown');
|
||||
easyMDE.codemirror.setOption('htmlMode', language === 'html');
|
||||
}
|
||||
|
||||
easyMDE.codemirror.on('change', () => {
|
||||
const value = easyMDE.value();
|
||||
let html = '';
|
||||
@@ -461,14 +491,17 @@ export function setPreview(editorId, show) {
|
||||
export function destroyEditor(editorId) {
|
||||
const diff = diffEditors.get(editorId);
|
||||
if (diff) {
|
||||
diff.mergeView.wrapper.parentNode?.removeChild(diff.mergeView.wrapper);
|
||||
const wrapper = diff.mergeView?.wrapper;
|
||||
if (wrapper?.parentNode) {
|
||||
wrapper.parentNode.removeChild(wrapper);
|
||||
}
|
||||
diffEditors.delete(editorId);
|
||||
return;
|
||||
}
|
||||
const stored = editors.get(editorId);
|
||||
if (stored) {
|
||||
stored.easyMDE.toTextArea();
|
||||
stored.easyMDE.clearAutosavedValue?.();
|
||||
stored.easyMDE?.toTextArea?.();
|
||||
stored.easyMDE?.clearAutosavedValue?.();
|
||||
editors.delete(editorId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user