In this new post, I will show you have to create a simple Markdown editor component for Blazor Assembly and Blazor Server.
The source code of this component with an example is on GitHub. If you are looking for more examples of components, here some more links:
- Using Chart.Js With Blazor
- Create An Accordion Component With Blazor
- Create a Blazor component for Quill
- Segment control for Blazor
- Tabs control for Blazor
In January 2022 I completely rewrite this component. Now, the Markdown Editor for Blazor is a very powerful and complete component with upload images.
The final result of this component in an application is like the following screenshots. In the Write tab, you type your text in Markdown format. When you click on the Preview tab, you have the text in HTML.
The code
So, as usual I have to create a new project for the component and create a new Razor page and its name is MarkdownEditor.razor
. The HTML for this page is the following code
@using System.Linq.Expressions
<div id="markdownEditor">
<ul class="nav nav-tabs mb-3" id="editorTabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link @(isWriteActive ? "active" : "")" id="editor-tab"
data-toggle="tab" href="#editor" role="tab"
aria-controls="editor" aria-selected="true" @onclick:preventDefault
@onclick="() => HandleWriteClick()">Write</a>
</li>
<li class="nav-item @(isWriteActive ? "" : "active")" role="presentation">
<a class="nav-link" id="preview-tab" data-toggle="tab" href="#preview" role="tab"
aria-controls="preview" aria-selected="false" @onclick:preventDefault
@onclick="() => HandlePreviewClick()">Preview</a>
</li>
@if (EnableToolbar)
{
<li class="nav-item ml-auto">
<button class="btn btn-sm btn-secondary" @onclick:preventDefault
@onclick="() => HandleBoldClick()"><i class="fas fa-bold"></i></button>
<button class="btn btn-sm btn-secondary" @onclick:preventDefault
@onclick="() => HandleItalicClick()"><i class="fas fa-italic"></i></button>
<button class="btn btn-sm btn-secondary" @onclick:preventDefault
@onclick="() => HandleListClick()"><i class="fas fa-list"></i></button>
</li>
}
</ul>
<div class="tab-content" id="editorTabContent">
@if (isWriteActive)
{
<div class="tab-pane fade show active" id="editor" role="tabpanel"
aria-labelledby="editor-tab">
<textarea id="@id" value="@Value" @oninput="HandleInput"
class="@_fieldCssClasses form-control" rows="@_rows"></textarea>
<span class="text-muted">Learn more about MarkDown
<a href="" @onclick:preventDefault @onclick="() => HandleHelpClick()">here.</a>
</span>
@if (showHelp)
{
<div class="alert alert-info" role="alert">
<MarkdownHelp />
<button type="button" class="btn btn-sm btn-secondary" @onclick:preventDefault
@onclick="() => HandleCloseHelpClick()">Close Help</button>
</div>
}
</div>
}
else
{
<div class="tab-pane fade show active" id="editor" role="tabpanel"
aria-labelledby="editor-tab">
@((MarkupString)_previewText)
</div>
}
</div>
</div>
Then, the code section in the page is:
@code {
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public Expression<Func<string>> ValueExpression { get; set; }
[Parameter]
public bool EnableToolbar { get; set; } = true;
[Parameter]
public string id { get; set; }
[CascadingParameter]
private EditContext CascadedEditContext { get; set; }
private bool isWriteActive = true;
private string _previewText = "";
private int _rows = 6;
private bool showHelp = false;
private FieldIdentifier _fieldIdentifier;
private string _fieldCssClasses => CascadedEditContext?.FieldCssClass(_fieldIdentifier) ?? "";
protected override void OnInitialized()
{
_fieldIdentifier = FieldIdentifier.Create(ValueExpression);
}
private void CalculateSize(string value)
{
_rows = Math.Max(value.Split('\n').Length, value.Split('\r').Length);
_rows = Math.Max(_rows, 6);
}
private void HandleHelpClick()
{
showHelp = true;
}
private void HandleCloseHelpClick()
{
showHelp = false;
}
private async Task HandleInput(ChangeEventArgs args)
{
CalculateSize(args.Value.ToString());
await ValueChanged.InvokeAsync(args.Value.ToString());
CascadedEditContext?.NotifyFieldChanged(_fieldIdentifier);
_previewText = MarkdownParser.Parse(args.Value.ToString());
}
private void UpdatePreview()
{
_previewText = MarkdownParser.Parse(Value.ToString());
}
private void HandleBoldClick()
{
Value = $"{Value} **(Bolded Text Here)**";
UpdatePreview();
}
private void HandleItalicClick()
{
Value = $"{Value} *(Italic Text Here)*";
UpdatePreview();
}
private void HandleListClick()
{
Value = $"{Value} \n - List Item";
UpdatePreview();
}
private void HandleWriteClick()
{
isWriteActive = true;
}
private void HandlePreviewClick()
{
isWriteActive = false;
}
}
MarkdownParser
internal static class MarkdownParser
{
internal static string Parse(string value)
{
if (!string.IsNullOrEmpty(value))
{
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley()
.UseAdvancedExtensions()
.Build();
return Markdown.ToHtml(value, pipeline);
}
return "";
}
}
Usage
Now, to convert Markdown in HTML, I’m adding Markdig from a NuGet package with
Install-Package Markdig
So, add the Editor to your _Imports.razor
@using PSC.Blazor.Components.MarkdownEditor
Then, inside of an EditForm
reference the editor component and bind it.
<EditForm OnValidSubmit="DoSave" Model="model">
<MarkdownEditor @bind-Value="model.Comments"/>
</EditForm>
The editor binds the markdown text, not parsed HTML. The toolbar is added by default. You can disable this by passing EnableToolbar="false"
into the component.
Wrap up
In conclusion, I created a component for a Markdown editor with Blazor. Please leave your comment below or in the forum.