Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added MudHtmlPicker #19

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,54 @@ For more advanced customization, you can define your own toolbar options inside
</MudHtmlEditor>
```

#### Custom Handlers
As Quill expects a handler created for every class you can add in custom handlers through the CustomHandler parameter where CustomHandler is a Dictionary<string, string>:

```csharp
// classname is added to the dictionary without the leading ql- as that is inferred during Quill instanciation
private Dictionary<string, string> _customHandler_ = new()
{
{"classname", "console.log('hello world');"}
}
```

```razor
<MudHtmlEditor CustomHandler="@_customHandler">
<span class="ql-formats">
<button class="ql-classname" type="button"/>
</span>
</>
```

This would require a css class for ql-classname to display a nice button with icon or text etc as per the QuillJS documentation.

See the [QuillJS documentation](https://quilljs.com/docs/modules/toolbar/) for more information on customizing the toolbar.

## Migrating from v1.0 to v2.0
* Remove the `services.AddMudBlazorHtmlEditor();` call from your `Startup.cs` or `Program.cs` file.
* Remove the `<script src="_content/Tizzani.MudBlazor.HtmlEditor/HtmlEditor.js">` tag from the document body. The required JS is now included by default.

### Easy Custom Picker (originally designed for inserting tags)
Requires `<MudHtmlEditor>` to have valid CustomHandlers property and Dictionary<string, string> for Options property of `<MudHtmlPicker>`:

```csharp
private Dictionary<string, string> _customHandlers = new() {
{"custom", "const e=this.quill.getSelection(); this.quill.insertText(e.index, value);"}
};

private Dictionary<string, string> _options = new()
{
{ "{{ Tag1 }}", "Tag 1" },
{ "{{ Tag2 }}", "Tag 2" },
{ "{{ Tag3 }}", "Tag 3" }
};
```

```razor
<MudHtmlEditor @ref="_editor" @bind-Html="@_html" CustomHandlers="@_customHandlers">
<MudHtmlToolbarOptions />
<MudHtmlPicker Name="custom" Placeholder="Insert Tag ..." Options="_options" />
</MudHtmlEditor>
```

Name must be a value existing as the key in the _customHandlers dictionary.
16 changes: 15 additions & 1 deletion samples/BlazorWasm/Pages/EditorView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
<MudButton Color="Color.Primary" OnClick="SaveChanges" Size="Size.Small" StartIcon="@Icons.Material.Filled.Save" Variant="Variant.Filled">Save Changes</MudButton>
</MudStack>

<MudHtmlEditor @ref="_editor" @bind-Html="@_html" />
<MudHtmlEditor @ref="_editor" @bind-Html="@_html" CustomHandlers="@_customHandlers">
<MudHtmlToolbarOptions />
<MudHtmlPicker Name="custom" Placeholder="Insert Tag ..." Options="_options" />
</MudHtmlEditor>

@code {
private MudHtmlEditor _editor = default!;
Expand All @@ -19,6 +22,17 @@
[Parameter]
public EventCallback<string> OnSave { get; set; }

private Dictionary<string, string> _customHandlers = new() {
{"custom", "const e=this.quill.getSelection(); this.quill.insertText(e.index, value);"}
};

private Dictionary<string, string> _options = new()
{
{ "{{ Tag1 }}", "Tag 1" },
{ "{{ Tag2 }}", "Tag 2" },
{ "{{ Tag3 }}", "Tag 3" }
};

protected override void OnParametersSet()
{
_html = InitialHtml;
Expand Down
5 changes: 3 additions & 2 deletions src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public sealed partial class MudHtmlEditor : IAsyncDisposable
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object?>? UserAttributes { get; set; }

[Parameter]
public IDictionary<string, string>? CustomHandlers { get; set; }

/// <summary>
/// Clears the content of the editor.
Expand Down Expand Up @@ -114,8 +116,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
_dotNetRef = DotNetObjectReference.Create(this);

await using var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js");
_quill = await module.InvokeAsync<IJSObjectReference>("createQuillInterop", _dotNetRef, _editor, _toolbar, Placeholder);

_quill = await module.InvokeAsync<IJSObjectReference>("createQuillInterop", _dotNetRef, _editor, _toolbar, Placeholder, CustomHandlers);
await SetHtml(Html);

StateHasChanged();
Expand Down
11 changes: 10 additions & 1 deletion src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ try {
Quill.register('modules/blotFormatter', QuillBlotFormatter.default);
} catch { }

export function createQuillInterop(dotNetRef, editorRef, toolbarRef, placeholder) {
export function createQuillInterop(dotNetRef, editorRef, toolbarRef, placeholder, customHandlers) {
var quill = new Quill(editorRef, {
modules: {
toolbar: {
Expand All @@ -27,6 +27,15 @@ export function createQuillInterop(dotNetRef, editorRef, toolbarRef, placeholder
placeholder: placeholder,
theme: 'snow'
});

// Dynamically add handlers from customHandlers to the toolbar
const toolbar = quill.getModule('toolbar');
if (customHandlers) {
Object.keys(customHandlers).forEach(key => {
toolbar.addHandler(key, new Function('value', customHandlers[key]));
});
}

return new MudQuillInterop(dotNetRef, quill, editorRef, toolbarRef);
}

Expand Down
6 changes: 6 additions & 0 deletions src/Tizzani.MudBlazor.HtmlEditor/MudHtmlPicker.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<select class="ql-@Name">
@foreach (var opt in Options)
{
<option value="@opt.Key">@opt.Value</option>
}
</select>
61 changes: 61 additions & 0 deletions src/Tizzani.MudBlazor.HtmlEditor/MudHtmlPicker.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace Tizzani.MudBlazor.HtmlEditor
{
public partial class MudHtmlPicker
{
[Inject]
private IJSRuntime JS { get; set; } = default!;

[Parameter]
public string Name { get; set; } = default!;

[Parameter]
public Dictionary<string, string> Options { get; set; } = default!;

[Parameter]
public string Placeholder { get; set; } = "No placeholder set";

private string GeneratedScript => $@"
// Define a custom format
const {Name.FirstCharToUpper()}Format = Quill.import('blots/inline');
CustomFormat.blotName = '{Name}';
CustomFormat.tagName = 'SPAN';
Quill.register({Name.FirstCharToUpper()}Format, true);
";

protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var listof = Options.ToList();

var generatedCss = GenerateCss();
await JS.InvokeVoidAsync("eval", $"document.head.insertAdjacentHTML('beforeend', `<style>{generatedCss}</style>`);");
await JS.InvokeVoidAsync("eval", GeneratedScript);
}
}

private string GenerateCss()
{
string generatedCss = $@"
.ql-picker.ql-{Name} .ql-picker-label:before {{
content: '{Placeholder}';
width: 150px;
}}
";

foreach (var key in Options.Keys)
{
generatedCss += $@"
.ql-picker.ql-{Name} .ql-picker-item[data-value='{key}']:before {{
content: '{Options[key]}';
}}
";
}

return generatedCss;
}
}
}
15 changes: 15 additions & 0 deletions src/Tizzani.MudBlazor.HtmlEditor/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Tizzani.MudBlazor.HtmlEditor
{
public static class StringExtensions
{
public static string FirstCharToUpper(this string input)
{
switch (input)
{
case null: throw new ArgumentNullException(nameof(input));
case "": throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input));
default: return input[0].ToString().ToUpper() + input.Substring(1);
}
}
}
}