Quill: Spell check deletes words if there's color formatting on the line

Created on 30 Apr 2018  路  11Comments  路  Source: quilljs/quill

Steps for Reproduction

  1. Visit quilljs.com and go to the third example where you can set a font color
  2. Create a colored sentence with a typo, like "I love makig bugs"
  3. Right click "makig" to correct the spelling

Expected behavior:
The spelling should be corrected just like regular, non-formatted text

Actual behavior:
Characters are deleted. Sometimes it's just the word, sometimes it's everything before it

Platforms:
macOS High Sierra. Chrome and Safari (notably, not Firefox)

Version:
1.3.6

Related:

The issue _doesn't_ happen when you use classes instead of inline styles, which makes me think it's some kind of issue with normalized, rgb() values and quill uses hex. I tried forcing the Delta to use rgb() but it didn't help.

Most helpful comment

This seems to do everything correctly, I'll update it with any fixes:

const Inline = Quill.import('blots/inline');

class CustomColor extends Inline {
  constructor(domNode, value) {
    super(domNode, value);

    // Map <font> properties
    domNode.style.color = domNode.color;

    const span = this.replaceWith(new Inline(Inline.create()));

    span.children.forEach(child => {
      if (child.attributes) child.attributes.copy(span);
      if (child.unwrap) child.unwrap();
    });

    this.remove();

    return span;
  }
}

CustomColor.blotName = "customColor";
CustomColor.tagName = "FONT";

Quill.register(CustomColor, true);

All 11 comments

I've just encountered this as well. It seems to happen when there's any styling, not just colour.

Just here to say I'm experiencing this too. Gonna see if I can find a fix but this is a pretty troublesome bug.

Hey, guys... I found a solution. So here's the breakdown of what's happening:

Quill's color format puts out code like this:

<span style="color: rgba(0, 0, 0, 1);">Mispeled word</span>

The browser's spell-check functionality changes that to:

<font style="color: rgba(0, 0, 0, 1);">Mispelled word</font>

Quill doesn't seem to recognize that font tag as a valid color blot so it deletes it (although once could argue that's still a bug in Quill, deleting content should never happen unless done explicitly IMO).

But the fix is quite easy. I just created an additional blot that recognizes both the font tag and the color attribute, this way when Quill comes across it it knows what to do. I haven't had time to put together a demo but here's my code.

Then just import that into your file and use Quill.register. It doesn't even have to be the primary color formatter, it just has to be present.

Brilliant!! Awesome work @foleyatwork!

It needs a little more work though:

  • Probably attrName should be set to color so the quill document is standard ({color: "#aaa"})
  • The model gets correctly updated with the color but (in Safari) it still uses the text before the substitution
  • I'm getting some console errors followed by strange behavior where Quill seems to be going out of sync with the contentEditable
  • create never seems to be called

Good feedback, this could definitely use some improvement, I'm no expert in creating these blots yet. I'll work on refining this. If you've got any code improvements to share, please do.

This seems to do everything correctly, I'll update it with any fixes:

const Inline = Quill.import('blots/inline');

class CustomColor extends Inline {
  constructor(domNode, value) {
    super(domNode, value);

    // Map <font> properties
    domNode.style.color = domNode.color;

    const span = this.replaceWith(new Inline(Inline.create()));

    span.children.forEach(child => {
      if (child.attributes) child.attributes.copy(span);
      if (child.unwrap) child.unwrap();
    });

    this.remove();

    return span;
  }
}

CustomColor.blotName = "customColor";
CustomColor.tagName = "FONT";

Quill.register(CustomColor, true);

Nice one!

Is there a reason you didn't do the workaround of using classes instead of inline styles? I'm looking into it as it makes sanitising XSS easier anyway.

Mostly because we support a continuous range of colors in our app, so I didn't want to generate 256^3 classes. It should totally work if you only allow a set of colors though.

This works well, but unfortunately we have block formatting at the P-level that seem to get lost as soon as the code is hit. The block level is being set with the following:

 const qlFontSize = new Parchment.Attributor.Style('ppsize', 'font-size', { scope: Parchment.Scope.BLOCK });
    Quill.register(qlFontSize, true);

which will result in the markup to result in this ( so the font size is set block related in the paragraph)

<div class="ql-editor" data-gramm="false" contenteditable="false" data-placeholder="Double click to type here">
  <p style="font-family: PPSans; font-size: 11pt;">
     <span style="color: rgb(0, 0, 0);">123 Address</span>
  </p>
  <p style="font-family: PPSans; font-size: 10pt;">
     Paragraph Content with <span style="color: rgb(255, 0, 0);">tyypo</span>.
  </p>
</div>

As soon as the typo is corrected, the text shows fine yet it clears the style from the paragraph. This results in:

<div class="ql-editor" data-gramm="false" contenteditable="false" data-placeholder="Double click to type here">
  <p style="font-family: PPSans; font-size: 11pt;">
     <span style="color: rgb(0, 0, 0);">123 Address</span>
  </p>
  <p style>
     Paragraph Content with <span style="color: rgb(255, 0, 0);">typo</span>.
  </p>
</div>

Any help to avoid losing the block-level formatting is appreciated.

Here another version of @chearon that keeps font-family :

const Inline = Quill.import('blots/inline');

class CustomAttributes extends Inline {
  constructor(domNode, value) {
    super(domNode, value);

    const span = this.replaceWith(new Inline(Inline.create()));

    span.children.forEach(child => {
      if (child.attributes) child.attributes.copy(span);
      if (child.unwrap) child.unwrap();
    });

   // here we apply every attribute from <font> tag to span as a style
    Object.keys(domNode.attributes).forEach(function (key) {

        if (domNode.attributes[key].name != "style")
        {
          var value = domNode.attributes[key].value;
          var name = domNode.attributes[key].name;
          if (name == "face")
            name = "font-family";
          span.format(name, value);
        }
  });

    this.remove();

    return span;
  }
}

CustomAttributes.blotName = "customAttributes";
CustomAttributes.tagName = "FONT";

Quill.register(CustomAttributes, true);
Was this page helpful?
0 / 5 - 0 ratings