slab / quill

Quill is a modern WYSIWYG editor built for compatibility and extensibility
https://quilljs.com
BSD 3-Clause "New" or "Revised" License
43.65k stars 3.39k forks source link

How to create unique id on each block? #1681

Open akashkamboj opened 7 years ago

akashkamboj commented 7 years ago

I've tried a lot of things but I am unable to create unique id on each block whether it's paragraph, header, blockquote. Need this to create some interesting things.

I've tried using attributor approach but each time i am changing the block type it's getting changed. What is the optimal way to create these unique ids which can be preserved as well as an attribute to pass on delta? That everytime a block is created it's id doesn't change at all, whether the block is converted to header or blockquote.

jhchen commented 7 years ago

What's the use case and what is the attributor approach you already tried?

akashkamboj commented 7 years ago

@jhchen Thanks for answering. Well there are a few use cases:

  1. To create table of contents for all headers and navigating to the page it's needed.
  2. To create comments on each block, i need some reference point

also there are a few more ideas ...

It seems like for all of these each block needs some kind of reference point and in real-time collaboration you can't rely on character index at all. So if all blocks can have a unique id as soon as they start, it will be a lot better.

Well I have created the id attribute as like other attributes overriding formats method in all blots. And it's getting passed in the delta as an attribute as well. But the problem is that when the blot gets attached, the IDs are regenerated because i've tried putting the ID generation in constructor, attach methods. Not able to understand where should I put it else.

The other thing is that this functionality is so useful for anyone that it should be part of core or even Parchment, so for each block, embed, container you don't have to define again and again.

Does all this make sense?

akashkamboj commented 7 years ago

hi @jhchen. ok here's something that I have done, which is kinda working:

  1. Created an IdAttribute
import Parchment from 'parchment'

const IdAttribute = new Parchment.Attributor.Attribute('id', 'id', {
    scope: Parchment.Scope.BLOCK,
});

export default IdAttribute;
  1. defined attribute into quill:
Quill.register({
    'attributors/attribute/id': IdAttribute
}, true);

Quill.register({
    'formats/id': IdAttribute,
}, true);
  1. created a random ID on block level
class Block extends Parchment.Block {
    constructor(domNode) {
        super(domNode);
        domNode.setAttribute('id', uuid.v4());
        this.cache = {};
    }
    ....

Which is saving the id attribute, but here are a few problems/need suggestion:

  1. If you are typing, it's ok. But if you paste something the ID's will not create. So I have to modify clipboard.js as well to create the ID. maybe I can modify matchAttributor method and create ID's everytime and hopefully it will save. But is this the right way?

  2. I will also need this on Parchment.Embed, not just block. What is the approach that I should take in that case? can I define array in attribute scope. And does it needs to be defined in every block type, at least on core level? I wish it was part of Parchment.

  3. I have to test the real time collaboration yet, but I guess it should work fine.

jhchen commented 7 years ago

Whether or not something is useful depends on the problems and use cases it solves. Referencing a header or comment via table of contents or a url hash does make sense. The primary algorithm for realtime collaboration (operational transform) is actually index based so ids will not help here.

Do you need these ids to be persisted? Or just generate them for example from the header text?

akashkamboj commented 7 years ago

Yes They need to be persisted, because I will also need these for comments referencing as well. I've also seen one approach that you did is creating the IDs based on header text in the optimize method which you removed later on. I think that decision was right because the same heading can repeat twice.

I've done the IDs generation and passing as an attribute on block level and it's working fine in collaborations at least on paragraph level. But now there are a few problems:

  1. I also need these IDs generation on Container, Embed as well. But unfortunately this is not working there. For Container Blot I think because it's not inherting from FormatBlot.

  2. I've also modified my Headers to work as ContainerBlot. So Headers are now working as Header > HeaderItem just like List > ListItem. I needed that to create interesting designs on Header. And since Header is a Container. I am having a hard time to save these IDs.

Can you help me on this Container, Embed part. I am kinda getting crazy here :). Thanks.

roy-rahul commented 6 years ago

@akashkamboj did you get any solution. I'm in almost same situation. It would be very helpfull if someone point me to right direction.

sankalpsans commented 5 years ago

Has there been any update on this? Any prescribed way of going about it?

laurensiusadi commented 5 years ago

Has there been any update on this? Any prescribed way of going about it?

I don't know if this will help, but this is how I did it for my custom List Item

import Quill from 'quill'
import uuid from 'uuid'

let ListItem = Quill.import('formats/list/item')

class CustomListItem extends ListItem {
  static create() {
    const node = super.create()
    node.setAttribute('id', uuid.v4())
    return node
  }

  split(index, force = false) {
    if (force && (index === 0 || index >= this.length() - 1)) {
      let clone = this.clone()
      clone.domNode.id = uuid.v4()
      if (index === 0) {
        this.parent.insertBefore(clone, this)
        return this
      } else {
        this.parent.insertBefore(clone, this.next)
        return clone
      }
    } else {
      let next = super.split(index, force)
      next.domNode.id = uuid.v4()
      this.cache = {}
      return next
    }
  }
}

CustomListItem.blotName = 'list-item'
CustomListItem.tagName = 'LI'

export default CustomListItem