From c5db5ee02423f5c4a3fcbc5418f611d84521e7af Mon Sep 17 00:00:00 2001 From: Erin McLaughlin Date: Tue, 14 Oct 2025 22:58:41 -0400 Subject: [PATCH 1/5] fix issue related to rendering list tags --- .../MudHtmlEditor.razor.cs | 23 ++++- .../MudHtmlEditor.razor.js | 83 ++++++++++++++----- .../Tizzani.MudBlazor.HtmlEditor.csproj | 2 +- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs index 58aadcc..073c90d 100644 --- a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs +++ b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs @@ -5,6 +5,8 @@ namespace Tizzani.MudBlazor.HtmlEditor; public sealed partial class MudHtmlEditor : IAsyncDisposable { + private readonly MudHtmlEditorOptions _options = new(); + private DotNetObjectReference? _dotNetRef; private IJSObjectReference? _quill; private ElementReference _toolbar; @@ -23,7 +25,7 @@ public sealed partial class MudHtmlEditor : IAsyncDisposable public bool Outlined { get; set; } = true; /// - /// The placeholder text to display when the editor has not content. + /// The placeholder text to display when the editor has no content. /// [Parameter] public string Placeholder { get; set; } = "Tell your story..."; @@ -52,6 +54,18 @@ public sealed partial class MudHtmlEditor : IAsyncDisposable [Parameter] public EventCallback TextChanged { get; set; } + /// + /// 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. + /// + [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; + } + /// /// Whether or not the user can resize the editor. Default value is . /// @@ -114,7 +128,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) _dotNetRef = DotNetObjectReference.Create(this); await using var module = await JS.InvokeAsync("import", "./_content/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js"); - _quill = await module.InvokeAsync("createQuillInterop", _dotNetRef, _editor, _toolbar, Placeholder); + _quill = await module.InvokeAsync("createQuillInterop", _dotNetRef, _editor, _toolbar, Placeholder, _options); await SetHtml(Html); @@ -152,4 +166,9 @@ async ValueTask IAsyncDisposable.DisposeAsync() _dotNetRef?.Dispose(); _dotNetRef = null; } +} + +public sealed class MudHtmlEditorOptions +{ + public bool SanitizeHtml { get; set; } = true; } \ No newline at end of file diff --git a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js index 974d5b6..1859ca6 100644 --- a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js +++ b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js @@ -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: { @@ -27,22 +27,19 @@ 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 = () => { @@ -50,12 +47,62 @@ export class MudQuillInterop { }; 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
    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
      element + const ul = doc.createElement('ul'); + + // Move children from
        to
          + while (ol.firstChild) { + ul.appendChild(ol.firstChild); + } + + // Replace
            with
              + 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
                elements + doc.querySelectorAll('ul').forEach(ul => { + // Create a new
                  element + const ol = doc.createElement('ol'); + + // Move children from
                    to
                      + while (ul.firstChild) { + ol.appendChild(ul.firstChild); + } + + // Replace
                        with
                          + ul.replaceWith(ol); + }); + + return doc.body.innerHTML; + }; insertDividerHandler = () => { const range = this.quill.getSelection(); @@ -65,14 +112,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()); }; -} \ No newline at end of file +} diff --git a/src/Tizzani.MudBlazor.HtmlEditor/Tizzani.MudBlazor.HtmlEditor.csproj b/src/Tizzani.MudBlazor.HtmlEditor/Tizzani.MudBlazor.HtmlEditor.csproj index f462726..ea380c6 100644 --- a/src/Tizzani.MudBlazor.HtmlEditor/Tizzani.MudBlazor.HtmlEditor.csproj +++ b/src/Tizzani.MudBlazor.HtmlEditor/Tizzani.MudBlazor.HtmlEditor.csproj @@ -9,7 +9,7 @@ https://github.com/erinnmclaughlin/MudBlazor.HtmlEditor LICENSE README.md - 2.3.0 + 2.3.1 A customizable HTML editor component for MudBlazor, powered by QuillJS. 2024 Erin McLaughlin https://github.com/erinnmclaughlin/MudBlazor.HtmlEditor From fc8a0d0c44655de8b4b69046fbc34f3447863a6c Mon Sep 17 00:00:00 2001 From: Erin McLaughlin Date: Tue, 14 Oct 2025 23:01:07 -0400 Subject: [PATCH 2/5] Fix comment typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js index 1859ca6..56f5387 100644 --- a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js +++ b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js @@ -92,7 +92,7 @@ export class MudQuillInterop { // Create a new
                            element const ol = doc.createElement('ol'); - // Move children from
                              to
                                + // Move children from
                                  to
                                    while (ul.firstChild) { ol.appendChild(ul.firstChild); } From 9012b64998cf726f9697a0c5157aa8b0e9e92639 Mon Sep 17 00:00:00 2001 From: Erin McLaughlin Date: Tue, 14 Oct 2025 23:01:27 -0400 Subject: [PATCH 3/5] Fix comment typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js index 56f5387..952ebcc 100644 --- a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js +++ b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js @@ -97,7 +97,7 @@ export class MudQuillInterop { ol.appendChild(ul.firstChild); } - // Replace
                                      with
                                        + // Replace
                                          with
                                            ul.replaceWith(ol); }); From 99f2702a1271c02151d160b15ebcec1c4261a55f Mon Sep 17 00:00:00 2001 From: Erin McLaughlin Date: Tue, 14 Oct 2025 23:03:47 -0400 Subject: [PATCH 4/5] more typo fixes --- src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js index 952ebcc..1cdc0e2 100644 --- a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js +++ b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js @@ -89,7 +89,8 @@ export class MudQuillInterop { // Find all
                                              elements doc.querySelectorAll('ul').forEach(ul => { - // Create a new
                                                element + + // Create a new
                                                  element const ol = doc.createElement('ol'); // Move children from
                                                    to
                                                      From 277634b674c8bb6efde7bdb59562bca57d0a8459 Mon Sep 17 00:00:00 2001 From: Erin McLaughlin Date: Tue, 14 Oct 2025 23:05:16 -0400 Subject: [PATCH 5/5] be on the safe side; check if ul have li data-list="bullet" --- .../MudHtmlEditor.razor.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js index 1cdc0e2..34bb154 100644 --- a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js +++ b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js @@ -89,17 +89,23 @@ export class MudQuillInterop { // Find all
                                                        elements doc.querySelectorAll('ul').forEach(ul => { + + const hasBullets = Array.from(ul.querySelectorAll('li')).some( + li => li.getAttribute('data-list') === 'bullet' + ); - // Create a new
                                                          element - const ol = doc.createElement('ol'); + if (hasBullets) { + // Create a new
                                                            element + const ol = doc.createElement('ol'); - // Move children from
                                                              to
                                                                - while (ul.firstChild) { - ol.appendChild(ul.firstChild); - } + // Move children from
                                                                  to
                                                                    + while (ul.firstChild) { + ol.appendChild(ul.firstChild); + } - // Replace
                                                                      with
                                                                        - ul.replaceWith(ol); + // Replace
                                                                          with
                                                                            + ul.replaceWith(ol); + } }); return doc.body.innerHTML;