Blazored / TextEditor

Rich text editor for Blazor applications - Uses Quill JS
MIT License
276 stars 58 forks source link

[Feature Request/Bugfix] Stream Data to/from Quill #65

Open JaronrH opened 1 year ago

JaronrH commented 1 year ago

I ran into an issue where SignalR fails to send/receive data if the payload is over roughly 32kb. So, if you're trying to use something like LoadContent() or GetContent() with a lot of data, the SignalR connection will be dropped due to an error. I found the solution is to chunk the data using DotNetStreamReference and IJSStreamReference on the .NET side and an ArrayBuffer on the JS side.

For Example... .NET:

    internal static async ValueTask<string> GetContentAsync(
        IJSRuntime jsRuntime,
        ElementReference quillElement)
    {
        var dataReference = await jsRuntime.InvokeAsync<IJSStreamReference>("QuillFunctions.getQuillContent", quillElement);
        await using var dataReferenceStream = await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);
        var reader = new StreamReader(dataReferenceStream, Encoding.Unicode);
        return await reader.ReadToEndAsync();
    }

    internal static async ValueTask<bool> LoadQuillContentAsync(
        IJSRuntime jsRuntime,
        ElementReference quillElement,
        string content)
    {
        var byteArray = Encoding.Unicode.GetBytes(content);
        var stream = new MemoryStream(byteArray);
        using var streamRef = new DotNetStreamReference(stream: stream, leaveOpen: false);
            return await jsRuntime.InvokeAsync<bool>("QuillFunctions.loadQuillContent", quillElement, streamRef);
    }

JS:

       getQuillContent: async function (quillElement) {
            return str2ab(JSON.stringify(quillElement.__quill.getContents()));
        },
        loadQuillContent: async function (quillElement, quillContentStream) {
            const data = await quillContentStream.arrayBuffer();
            const quillContent = ab2str(data);
            content = JSON.parse(quillContent);
            return quillElement.__quill.setContents(content, 'api') != null;
        },

(Note the return was changed from returning the delta to a boolean -- returning the delta would need to be chunked or the same error would happen again!)

With JS Helper Functions:

    function ab2str(buf) {
        return String.fromCharCode.apply(null, new Uint16Array(buf));
    }

    function str2ab(str) {
        var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
        var bufView = new Uint16Array(buf);
        for (var i = 0, strLen = str.length; i < strLen; i++) {
            bufView[i] = str.charCodeAt(i);
        }
        return buf;
    }
ADefWebserver commented 1 year ago

I will consider looking at a PR if anyone wants to submit one.