NoelOConnell/quill-image-uploader

Duplicate blots when uploading image

wooolfgang opened this issue · 6 comments

See here for demo
https://www.loom.com/share/6be7f1acb77147e8ba9e019607927c8c

When typing on the line of an uploading image blot, it creates duplicate loader blots.

came here to discuss the same issue

Ok so I looked at your video and my situation isn't quite as bad. I don't know what you're doing to get that result @wooolfgang but I don't have it.

I am just testing this out locally and so I had a dummy promise that immediately returned a canned URL (following code is coffeescript, hopefully you can translate)

    imageUpload:
      upload: (file) ->
        url = "https://upload.wikimedia.org/wikipedia/commons/4/47/PiLposterforWikipedia.jpg"
        new Promise (resolve, reject) -> resolve(url)

This did insert the dummy image into the editor but it didn't get rid of the loading spinner. I had to manually go and delete the spinner (it was inserted below the image).

To get rid of this extra loading spinner all I had to do was add a 1 ms delay:

    imageUpload:
      upload: (file) ->
        url = "https://upload.wikimedia.org/wikipedia/commons/4/47/PiLposterforWikipedia.jpg"
        new Promise (resolve, reject) ->
          setTimeout ->
            console.log(url)
            resolve(url)
          , 1

Not sure why this is necessary. I have a suspicion there is some race condition in the source - some asynchronous dependency that is not called in the correct order.

Maybe it has to do with the following code?

readAndUploadFile(file) {
    let isUploadReject = false;

    const fileReader = new FileReader();

    fileReader.addEventListener(
        "load",
        () => {
            if (!isUploadReject) {
                let base64ImageSrc = fileReader.result;
                this.insertBase64Image(base64ImageSrc);
            }
        },
        false
    );

    if (file) {
        fileReader.readAsDataURL(file);
    }

    this.options.upload(file).then(
        (imageUrl) => {
            this.insertToEditor(imageUrl);
        },
        (error) => {
            isUploadReject = true;
            this.removeBase64Image();
            console.warn(error);
        }
    );
}

Do the insertBase64Image and insertToEditor need to be called in a specific order? Maybe that order is not guaranteed because they're called from separate async flows?

Sorry for the late reply. @maxp-hover

The issue will happen if you add a more realistic delay (~200ms) to your mocked upload function. Then start pressing the "enter" key (or type random characters) on the current line where the image is being uploaded. You will see duplicate spinners being rendered as demoed in the above video.

Aside from this, you will also notice that change in y-coordinates of the currently uploading picture (moving it up or down) will not save that location once it finishes. The image will always render to the location where it was uploaded - not to its updated line

There's also some issues with handling multiple async uploads of images iirc.

I was able to create a decent solution to solve these issues above (for work).

The fixes are:

  • No weird blots/spinners when typing chaotically while the image is being uploaded
  • Handles multiple image uploads and render the uploaded image on the new location when the editor content is being changed

Hopefully by this weekend I can be able to create a PR request for the changes.

This plugin is doing it wrong. Some of the problems you are talking about can be easily solved by creating it as a block embed instead of an inline embed. Here's the salient bits:


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

export default class QuillLoadingImageBlot extends BlockEmbed {
...
}
Quill.register(QuillLoadingImageBlot);
Mmoks commented

I have the same issue.

I solved this by control the func execute order:

  constructor(
    private readonly quill: Quill,
    private readonly options: UploadImageOptions
  ) {
    // I add two var in order to sign if user deleted placeholder when uploading image
    this.autoDeleteImage = false
    this.shouldStopInsert = false

   // here watch text-change
    this.quill.on('text-change', this.handleTextChange)
  }
  handleTextChange(delta, oldContents, source) {
    // If placeholder deleted by func insertToEditor not user, just return
    if (this.autoDeleteImage) {
      this.autoDeleteImage = false
      this.shouldStopInsert = true
      return
    }

    if (source !== 'user') return

    const deleted = this.getImgUrls(this.quill.getContents().diff(oldContents))
    const shouldStopInsert = deleted.every((imgURL: string) =>
      imgURL.startsWith('data:')
    )
    if (deleted.length && shouldStopInsert) {
      const range = this.range

      this.shouldStopInsert = true

      const content = this.quill.getContents(range?.index, 2)

      let idx = 0
      content.forEach((data, index) => {
        if (
          data.hasOwnProperty('insert') &&
          data.hasOwnProperty('attributes') &&
          data.attributes?.imageBlot
        ) {
          idx = index
        }
      })
      range && this.quill.deleteText(range.index + idx, 1, 'user')
    }
  }

  getImgUrls(delta: any) {
    return delta.ops
      .filter((i: any) => i.insert && i.insert.image)
      .map((i: any) => i.insert.image)
  }