javascriptparsingemojiquillrich-text-editor

In Quill, how to parse deltas and replace some of its content


After experimenting a lot with contenteditable elements, I finally chosed Quill to create a rich text message box on a chat app, because it is correctly handling selection, which appears to be a mess when dealing directly with a contenteditable element.

In my experiments I used to parse the innerHTML and replace some strings by their emoji unicode counterpart (eg: '<3': '❤️'), and used Autolinker library to replace found links by a link tag in my editable element.

google.com --> <a href="https://www.google.com">google.com</a>

I want to avoid Quill toolbar and replace automatically emojis and links.

So, I guess I have to replace editor's content at some place...

I'm really new to Quill, but I understand that it is working with a specific internal model called deltas, that allows operational transform (correct me if I'm wrong).

This assumption done, how would I check/parse these deltas and replace specific content in it in a way that preserves selection and caret position?

References:

https://quilljs.com/guides/designing-the-delta-format/

https://github.com/nadar/quill-delta-parser


Solution

  • You are correct that Quill uses Delta to describe its contents. To get the current Deltas, you can use quill.getContents, and to set the new Deltas you can use quill.setContents.

    To preserve the caret position you can use getSelection and setSelection.

    However, the problem is that the text-change event happens after the DOM updates. Currently there are no really built-in such as before-text-change. This similar Issue has more details about problems modifying contents during text-change event. One way is to wrap the updates in queueMicrotask.

    Here is a working example combining the methods mentioned above that replaces <3 to ❤️ on text-change. You can extend it to replace other emojis and links, though it might have problems as said in the issue.

    var quill = new Quill('#editor', {
      theme: 'snow'
    });
    
    quill.on('text-change', update);
    
    update(); //update for initial texts
    
    function update(delta, oldContents, source) {
      if (source === 'silent') return;
      const newContents = quill.getContents()
        .map((op) => {
          if (typeof op.insert === 'string') {
            op.insert = op.insert.replaceAll('<3', '❤️');
          }
          return op;
        });
    
      queueMicrotask(() => {
        //getSelection be queued after DOM update to get copy-paste position updates  
        const selection = quill.getSelection();
        quill.setContents(newContents, 'silent');
        //don't set caret position if the editor wasn't focused before
        if (selection)
          quill.setSelection(selection.index, 0, 'silent');
      });
    }
    <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
    <div id="editor">
      <p>Hello World!</p>
      <p>Some initial <strong>bold <3 </strong> text <3 <3</p>
      <p><br></p>
    </div>
    <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>