Create widget decorators on drag and drop

Hi, I couldn’t find a solution for this, so I thought a new topic is appropriate.

I’ve build an image upload plugin, which works well so far. The widget decorators get updated to show the upload progress and are removed when the image is fully uploaded or the upload fails. But if I drop multiple files, the decorators are created at the same cursor position (or index, as described in the guide). So as the progress is updated, the decorators will jump around the same position and won’t keep order (DecorationSet). Is there a solution to this problem?

Right now I’m experimenting with node decorators. But that means I have to add a node first, add the node decorator to that new node, style it in a different way and update or remove it after upload/failure. Is that a viable solution? And how do I handle the history in that case? If an upload fails for example, I don’t want the user to be able to get an empty image node on undo.

Alright, so the history is my last issue now. When I upload an image and try to undo it afterwards, I get an error.

  1. create an empty node as a placeholder for the image:

     node = editorSchema.nodes.image.create({
         src: "",
         name: filename,
         size: getSizeFromBytes(filesize)
    let tr =;
    let resolvedPos = tr.doc.resolve(pos);
    // don't allow dropping files into code editors
    if( === "code_block") {
        return -1;
    // handle dropping images into inline content
    if( === "image") {
         if(resolvedPos.depth && resolvedPos.parent && resolvedPos.parent.childCount > 0) {
              pos = resolvedPos.after(resolvedPos.depth);
       pos = insertPoint(tr.doc, pos, nodeType);
       resolvedPos = tr.doc.resolve(pos);
    tr.setSelection(new Selection(resolvedPos, resolvedPos));
  2. create a node decorator for that newly added image node:

    let resolvedPos = this.view.state.doc.resolve(pos);
    let after = pos+resolvedPos.nodeAfter.nodeSize;
    let decorator = Decoration.node(pos, after, {style: `width:${200+percentCompleted}px;`}, {id, filename, filesize});
    let tr =;
    this.decorators = this.decorators.add(tr.doc, [decorator]);
  3. while uploading, delete the decorator and create a new one with the progress

  4. when done uploading, delete the decorator and replace the node with a new node containing the src attribute:

     let node = editorSchema.nodes.image.create({
         src: this.getFileURL(uniqueName)
     let tr =;
     let selection = NodeSelection.create(tr.doc, pos);
     tr.replaceSelectionWith(node, tr.doc.resolve(pos));

So for this to work, I have to create multiple transactions. When I undo after the upload completes, I get this error:

    history.js:130 Uncaught ReferenceError: Cannot access 'mirrorPos' before initialization
at eval (history.js:130)
at Leaf.forEachInner (index.mjs:102)
at Leaf.forEach (index.mjs:55)
at Branch.remapping (history.js:128)
at Branch.popEvent (history.js:55)
at histTransaction (history.js:346)
at undo (history.js:430)
at Plugin.eval (keymap.js:90)
at eval (input.js:116)
at EditorView.someProp (index.js:242)

The image gets removed, but the placeholder node stays in place and I have to delete it manually. Interesting is that it works as intended (the image and the placeholder node get removed with a single undo) when I have a fast connection. Maybe the steps must be send in one go to the collaboration server? Or can I make this steps atomic in the history or ignore them?

Interesting. I can’t see anything specifically wrong with your code, but it also has too many moving parts to easily reproduce. Can you try to reduce the code that causes this to the most minimal shape that still triggers the issue? (Replacing your remote collaboration service with a, possibly intentionally slowed down, client-side stub.)

1 Like

Using prosemirror-history in an unrelated use case, I also ran into this error. Could it be due to this assignment statement?

I think what’s happening is the “true” branch errors, similar to

let test = true ? test = 'too soon' : 'not too soon'


let test = false ? test = 'too soon' : 'not too soon'

Maybe, I planned to look into this next weekend as we have launched our product yesterday :slight_smile: Reminds me to write about it here…

That seems pointless, but harmless. I’ve removed the double assignment in this patch.

1 Like

That seems to fix it in my case when I manually test that change locally. Thank you!