nozer/quill-delta-to-html

Custom blot fails to be recognized as blockGroup in `converter.getGroupedOps()`

laukaichung opened this issue · 4 comments

Here's a custom image blot "imageBlock" that is an extension of BlockEmbed. This is not a replacement of the default "image" blot.

import React, { Component } from 'react';
import ReactQuill from 'react-quill';

// / Custom ImageBlot to add alt text to inline images / ///

const Quill = ReactQuill.Quill;
const BlockEmbed = Quill.import('blots/block/embed');

class ImageBlot extends BlockEmbed {
  static create(value) {
    const node = super.create();
    node.setAttribute('alt', value.alt);
    node.setAttribute('src', value.url);
    return node;
  }

  static value(node) {
    return {
      alt: node.getAttribute('alt'),
      url: node.getAttribute('src'),
    };
  }
}
ImageBlot.blotName = 'imageBlock';
ImageBlot.tagName = 'img';
ImageBlot.className = 'inline-img';
Quill.register(ImageBlot);

When I run the converter, this custom blot is recognized as InsertDataCustom but it falls into the inlineGroup group instead of blockGroup. Is there any problem with the custom blot above that makes it fail to be recognized as blockGroup?

    let converter = new QuillDeltaToHtmlConverter(html.ops);
    let groupedOps = converter.getGroupedOps();
    console.log(groupedOps)

I may have figured it out.
The custom image blot generates an op like this:

{"ops":[{"insert":{"customImageBlot":{"height":150,"width":150,"url":"https://xxx.cloudfront.net/image/i@Hy81U57G7.png"}}}

In order to be treated as a block, it must pass the check in the DeltaInsertOp.isContainerBlock() method .

   isContainerBlock() {
      var attrs = this.attributes;
      return !!(
         attrs.blockquote || attrs.list || attrs['code-block'] ||
         attrs.header || attrs.align || attrs.direction || attrs.indent);
   }

I must get the custom blot to store an attribute like {header:true} in the op object. What do you think?

nozer commented

I just pushed an update for this. It is tagged as v0.10.2. Now, if you like your custom blot to be treated as a block, add renderAsBlock: true in its attributes like:

attributes: { renderAsBlock: true}

Also, please note that after the version v0.10.0, I updated the way we import/require this library. See the readme under breaking changes.

nozer commented

P.S. When you get grouped ops via

let converter = new QuillDeltaToHtmlConverter(html.ops);
    let groupedOps = converter.getGroupedOps();
    console.log(groupedOps)

custom blots that are categorized as block will be of type BlotBlock not BlockGroup

This library is just getting better and better. Thanks to the recent updates, I can render delta into native components in react native! I'm also using it in node.js to extract temporary images from delta and upload them to s3.

Here's the updated custom image blot:

const Embed = Quill.import('blots/block/embed');

class ImageBlot extends Embed {

    static create(value: EditorMediaValues) {
        let {src, height, width} = value;
        const node = super.create();
        node.setAttribute('height', getSize(height));
        node.setAttribute('width', getSize(width));
        node.setAttribute('src', src);
        return node;
    }
    
    formats() {
        return {renderAsBlock:true};
    }

    static value(node) {
        return {
            src: node.getAttribute("src"),
            height: node.getAttribute("height"),
            width: node.getAttribute("width"),
        }
    }

}

ImageBlot.blotName = "imageblock";
ImageBlot.tagName = 'img';
Quill.register(ImageBlot);

This blot will generate an op like this:

{
  "ops": [
    {
      "insert": {
        "imageblock": {
          "src": "https://some.cloudfront.net/image/i@picture.png",
          "height": "150",
          "width": "150"
        }
      },
      "attributes": {
        "renderAsBlock": true
      }
    }
  ]
}

And yes, the type will be BlotBlock instead of BlockGroup, so I have to add one more case to the switch statement.

return groupedOps.map((group, key) => {
            switch (group.constructor) {
                case InlineGroup:
                    return rules.inlineGroup(group, key);
                case ListGroup:
                    return rules.listGroup(group, key);
                case BlockGroup:
                    return rules.blockGroup(group, key);
                case VideoItem:
                    return rules.videoItem(group, key);
                case BlotBlock:
                    return rules.blotBlock(group,key);
                default:
                    console.log(`Unknown instance`);
                    return;
            }
        })