Initial checklist
Affected packages and versions
@milkdown/crepe: 7.20.0
@milkdown/components: 7.20.0
prosemirror-view: 1.41.4
Link to runnable example
I don't have a small public reproduction yet. The issue was observed in an integration that uses Crepe image block plus a custom drag-and-drop upload plugin that inserts an image-block node after upload completes.
Steps to reproduce
- Create a Milkdown/Crepe editor with the
ImageBlock feature enabled.
- Add an existing image block with
src = A.
- Upload or programmatically insert a new image block with
src = B above the existing image block.
- Observe the newly inserted image block while image
B is being loaded/decoded.
The behavior is easiest to reproduce when the new image is inserted directly above an existing image block.
Expected behavior
The newly inserted image block should not display the existing image below it. It should either show an empty/loading state or show image B once it is ready.
Actual behavior
The new image block is inserted correctly, but it briefly displays the image from the existing image block below it (src = A) before updating to the new uploaded image (src = B).
This looks like a stale DOM/image presentation issue rather than an upload result issue. The document node has the correct new src, but the reused image DOM can visually show the previous bitmap until the new image is applied and loaded.
Suspected cause
The current image-block NodeView update implementation appears to accept any node of the same type:
update: (updatedNode) => {
if (updatedNode.type !== initialNode.type) return false
bindAttrs(updatedNode)
return true
}
When a new image block is inserted above an existing image block, ProseMirror's view diff can try to reuse a later existing NodeView for the new node via updateNextNode. Since the image-block NodeView returns true for any image-block regardless of src, an old NodeView for image A can be reused to represent the new node for image B.
bindAttrs(updatedNode) updates the Vue refs, but that does not guarantee that the underlying <img> presentation is immediately replaced. The old <img> element can keep showing the previously decoded image until Vue patches the DOM and the browser loads/decodes the new src.
Possible fix
The image-block NodeView could return false when the visual identity of the image changes, especially when src changes:
let currentNode = initialNode
update: (updatedNode) => {
if (updatedNode.type !== currentNode.type) return false
if (updatedNode.attrs.src !== currentNode.attrs.src) return false
bindAttrs(updatedNode)
currentNode = updatedNode
return true
}
This would force ProseMirror to recreate the NodeView when the image src changes, avoiding reuse of a DOM node that may still display the previous image bitmap.
Other attributes such as caption/ratio could still be updated in place, so normal caption editing and resizing should continue to work without recreating the NodeView.
Runtime
Chrome
OS
macOS
Build and bundle tools
Vite / React integration using Crepe
Initial checklist
Affected packages and versions
@milkdown/crepe:7.20.0@milkdown/components:7.20.0prosemirror-view:1.41.4Link to runnable example
I don't have a small public reproduction yet. The issue was observed in an integration that uses Crepe image block plus a custom drag-and-drop upload plugin that inserts an
image-blocknode after upload completes.Steps to reproduce
ImageBlockfeature enabled.src = A.src = Babove the existing image block.Bis being loaded/decoded.The behavior is easiest to reproduce when the new image is inserted directly above an existing image block.
Expected behavior
The newly inserted image block should not display the existing image below it. It should either show an empty/loading state or show image
Bonce it is ready.Actual behavior
The new image block is inserted correctly, but it briefly displays the image from the existing image block below it (
src = A) before updating to the new uploaded image (src = B).This looks like a stale DOM/image presentation issue rather than an upload result issue. The document node has the correct new
src, but the reused image DOM can visually show the previous bitmap until the new image is applied and loaded.Suspected cause
The current
image-blockNodeViewupdateimplementation appears to accept any node of the same type:When a new image block is inserted above an existing image block, ProseMirror's view diff can try to reuse a later existing NodeView for the new node via
updateNextNode. Since theimage-blockNodeView returnstruefor anyimage-blockregardless ofsrc, an old NodeView for imageAcan be reused to represent the new node for imageB.bindAttrs(updatedNode)updates the Vue refs, but that does not guarantee that the underlying<img>presentation is immediately replaced. The old<img>element can keep showing the previously decoded image until Vue patches the DOM and the browser loads/decodes the newsrc.Possible fix
The
image-blockNodeView could returnfalsewhen the visual identity of the image changes, especially whensrcchanges:This would force ProseMirror to recreate the NodeView when the image
srcchanges, avoiding reuse of a DOM node that may still display the previous image bitmap.Other attributes such as caption/ratio could still be updated in place, so normal caption editing and resizing should continue to work without recreating the NodeView.
Runtime
Chrome
OS
macOS
Build and bundle tools
Vite / React integration using Crepe