soccerloway / quill-better-table

Module for better table in Quill, more useful features are supported.
MIT License
319 stars 121 forks source link

Pasting multiple lines breaks the table #33

Open UnivocalHoneyBadger opened 4 years ago

UnivocalHoneyBadger commented 4 years ago

When copying multiple lines into the table it breaks. I think that due to the clipboard module, each line is seen as a delta insert which does not check against the table and simply inserts it in between.

Here's a GIF that shows what happened: 0VcWUYUNUu

Here's what the clipboard text/html data returned:

<html><body>
<!--StartFragment--><p>test</p><p>test</p><!--EndFragment-->
</body>
</html>

Here's what the clipboard text/plain data returned: test\ntest

Edit: This also breaks the showTableTools function

soccerloway commented 4 years ago

Yes, pasting in table should be processed specially. In my product, I forked quilljs and processed this in clipboard module(onCapturePaste). But there, I can not find a proper way to process.

UnivocalHoneyBadger commented 4 years ago

I agree it's quite tricky. What could work is adding a paste eventListener where you access the text\html and find all instances of <p> and replace them with something else. Maybe with the help of a clipboard matcher we could properly turn each line into cell divs.

How does this suggestion sound, and please correct me if I'm wrong: Find all instances of <p> and </p> in text/html data of the paste event, replace it with a tag like (for example) <celldiv>.

Then by using a matcher you let the clipboard itself process it for you into the element you want. Here's the documentation about matchers: https://quilljs.com/docs/modules/clipboard/#addmatcher

soccerloway commented 4 years ago

As far as I know, I think the matchers is used to handle that how quill convert DOM element to content, they are always some pure functions, it may not be suitable for this. I have already fixed this issue in my company project. As you said, adding a paste eventListener, processing pasted delta if current selection is an instance of TableCellLine.

Here is the code:

onCapturePaste(e) {
    if (e.defaultPrevented || !this.quill.isEnabled()) return;
    const range = this.quill.getSelection(true);

    // Process pasting in table-cell-line before
    const [thisLeaf] = this.quill.getLine(range.index)
    if (thisLeaf && thisLeaf.constructor.name === 'TableCellLine') {
      e.preventDefault();
      const html = e.clipboardData.getData('text/html');
      const text = e.clipboardData.getData('text/plain');
      const files = Array.from(e.clipboardData.files || []);
      if (!html && files.length > 0) {
        this.quill.uploader.upload(range, files);
      } else {
        this.onTableCellPaste(range, { html, text });
      }
      return;
    }

    // ............bypass quill origin code
}

onTableCellPaste is the function processing default converted delta:

onTableCellPaste (range, {text, html}) {
    const [line] = this.quill.getLine(range.index)
    const lineFormats = line.formats()
    const formats = this.quill.getFormat(range.index);
    let pastedDelta = this.convert({ text, html }, formats);

    pastedDelta = pastedDelta.reduce((newDelta, op) => {
      if (op.insert && typeof op.insert === 'string') {
        const lines = []
        let insertStr = op.insert
        let start = 0
        for (let i = 0; i < op.insert.length; i++) {
          if (insertStr.charAt(i) === '\n') {
            if (i === 0) {
              lines.push('\n')
            } else {
              lines.push(insertStr.substring(start, i))
              lines.push('\n')
            }
            start = i + 1
          }
        }

        const tailStr = insertStr.substring(start)
        if (tailStr) lines.push(tailStr)

        lines.forEach(text => {
          text === '\n'
          ? newDelta.insert('\n', extend(
              {},
              op.attributes,
              lineFormats
            ))
          : newDelta.insert(text, op.attributes)
        })
      } else {
        newDelta.insert(op.insert, op.attributes)
      }

      return newDelta
    }, new Delta())

    debug.log('onTableCellPaste', pastedDelta, { text, html });
    const delta = new Delta()
      .retain(range.index)
      .delete(range.length)
      .concat(pastedDelta);
    this.quill.updateContents(delta, Quill.sources.USER);
    this.quill.setSelection(
      delta.length() - range.length,
      Quill.sources.SILENT,
    );
    this.quill.scrollIntoView();
  }
soccerloway commented 4 years ago

The delta of multiple lines is merged in only one insertion:

{
  insert: first line \n second line \n third line,
  attributes: { foo: foo }
}

It caused the problem. Back to quill-better-table module, I have not found a proper way to fix it. If I add a new paste eventListener, I need to make sure that the new listener must execute before the old one. Maybe there is a better way to fix it? I will try adding a new paste eventListener first, if you have some other better way, help me please~:)

NasirTaha commented 3 months ago

Does any body has any fix or work-around for this issue?