Integrating the Quill Editor

I’m looking to incorporate the Quill editor into my 1.1.0.3 Meteor application.

Specifically I have a messaging template that could use more advanced editor, more than the simple textarea control I’m using now, but I’m having a little trouble getting started.

I’m using a Quill Meteor wrapper to Quill Editor v0.19.14, found here: GitHub - themeteorites/quilljs: Quill.js wrapped for Meteor

This is what I think needs to be in place to get things working:

The main messaging template:

<template name="messages_detail">
   <textarea id="message" rows="3"></textarea>
   <button type="submit" id="submit-message" class="btn-submit">Submit</button>
</template>

I would like to replace the textarea, I currently use for message input, with Quill.

Would I build a sub template like the following:

<template name="quill_editor">
  <div id="editor-container">
    <div class="quill-wrapper">
      <div id="full-toolbar" class="toolbar ql-toolbar ql-snow">
        <span class="ql-format-group">
          <span title="Bold" class="ql-format-button ql-bold"></span>
          <span class="ql-format-separator"></span>
          <span title="Italic" class="ql-format-button ql-italic"></span>
          <span class="ql-format-separator"></span>
          <span title="Underline" class="ql-format-button ql-underline"></span>
          <span class="ql-format-separator"></span>
          <span title="Strikethrough" class="ql-format-button ql-strike"></span>
        </span>
        <span class="ql-format-group">
          <span title="Text Color" class="ql-color ql-picker ql-color-picker">
          </span>
          <span class="ql-format-separator"></span>
          <span title="Background Color" class="ql-background ql-picker ql-color-picker">
            <span class="ql-picker-label"></span>
            <span class="ql-picker-options">
              <span data-value="rgb(0, 0, 0)" class="ql-picker-item ql-primary-color" style="background-color: rgb(0, 0, 0);"></span>
              <span data-value="rgb(230, 0, 0)" class="ql-picker-item ql-primary-color" style="background-color: rgb(230, 0, 0);"></span>
              <span data-value="rgb(255, 153, 0)" class="ql-picker-item ql-primary-color" style="background-color: rgb(255, 153, 0);"></span>
              <span data-value="rgb(255, 255, 0)" class="ql-picker-item ql-primary-color" style="background-color: rgb(255, 255, 0);"></span>
              <span data-value="rgb(0, 138, 0)" class="ql-picker-item ql-primary-color" style="background-color: rgb(0, 138, 0);"></span>
              <span data-value="rgb(0, 102, 204)" class="ql-picker-item ql-primary-color" style="background-color: rgb(0, 102, 204);"></span>
              <span data-value="rgb(153, 51, 255)" class="ql-picker-item ql-primary-color" style="background-color: rgb(153, 51, 255);"></span>
              <span data-value="rgb(255, 255, 255)" class="ql-picker-item ql-selected" style="background-color: rgb(255, 255, 255);"></span>
              <span data-value="rgb(250, 204, 204)" class="ql-picker-item" style="background-color: rgb(250, 204, 204);"></span>
              <span data-value="rgb(255, 235, 204)" class="ql-picker-item" style="background-color: rgb(255, 235, 204);"></span>
              <span data-value="rgb(255, 255, 204)" class="ql-picker-item" style="background-color: rgb(255, 255, 204);"></span>
              <span data-value="rgb(204, 232, 204)" class="ql-picker-item" style="background-color: rgb(204, 232, 204);"></span>
              <span data-value="rgb(204, 224, 245)" class="ql-picker-item" style="background-color: rgb(204, 224, 245);"></span>
              <span data-value="rgb(235, 214, 255)" class="ql-picker-item" style="background-color: rgb(235, 214, 255);"></span>
              <span data-value="rgb(187, 187, 187)" class="ql-picker-item" style="background-color: rgb(187, 187, 187);"></span>
              <span data-value="rgb(240, 102, 102)" class="ql-picker-item" style="background-color: rgb(240, 102, 102);"></span>
              <span data-value="rgb(255, 194, 102)" class="ql-picker-item" style="background-color: rgb(255, 194, 102);"></span>
              <span data-value="rgb(255, 255, 102)" class="ql-picker-item" style="background-color: rgb(255, 255, 102);"></span>
              <span data-value="rgb(102, 185, 102)" class="ql-picker-item" style="background-color: rgb(102, 185, 102);"></span>
              <span data-value="rgb(102, 163, 224)" class="ql-picker-item" style="background-color: rgb(102, 163, 224);"></span>
              <span data-value="rgb(194, 133, 255)" class="ql-picker-item" style="background-color: rgb(194, 133, 255);"></span>
              <span data-value="rgb(136, 136, 136)" class="ql-picker-item" style="background-color: rgb(136, 136, 136);"></span>
              <span data-value="rgb(161, 0, 0)" class="ql-picker-item" style="background-color: rgb(161, 0, 0);"></span>
              <span data-value="rgb(178, 107, 0)" class="ql-picker-item" style="background-color: rgb(178, 107, 0);"></span>
              <span data-value="rgb(178, 178, 0)" class="ql-picker-item" style="background-color: rgb(178, 178, 0);"></span>
              <span data-value="rgb(0, 97, 0)" class="ql-picker-item" style="background-color: rgb(0, 97, 0);"></span>
              <span data-value="rgb(0, 71, 178)" class="ql-picker-item" style="background-color: rgb(0, 71, 178);"></span>
              <span data-value="rgb(107, 36, 178)" class="ql-picker-item" style="background-color: rgb(107, 36, 178);"></span>
              <span data-value="rgb(68, 68, 68)" class="ql-picker-item" style="background-color: rgb(68, 68, 68);"></span>
              <span data-value="rgb(92, 0, 0)" class="ql-picker-item" style="background-color: rgb(92, 0, 0);"></span>
              <span data-value="rgb(102, 61, 0)" class="ql-picker-item" style="background-color: rgb(102, 61, 0);"></span>
              <span data-value="rgb(102, 102, 0)" class="ql-picker-item" style="background-color: rgb(102, 102, 0);"></span>
              <span data-value="rgb(0, 55, 0)" class="ql-picker-item" style="background-color: rgb(0, 55, 0);"></span>
              <span data-value="rgb(0, 41, 102)" class="ql-picker-item" style="background-color: rgb(0, 41, 102);"></span>
              <span data-value="rgb(61, 20, 102)" class="ql-picker-item" style="background-color: rgb(61, 20, 102);"></span>
            </span>
          </span>
        </span>
        <span class="ql-format-group">
          <span title="Link" class="ql-format-button ql-link"></span>
        </span>
      </div>
      <div id="full-editor" class="editor ql-container ql-snow">
        <div class="ql-multi-cursor">
          <span class="cursor hidden" style="top: 218px; left: 277px; height: 15px;">
            <span class="cursor-flag">
              <span class="cursor-triangle top" style="border-bottom-color: rgba(255, 153, 51, 0.901961);"></span>
              <span class="cursor-name" style="background-color: rgba(255, 153, 51, 0.901961);">Gandalf</span>
              <span class="cursor-triangle bottom" style="border-top-color: rgba(255, 153, 51, 0.901961);"></span>
            </span>
            <span class="cursor-caret" style="background-color: rgba(255, 153, 51, 0.901961);"></span>
          </span>
        </div>
        <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">
          <div>
            <span style="font-size: 18px;">One Ring to Rule Them All</span>
          </div>
          <div><a href="http://en.wikipedia.org/wiki/One_Ring">http://en.wikipedia.org/wiki/One_Ring</a></div>
          <div>
            <br>
          </div>
          <div>Three Rings for the <u>Elven-kings</u> under the sky,</div>
          <div>Seven for the <u>Dwarf-lords</u> in halls of stone,</div>
          <div>Nine for <u>Mortal Men</u>, doomed to die,</div>
          <div>One for the <u>Dark Lord</u> on his dark throne.</div>
          <div>
            <br>
          </div>
          <div>In the Land of Mordor where the Shadows lie.</div>
          <div>One Ring to <b>rule</b> them all, One Ring to <b>find</b> them,</div>
          <div>One Ring to <b>bring</b> them all and in the darkness <b>bind</b> them.</div>
          <div>In the Land of Mordor where the Shadows lie.</div>
        </div>
        <div class="ql-paste-manager" contenteditable="true"></div>
      </div>
    </div>
  </div>
</template>

And then in the quill_editor js file like so:

Template.quill_editor.onRendered(function () {
  var fullEditor;
  fullEditor = new Quill('#full-editor', {
    modules: {
      'authorship': {
        authorId: 'test', //Meteor.user().profile.user_name,
        enabled: true
      },
      'multi-cursor': true,
      'toolbar': {
        container: '#full-toolbar'
      },
      'link-tooltip': true
    },
    theme: 'snow'
  });

  // Add basic editor's author
  var authorship = fullEditor.getModule('authorship');
  authorship.addAuthor('gandalf', 'rgba(255,153,51,0.4)');

  // Add a cursor to represent basic editor's cursor
  var cursorManager = fullEditor.getModule('multi-cursor');
  cursorManager.setCursor('gandalf', fullEditor.getLength()-1, 'Gandalf', 'rgba(255,153,51,0.9)');

  // Sync basic editor's cursor location
  basicEditor.on('selection-change', function(range) {
    if (range) {
      cursorManager.moveCursor('gandalf', range.end);
    }
  });

  // Update basic editor's content with ours
  fullEditor.on('text-change', function(delta, source) {
    if (source === 'user') {
      basicEditor.updateContents(delta);
    }
  });

  // basicEditor needs authorship module to accept changes from fullEditor's authorship module
  basicEditor.addModule('authorship', {
    authorId: 'gandalf',
    color: 'rgba(255,153,51,0.4)'
  });

  // Update our content with basic editor's
  basicEditor.on('text-change', function(delta, source) {
    if (source === 'user') {
      fullEditor.updateContents(delta);
    }
  });

  return fullEditor;
});

And now change the main template to incorporate Quill like this:

<template name="messages_detail">
  {{> quill_editor}}
   <button type="submit" id="submit-message" class="btn-submit">Submit</button>
</template>

For some reason the drop downs do not work right, they look like this so far:

See how the drop down Text and Background color are blank – it didn’t matter if I have the dropdown select options in the HTML or not, nothing would show, so in the first case I took it out and in the second case I left it in to show no difference.


I’m looking for this look and these controls in the end:

Am I on the right track? There are no really good examples with Meteor integration that I can find.

what about including in your quill-editor template:

<div class="basic-wrapper">
    <div class="toolbar-container">
        <button class="ql-bold">Bold</button>    
    </div>
    <div class="editor-container">
        <div>Some initial <b>bold</b> text</div>
    </div>
</div>

or try to remove basic-wrapper references from template and from onRendered

1 Like

Thanks @vioan, I missed that.

If you want the full editor, check the Full Example here

Thanks @vioan, this helped a lot – but I still can’t get the dropdowns to work properly.

did you add the corresponding code for dropdown? for example:

<!-- Add font size dropdown -->
  <select class="ql-size">
    <option value="10px">Small</option>
    <option value="13px" selected>Normal</option>
    <option value="18px">Large</option>
    <option value="32px">Huge</option>
  </select>

Ah – was using the wrong dropdown formatting – works now – thank you!

One thing, it seems Quill has a set of predefined classes, such as Text Color that work out of the box right?

for classes have a look at their GitHub repo

1 Like

Do you typically just save off the entire HTML beneath #full-editor ‘div’ into a field in Mongo? Then how do you reconstitute the HTML into a ‘div’ container (for example), keeping the formatting?

usually you have to clean the code: see html sanitizer or html purifier. Purifier is working on client and server side, and is what I use. they also have packages on atmospherejs.

1 Like

Awesome, Purifier looks great, I’ll work on integrating this into my workflow.

@vioan so after your ‘clean’ the HTML code, do you actually save some or all of the HMTL in a field in Mongo in order to keep the formatting – you almost have to right? How do you get the formatted edited text to display correctly after coming back from the DB?

You have to clean the html code from untrusted users in order to avoid cross-site scripting (XSS) attacks, and insert it in mongo. And then, in your template you can have an empty DIV where you want to show your html code and for example you can use

$('#your-div-id').html(html-from-mongo)

see this example. There are other options as well, but that one should work.

Nice. It seems like i can use {{{message}}} instead of {{message}} in order for Blaze to apply any HTML formatting coming from Mongo.

That means I just need to save the HTML from Quill properly. @vioan, can I ask, what exactly do you extract out of your HTML Quill full-editor ‘div’ and save off to Mongo?

Haha – we posted these at the same time.

exactly, {{{ }}} will let you insert html code

check their API to see how to get the text/html. example :smile:

I am not able to help you more right now, I am on my way to holiday and posting/searching from tablet is not what I really want :smile:

Haha – thanks for everything – you got me really far anyway!

I got this almost working…

With a Quill Editor that looks like this:

Outputs to raw outer HTML like so:

<div class="ql-editor authorship" id="ql-editor-1" contenteditable="true">
    <div class="ql-multi-cursor"><span class="cursor hidden"><span class="cursor-flag"><span class="cursor-name" style="background-color: rgba(255, 153, 51, 0.901961);">Gandalf</span></span>
        </span>
    </div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true"><span style="font-size: 18px;">One Ring to Rule Them All</span>
    </div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true"><a href="http://en.wikipedia.org/wiki/One_Ring">http://en.wikipedia.org/wiki/One_Ring</a>
    </div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">
        <br>
    </div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">Three Rings for the <u>Elven-kings</u> under the sky,</div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">Seven for the <u>Dwarf-lords</u> in halls of stone,</div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">Nine for <u>Mortal Men</u>, doomed to die,</div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">One for the <u>Dark Lord</u> on his dark throne.</div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">
        <br>
    </div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">In the Land of Mordor where the Shadows lie.</div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">One Ring to <b>rule</b> them all, One Ring to <b>find</b> them,</div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">One Ring to <b>bring</b> them all and in the darkness <b>bind</b> them.</div>
    <div class="ql-editor authorship" id="ql-editor-2" contenteditable="true">In the Land of Mordor where the Shadows lie.</div>
    <div class="ql-paste-manager" contenteditable="true">
        <br>
    </div>
</div>

This is the output after using the Purifier:

<span class="cursor hidden">
    <span class="cursor-flag">
        <span class="cursor-name" style="background-color: rgba(255, 153, 51, 0.901961);">
            Gandalf
        </span>
</span>
</span>
<span style="font-size: 18px;">
    One Ring to Rule Them All
</span>
<a href="http://en.wikipedia.org/wiki/One_Ring">
    http://en.wikipedia.org/wiki/One_Ring
</a>
<br/>Three Rings for the
<u>
    Elven-kings
</u> under the sky,Seven for the
<u>
    Dwarf-lords
</u> in halls of stone,Nine for
<u>
    Mortal Men
</u> , doomed to die,One for the
<u>
    Dark Lord
</u> on his dark throne.
<br/>In the Land of Mordor where the Shadows lie.One Ring to <b>rule</b> them all, One Ring to <b>find</b> them,One Ring to <b>bring</b> them all and in the darkness <b>bind</b> them.In the Land of Mordor where the Shadows lie.
<br/>

I then save this to Mongo, and get the value and add it as a message sent. The output in a {{{message}}} tag looks like so:


As you can see there are no returns.

This is a beautiful editor, I’d be interested in using this over summernote. Any advice from your trials so far?

You probably didn’t notice that Quill has a rather nice API that would give you clean HTML back if you asked for it nicely :wink:

No need for additional tools that clean up html for you. It’s all there. With newlines, too.