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 bool EnableHighlight { get; set; } = true;
|
||||||
[Parameter] public string HighlightTheme { get; set; } = "github";
|
[Parameter] public string HighlightTheme { get; set; } = "github";
|
||||||
[Parameter] public SbMarkdownEditorMode EditorMode { get; set; } = SbMarkdownEditorMode.Markdown;
|
[Parameter] public SbMarkdownEditorMode EditorMode { get; set; } = SbMarkdownEditorMode.Markdown;
|
||||||
|
[Parameter] public string? SourceLanguage { get; set; }
|
||||||
[Parameter] public bool UseToolbarContributors { get; set; }
|
[Parameter] public bool UseToolbarContributors { get; set; }
|
||||||
|
[Parameter] public bool IncludeDefaultToolbarItems { get; set; } = true;
|
||||||
[Parameter] public bool HideToolbar { get; set; }
|
[Parameter] public bool HideToolbar { get; set; }
|
||||||
[Parameter] public IReadOnlyList<SbMarkdownToolbarItem>? ToolbarItems { get; set; }
|
[Parameter] public IReadOnlyList<SbMarkdownToolbarItem>? ToolbarItems { get; set; }
|
||||||
[Parameter] public EventCallback<string> OnShortcut { get; set; }
|
[Parameter] public EventCallback<string> OnShortcut { get; set; }
|
||||||
@@ -52,6 +54,8 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
|||||||
private string? _lastRenderedSuggested;
|
private string? _lastRenderedSuggested;
|
||||||
private bool _lastRenderedDiffMode;
|
private bool _lastRenderedDiffMode;
|
||||||
private SbMarkdownEditorMode _lastEditorMode;
|
private SbMarkdownEditorMode _lastEditorMode;
|
||||||
|
private string? _lastSourceLanguage;
|
||||||
|
private bool _lastIncludeDefaultToolbarItems;
|
||||||
private List<SbMarkdownToolbarItem> _toolbarItems = new();
|
private List<SbMarkdownToolbarItem> _toolbarItems = new();
|
||||||
private bool _useFallback;
|
private bool _useFallback;
|
||||||
|
|
||||||
@@ -64,18 +68,7 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
|||||||
_interop = new SbMarkdownEditorInterop(JSRuntime);
|
_interop = new SbMarkdownEditorInterop(JSRuntime);
|
||||||
_dotNetRef = DotNetObjectReference.Create(this);
|
_dotNetRef = DotNetObjectReference.Create(this);
|
||||||
|
|
||||||
if (!IsDiffReview && UseToolbarContributors)
|
await LoadToolbarItemsAsync();
|
||||||
{
|
|
||||||
_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 InitializeEditorAsync();
|
await InitializeEditorAsync();
|
||||||
_lastRenderedValue = Value;
|
_lastRenderedValue = Value;
|
||||||
@@ -83,6 +76,8 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
|||||||
_lastRenderedSuggested = SuggestedValue;
|
_lastRenderedSuggested = SuggestedValue;
|
||||||
_lastRenderedDiffMode = IsDiffReview;
|
_lastRenderedDiffMode = IsDiffReview;
|
||||||
_lastEditorMode = EditorMode;
|
_lastEditorMode = EditorMode;
|
||||||
|
_lastSourceLanguage = SourceLanguage;
|
||||||
|
_lastIncludeDefaultToolbarItems = IncludeDefaultToolbarItems;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -107,11 +102,48 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
|||||||
_lastRenderedSuggested = SuggestedValue;
|
_lastRenderedSuggested = SuggestedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Value != _lastRenderedValue || EditorMode != _lastEditorMode)
|
else if (Value != _lastRenderedValue ||
|
||||||
|
EditorMode != _lastEditorMode ||
|
||||||
|
SourceLanguage != _lastSourceLanguage ||
|
||||||
|
IncludeDefaultToolbarItems != _lastIncludeDefaultToolbarItems)
|
||||||
|
{
|
||||||
|
if (EditorMode != _lastEditorMode ||
|
||||||
|
SourceLanguage != _lastSourceLanguage ||
|
||||||
|
IncludeDefaultToolbarItems != _lastIncludeDefaultToolbarItems)
|
||||||
|
{
|
||||||
|
await ReinitializeEditorAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await _interop.SetValueAsync(_editorId, Value);
|
await _interop.SetValueAsync(_editorId, Value);
|
||||||
_lastRenderedValue = Value;
|
_lastRenderedValue = Value;
|
||||||
_lastEditorMode = EditorMode;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = await _interop.InitEditorAsync(_textarea, _dotNetRef!, new SbMarkdownEditorInitOptions
|
||||||
{
|
{
|
||||||
EditorId = _elementId,
|
EditorId = GetEditorId(),
|
||||||
Value = Value,
|
Value = Value,
|
||||||
Placeholder = Placeholder,
|
Placeholder = Placeholder,
|
||||||
ReadOnly = ReadOnly,
|
ReadOnly = ReadOnly,
|
||||||
Direction = RightToLeft ? "rtl" : "ltr",
|
Direction = RightToLeft ? "rtl" : "ltr",
|
||||||
EditorMode = EditorMode == SbMarkdownEditorMode.Source ? "source" : "markdown",
|
EditorMode = EditorMode == SbMarkdownEditorMode.Source ? "source" : "markdown",
|
||||||
|
SourceLanguage = SourceLanguage,
|
||||||
EnablePreview = EnablePreview && EditorMode != SbMarkdownEditorMode.Source,
|
EnablePreview = EnablePreview && EditorMode != SbMarkdownEditorMode.Source,
|
||||||
EnableMermaid = EnableMermaid,
|
EnableMermaid = EnableMermaid,
|
||||||
EnableHighlight = EnableHighlight,
|
EnableHighlight = EnableHighlight,
|
||||||
@@ -177,14 +210,23 @@ public partial class SbMarkdownEditor : ComponentBase, IAsyncDisposable
|
|||||||
IsReady = false;
|
IsReady = false;
|
||||||
_useFallback = false;
|
_useFallback = false;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
await LoadToolbarItemsAsync();
|
||||||
await InitializeEditorAsync();
|
await InitializeEditorAsync();
|
||||||
_lastRenderedDiffMode = IsDiffReview;
|
_lastRenderedDiffMode = IsDiffReview;
|
||||||
_lastRenderedValue = Value;
|
_lastRenderedValue = Value;
|
||||||
_lastRenderedOriginal = OriginalValue;
|
_lastRenderedOriginal = OriginalValue;
|
||||||
_lastRenderedSuggested = SuggestedValue;
|
_lastRenderedSuggested = SuggestedValue;
|
||||||
|
_lastEditorMode = EditorMode;
|
||||||
|
_lastSourceLanguage = SourceLanguage;
|
||||||
|
_lastIncludeDefaultToolbarItems = IncludeDefaultToolbarItems;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetEditorId() =>
|
||||||
|
string.Equals(SourceLanguage, "html", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? $"{_elementId}-html"
|
||||||
|
: _elementId;
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public async Task OnEditorChangeAsync(string value, string html)
|
public async Task OnEditorChangeAsync(string value, string html)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,9 +87,21 @@ public class SbMarkdownEditorInterop : IAsyncDisposable
|
|||||||
public async Task DestroyEditorAsync(string editorId)
|
public async Task DestroyEditorAsync(string editorId)
|
||||||
{
|
{
|
||||||
if (_module != null)
|
if (_module != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await _module.InvokeVoidAsync("destroyEditor", editorId);
|
await _module.InvokeVoidAsync("destroyEditor", editorId);
|
||||||
}
|
}
|
||||||
|
catch (JSDisconnectedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (JSException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> RenderMarkdownAsync(string content, SbMarkdownAssetOptions? options = null)
|
public async Task<string> RenderMarkdownAsync(string content, SbMarkdownAssetOptions? options = null)
|
||||||
@@ -135,6 +147,7 @@ public class SbMarkdownEditorInitOptions
|
|||||||
public bool ReadOnly { get; set; }
|
public bool ReadOnly { get; set; }
|
||||||
public string? Direction { get; set; }
|
public string? Direction { get; set; }
|
||||||
public string EditorMode { get; set; } = "markdown";
|
public string EditorMode { get; set; } = "markdown";
|
||||||
|
public string? SourceLanguage { get; set; }
|
||||||
public bool EnablePreview { get; set; } = true;
|
public bool EnablePreview { get; set; } = true;
|
||||||
public bool EnableMermaid { get; set; } = true;
|
public bool EnableMermaid { get; set; } = true;
|
||||||
public bool EnableHighlight { get; set; } = true;
|
public bool EnableHighlight { get; set; } = true;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ let easyMdeLoadPromise = null;
|
|||||||
let markedLoadPromise = null;
|
let markedLoadPromise = null;
|
||||||
let assetsLoadPromise = null;
|
let assetsLoadPromise = null;
|
||||||
let diffAssetsLoadPromise = null;
|
let diffAssetsLoadPromise = null;
|
||||||
|
let codeAssetsLoadPromise = null;
|
||||||
|
|
||||||
function getContentBasePath() {
|
function getContentBasePath() {
|
||||||
const scripts = document.querySelectorAll('script[src*="sufiblazor-markdown-editor.js"]');
|
const scripts = document.querySelectorAll('script[src*="sufiblazor-markdown-editor.js"]');
|
||||||
@@ -131,6 +132,27 @@ async function ensureDiffAssets() {
|
|||||||
return diffAssetsLoadPromise;
|
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() {
|
function configureMarked() {
|
||||||
if (!marked || window.__sbMarkedConfigured) {
|
if (!marked || window.__sbMarkedConfigured) {
|
||||||
return;
|
return;
|
||||||
@@ -268,7 +290,9 @@ export async function initEditor(textarea, dotNetRef, options) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sourceMode) {
|
if (sourceMode) {
|
||||||
|
await ensureCodeAssets();
|
||||||
|
} else {
|
||||||
await ensureAssets({
|
await ensureAssets({
|
||||||
enableMermaid: options.enableMermaid,
|
enableMermaid: options.enableMermaid,
|
||||||
enableHighlight: options.enableHighlight,
|
enableHighlight: options.enableHighlight,
|
||||||
@@ -307,6 +331,12 @@ export async function initEditor(textarea, dotNetRef, options) {
|
|||||||
|
|
||||||
const easyMDE = new EasyMDE(easyOptions);
|
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', () => {
|
easyMDE.codemirror.on('change', () => {
|
||||||
const value = easyMDE.value();
|
const value = easyMDE.value();
|
||||||
let html = '';
|
let html = '';
|
||||||
@@ -461,14 +491,17 @@ export function setPreview(editorId, show) {
|
|||||||
export function destroyEditor(editorId) {
|
export function destroyEditor(editorId) {
|
||||||
const diff = diffEditors.get(editorId);
|
const diff = diffEditors.get(editorId);
|
||||||
if (diff) {
|
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);
|
diffEditors.delete(editorId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const stored = editors.get(editorId);
|
const stored = editors.get(editorId);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
stored.easyMDE.toTextArea();
|
stored.easyMDE?.toTextArea?.();
|
||||||
stored.easyMDE.clearAutosavedValue?.();
|
stored.easyMDE?.clearAutosavedValue?.();
|
||||||
editors.delete(editorId);
|
editors.delete(editorId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user