从零开始一起学Blazor WebAssembly 开发(6) 富文本框

今天用到富文本框了,ant-design提供的是vditor的控件。但是感觉功能有点少,而且扩展的不太好。先后对比了下vditor、quill、ckeditor

vditor 主要优势在markdown的编写上,我这项目用户群体不适合用markdown,quill是轻量级的,利于扩展,ckeditor重量级,功能很多。但是就是太重了。最终选择了这个quill。

网上走到一个结合quill 富文本框做的一个开源的组件,但是这个组件有几个缺点

1、没有实现双向绑定

2、图片上传采用的base64存储

于是对这个组件做了一次大手术,修改了不少地方。代码改动量几乎相当于重新做了遍了。

先看下效果

 

从零开始一起学Blazor WebAssembly 开发(5) 富文本框

 

接下来就给看下每个细节地方的代码

QuillEditor.razor

@inject IJSRuntime JSRuntime
    <div @ref="@ToolBarContainer">
        @if (ToolBarMode == "basic")
        {
            <span class="ql-formats">
                <select class="ql-font"></select>
                <select class="ql-size"></select>
            </span>
            <span class="ql-formats">
                <button class="ql-bold"></button>
                <button class="ql-italic"></button>
                <button class="ql-underline"></button>
                <button class="ql-strike"></button>
            </span>
            <span class="ql-formats">
                <select class="ql-color"></select>
                <select class="ql-background"></select>
            </span>
            <span class="ql-formats">
                <button class="ql-header" value="1"></button>
                <button class="ql-header" value="2"></button>
                <button class="ql-blockquote"></button>
                <button class="ql-code-block"></button>
            </span>
            <span class="ql-formats">
                <button class="ql-list" value="ordered"></button>
                <button class="ql-list" value="bullet"></button>
                <button class="ql-indent" value="-1"></button>
                <button class="ql-indent" value="+1"></button>
            </span>
            <span class="ql-formats">
                <button class="ql-link"></button>
                <button class="ql-image"></button>
            </span>
            <span class="ql-formats">
                <button class="ql-clean"></button>
            </span>
        }
        else if (ToolBarMode == "full")
        {
            <span class="ql-formats">
                <select class="ql-font"></select>
                <select class="ql-size"></select>
            </span>
            <span class="ql-formats">
                <button class="ql-bold"></button>
                <button class="ql-italic"></button>
                <button class="ql-underline"></button>
                <button class="ql-strike"></button>
            </span>
            <span class="ql-formats">
                <select class="ql-color"></select>
                <select class="ql-background"></select>
            </span>
            <span class="ql-formats">
                <button class="ql-script" value="sub"></button>
                <button class="ql-script" value="super"></button>
            </span>
            <span class="ql-formats">
                <button class="ql-header" value="1"></button>
                <button class="ql-header" value="2"></button>
                <button class="ql-blockquote"></button>
                <button class="ql-code-block"></button>
            </span>
            <span class="ql-formats">
                <button class="ql-list" value="ordered"></button>
                <button class="ql-list" value="bullet"></button>
                <button class="ql-indent" value="-1"></button>
                <button class="ql-indent" value="+1"></button>
            </span>
            <span class="ql-formats">
                <button class="ql-direction" value="rtl"></button>
                <select class="ql-align"></select>
            </span>
            <span class="ql-formats">
                <button class="ql-link"></button>
                <button class="ql-image"></button>
                <button class="ql-video"></button>
                <button class="ql-formula"></button>
            </span>
            <span class="ql-formats">
                <button class="ql-clean"></button>
            </span>
        }
        else if (ToolBarMode == "custom")
        {
            @ToolBarContent
        }
    </div>
    <div @ref="@ElementContentContainer" style="height:@ContainerHeight">
        @EditorContent
    </div>

QuillEditor.razor.cs

public partial class QuillEditor : ComponentBase
    {
        [Parameter]
        public string Value
        {
            get => _value??"";
            set
            {
                if (_value != value)
                {
                    _value = value;
                    _wattingUpdate = true;
                }
            }
        }
        private bool _wattingUpdate = false;
        private string _value;
        [Parameter] public EventCallback<string> ValueChanged { get; set; }
        [Inject] private QuillEditorService EditorService { get; set; }
        [Parameter]
        public RenderFragment EditorContent { get; set; }

        [Parameter]
        public RenderFragment ToolBarContent { get; set; }

        [Parameter]
        public bool ReadOnly { get; set; }= false;

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

        [Parameter]
        public string Theme { get; set; }= "snow";

        [Parameter]
        public string DebugLevel { get; set; }= "info";
        [Parameter]
        public string ToolBarMode { get; set; } = "basic";//full
        [Parameter]
        public string ContainerHeight { get; set; } = "600px";
        [Parameter]
        public string UploadImageUrl { get; set; }

        private ElementReference ElementContentContainer;
        private ElementReference ToolBarContainer;

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                await EditorService.CreateQuill(
                        ElementContentContainer,
                        ToolBarContainer,
                        ReadOnly,
                        Placeholder,
                        Theme,
                        DebugLevel,
                        UploadImageUrl,
                        this);
            }
        }

        protected override async Task OnParametersSetAsync()
        {
            await base.OnParametersSetAsync();
        }
        [JSInvokable]
        public void OnInput(string value)
        {
            _value = value;
            _wattingUpdate = false;

            if (ValueChanged.HasDelegate)
            {
                ValueChanged.InvokeAsync(value);
            }
        }
        public async Task<string> GetText()
        {
            return await EditorService.GetText(ElementContentContainer);
        }

        public async Task<string> GetHTML()
        {
            return await EditorService.GetHTML(ElementContentContainer);
        }

        public async Task<string> GetContent()
        {
            return await EditorService.GetContent(ElementContentContainer);
        }

        public async Task LoadContent(string Content)
        {
            var QuillDelta =
                await EditorService.LoadQuillContent(ElementContentContainer, Content);
        }

        public async Task LoadHTMLContent(string quillHTMLContent)
        {
            var QuillDelta =
                await EditorService.LoadQuillHTMLContent(ElementContentContainer, quillHTMLContent);
        }

        public async Task InsertImage(string ImageURL)
        {
            var QuillDelta =
                await EditorService.InsertQuillImage(ElementContentContainer, ImageURL);
        }

        public async Task EnableEditor(bool mode)
        {
            var QuillDelta =
                await EditorService.EnableQuillEditor(ElementContentContainer, mode);
        }
    }

QuillEditorService.cs

public class QuillEditorOption
    {
        public string UploadBaseUrl { get; set; }
    }
    public class QuillEditorService
    {
        private readonly IJSRuntime jsRuntime;
        private readonly QuillEditorOption _option;
        public QuillEditorService(IJSRuntime js, IOptions<QuillEditorOption> option)
        {
            jsRuntime = js;
            _option = option.Value;
        }
        
        public async ValueTask<object> CreateQuill(
                ElementReference quillElement,
                ElementReference toolbar,
                bool readOnly,
                string placeholder,
                string theme,
                string debugLevel,
                string uploadImageUrl,
                QuillEditor editor)
        {
            return await jsRuntime.InvokeAsync<object>(
                "QuillFunctions.createQuill",
                quillElement, toolbar, readOnly,
                placeholder, theme, debugLevel, DotNetObjectReference.Create(editor),editor.Value, _option.UploadBaseUrl+uploadImageUrl);
        }

        public async ValueTask<string> GetText(
            ElementReference quillElement)
        {
            return await jsRuntime.InvokeAsync<string>(
                "QuillFunctions.getQuillText",
                quillElement);
        }

        public async ValueTask<string> GetHTML(
            ElementReference quillElement)
        {
            return await jsRuntime.InvokeAsync<string>(
                "QuillFunctions.getQuillHTML",
                quillElement);
        }

        public async ValueTask<string> GetContent(
            ElementReference quillElement)
        {
            return await jsRuntime.InvokeAsync<string>(
                "QuillFunctions.getQuillContent",
                quillElement);
        }

        public async ValueTask<object> LoadQuillContent(
            ElementReference quillElement,
            string Content)
        {
            return await jsRuntime.InvokeAsync<object>(
                "QuillFunctions.loadQuillContent",
                quillElement, Content);
        }

        public async ValueTask<object> LoadQuillHTMLContent(
            ElementReference quillElement,
            string quillHTMLContent)
        {
            return await jsRuntime.InvokeAsync<object>(
                "QuillFunctions.loadQuillHTMLContent",
                quillElement, quillHTMLContent);
        }

        public async ValueTask<object> EnableQuillEditor(
                ElementReference quillElement,
                bool mode)
        {
            return await jsRuntime.InvokeAsync<object>(
                    "QuillFunctions.enableQuillEditor",
                    quillElement, mode);
        }

        public async ValueTask<object> InsertQuillImage(
                ElementReference quillElement,
                string imageURL)
        {
            return await jsRuntime.InvokeAsync<object>(
                "QuillFunctions.insertQuillImage",
                quillElement, imageURL);
        }
    }

blazorquill.js

(function () {
    window.QuillFunctions = {
        createQuill: function (
            quillElement, toolBar, readOnly,
            placeholder, theme, debugLevel,editor,value,uploadurl) {

            Quill.register('modules/blotFormatter', QuillBlotFormatter.default);

            var options = {
                debug: debugLevel,
                modules: {
                    toolbar: toolBar,
                    blotFormatter: {}
                },
                placeholder: placeholder,
                readOnly: readOnly,
                theme: theme
            };

            quillElement.__quill = new Quill(quillElement, options);
            quillElement.__quill.root.innerHTML = value;//初始化时设置初始值 HTML
            quillElement.__quill.on('text-change', function () {
                var cotent = quillElement.__quill.root.innerHTML;
                editor.invokeMethodAsync('OnInput', cotent);
            });
            let toolbar = quillElement.__quill.getModule('toolbar');
            toolbar.addHandler('image', () => {
                var fileInput = toolbar.container.querySelector('input.ql-image[type=file]');
                if (fileInput == null) {
                    fileInput = document.createElement('input');
                    fileInput.setAttribute('type', 'file');
                    fileInput.setAttribute('accept', 'image/*');
                    fileInput.classList.add('ql-image');
                    fileInput.addEventListener('change', function () {
                        if (fileInput.files != null && fileInput.files[0] != null) {
                            var formData = new FormData();
                            formData.append('file', fileInput.files[0]);
                            var xhr = new XMLHttpRequest();
                            // 调用xhr.open()函数
                            xhr.open("POST", uploadurl);
                            //调用xhr.send()函数,发送请求,这一步是异步操作
                            xhr.send(formData);
                            //监听 xhr.onreadystatechange  事件
                            xhr.onreadystatechange = function () {
                                if (xhr.readyState === 4 && xhr.status === 200) {
                                    var filejson = eval("(" + xhr.responseText + ")");
                                    var range = quillElement.__quill.getSelection();
                                   //插入图片
                                    quillElement.__quill.insertEmbed(range.index, 'image', filejson.fileUrl);
                                }
                            }
                        }
                    });
                    toolbar.container.appendChild(fileInput);
                }
                fileInput.click();
            });
        },
        getQuillContent: function (quillElement) {
            return JSON.stringify(quillElement.__quill.getContents());
        },
        getQuillText: function (quillElement) {
            return quillElement.__quill.getText();
        },
        getQuillHTML: function (quillElement) {
            return quillElement.__quill.root.innerHTML;
        },
        loadQuillContent: function (quillElement, quillContent) {
            content = JSON.parse(quillContent);
            return quillElement.__quill.setContents(content, 'api');
        },
        loadQuillHTMLContent: function (quillElement, quillHTMLContent) {
            return quillElement.__quill.root.innerHTML = quillHTMLContent;
        },
        enableQuillEditor: function (quillElement, mode) {
            quillElement.__quill.enable(mode);
        },
        insertQuillImage: function (quillElement, imageURL) {
            var Delta = Quill.import('delta');
            editorIndex = 0;

            if (quillElement.__quill.getSelection() !== null) {
                editorIndex = quillElement.__quill.getSelection().index;
            }

            return quillElement.__quill.updateContents(
                new Delta()
                    .retain(editorIndex)
                    .insert({ image: imageURL },
                        { alt: imageURL }));
        }
    };
})();

 

 
posted @ 2021-04-23 10:02  颗粒归仓  阅读(646)  评论(0编辑  收藏  举报