Skip to content
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
23 changes: 21 additions & 2 deletions src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace Tizzani.MudBlazor.HtmlEditor;

public sealed partial class MudHtmlEditor : IAsyncDisposable
{
private readonly MudHtmlEditorOptions _options = new();

private DotNetObjectReference<MudHtmlEditor>? _dotNetRef;
private IJSObjectReference? _quill;
private ElementReference _toolbar;
Expand All @@ -23,7 +25,7 @@ public sealed partial class MudHtmlEditor : IAsyncDisposable
public bool Outlined { get; set; } = true;

/// <summary>
/// The placeholder text to display when the editor has not content.
/// The placeholder text to display when the editor has no content.
/// </summary>
[Parameter]
public string Placeholder { get; set; } = "Tell your story...";
Expand Down Expand Up @@ -52,6 +54,18 @@ public sealed partial class MudHtmlEditor : IAsyncDisposable
[Parameter]
public EventCallback<string> TextChanged { get; set; }

/// <summary>
/// When true, ol elements containing li elements with data-list="bullet" will be replaced with ul elements.
/// Default value is true. Set to false to revert to previous behavior.
/// </summary>
[Obsolete("This parameter was added to preserve backwards compatibility, but will be removed in a future version.")]
[Parameter]
public bool ReplaceOrderedWithUnorderedListTag
{
get => _options.SanitizeHtml;
set => _options.SanitizeHtml = value;
}

/// <summary>
/// Whether or not the user can resize the editor. Default value is <see langword="true" />.
/// </summary>
Expand Down Expand Up @@ -114,7 +128,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, _options);

await SetHtml(Html);

Expand Down Expand Up @@ -152,4 +166,9 @@ async ValueTask IAsyncDisposable.DisposeAsync()
_dotNetRef?.Dispose();
_dotNetRef = null;
}
}

public sealed class MudHtmlEditorOptions
{
public bool SanitizeHtml { get; set; } = true;
}
90 changes: 71 additions & 19 deletions 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, options) {
var quill = new Quill(editorRef, {
modules: {
toolbar: {
Expand All @@ -27,35 +27,89 @@ export function createQuillInterop(dotNetRef, editorRef, toolbarRef, placeholder
placeholder: placeholder,
theme: 'snow'
});
return new MudQuillInterop(dotNetRef, quill, editorRef, toolbarRef);
return new MudQuillInterop(dotNetRef, quill, editorRef, toolbarRef, options);
}

export class MudQuillInterop {
/**
* @param {Quill} quill
* @param {Element} editorRef
* @param {Element} toolbarRef
*/
constructor(dotNetRef, quill, editorRef, toolbarRef) {

constructor(dotNetRef, quill, editorRef, toolbarRef, options) {
quill.getModule('toolbar').addHandler('hr', this.insertDividerHandler);
quill.on('text-change', this.textChangedHandler);
this.dotNetRef = dotNetRef;
this.quill = quill;
this.editorRef = editorRef;
this.toolbarRef = toolbarRef;
this.options = options;
}

getText = () => {
return this.quill.getText();
};

getHtml = () => {
return this.quill.root.innerHTML;
const html = this.quill.root.innerHTML;
return this.options.sanitizeHtml ? this.getSanitizedHtml(html) : html;
};

getSanitizedHtml = (html) => {

setHtml = (html) => {
this.quill.root.innerHTML = html;
}
// Parse the HTML into a DOM
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');

// Find all <ol> elements
doc.querySelectorAll('ol').forEach(ol => {

const hasBullets = Array.from(ol.querySelectorAll('li')).some(
li => li.getAttribute('data-list') === 'bullet'
);

if (hasBullets) {
// Create a new <ul> element
const ul = doc.createElement('ul');

// Move children from <ol> to <ul>
while (ol.firstChild) {
ul.appendChild(ol.firstChild);
}

// Replace <ol> with <ul>
ol.replaceWith(ul);
}
});

return doc.body.innerHTML;
};

getQuillHtml = (html) => {

// Parse the HTML into a DOM
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');

// Find all <ul> elements
doc.querySelectorAll('ul').forEach(ul => {

const hasBullets = Array.from(ul.querySelectorAll('li')).some(
li => li.getAttribute('data-list') === 'bullet'
);

if (hasBullets) {
// Create a new <ol> element
const ol = doc.createElement('ol');

// Move children from <ul> to <ol>
while (ul.firstChild) {
ol.appendChild(ul.firstChild);
}

// Replace <ul> with <ol>
ul.replaceWith(ol);
}
});

return doc.body.innerHTML;
};

insertDividerHandler = () => {
const range = this.quill.getSelection();
Expand All @@ -65,14 +119,12 @@ export class MudQuillInterop {
}
};

/**
*
* @param {Delta} delta
* @param {Delta} oldDelta
* @param {any} source
*/
setHtml = (html) => {
this.quill.root.innerHTML = this.options.sanitizeHtml ? this.getQuillHtml(html) : html;
}

textChangedHandler = (delta, oldDelta, source) => {
this.dotNetRef.invokeMethodAsync('HandleHtmlContentChanged', this.getHtml());
this.dotNetRef.invokeMethodAsync('HandleTextContentChanged', this.getText());
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<RepositoryUrl>https://github.com/erinnmclaughlin/MudBlazor.HtmlEditor</RepositoryUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Version>2.3.0</Version>
<Version>2.3.1</Version>
<Description>A customizable HTML editor component for MudBlazor, powered by QuillJS.</Description>
<Copyright>2024 Erin McLaughlin</Copyright>
<PackageProjectUrl>https://github.com/erinnmclaughlin/MudBlazor.HtmlEditor</PackageProjectUrl>
Expand Down