Question about moving block

Hi. I’m trying to implement a moving block feature similar to Notion’s, which allows users to move a block (or multiple blocks) up or down by pressing a keyboard shortcut. Check the video below for a quick demo of Notion’s implementation.

My current plan is to delete the whole selected block (by using tr.delete) and then insert the block in the appropriate position again (by using tr.insert). By doing that, I can’t use tr.mapping.map to map a position inside the selected block. This leads to two problems: preserving selection and preserving decoration.

Selection

ProseMirror already has two ways to move a block: Cut-and-Paste (Ctrl-X then Ctrl-V) and Drag-and-Drop. In both these two situations, users won’t expect their selection to be preserved. But when users move a block using keyboard shortcut, they want their selection to remain the same. Check the video above as an example.

Since I can’t simply use tr.mapping.map to map selection.from and selection.to, I would need to calculate the new selection in a more complex way. My current solution is to get the offset of the from/to against the selected node, and then set the selection based on the start position of the new inserted node.

This should be doable, at least in theory, but it’s still a little complex. Is there an easier way to preserve the selection?

Decoration

If I don’t get it wrong, a decoration requires mapping.map to get its new position after a transaction. Since mapping.map doesn’t work here. I will lose a decoration insert the selected node after moving this block. This issue is both for moving blocks with Drag-and-Drop and with keyboard shortcut.

I can use the official image upload example to demonstrate this issue. Notice that the image placeholder inside node B disappears after I move node B, which causes the image to fail to be inserted later.

Is there a way to preserve the decoration?

Related discussion

Indeed, because replace might need to do non-trivial things to make the block ‘fit’ in its new place, it’s not trivial to find its new offset. Iterating over the relevant range (possibly gotten from the transaction step maps) in the new document looking for a matching node might be the easiest way to find its new offset. ProseMirror position maps can’t represent content moving across other content, so indeed, moving decorations along with the block will also require some kind of custom solution.

Thank you for your response. I will ignore the decoration preservation issue for now since I haven’t come up with any solution. I will share some code for selection preservation once I finish it.

5 Likes

I’ve implemented some related feature in prosemirror-flat-list

Source code: prosemirror-flat-list/move-list.ts at 92f6d3d516bafad4a7a8b6ab7e3d9420ac19b2cc · ocavue/prosemirror-flat-list · GitHub

Video demo: CleanShot 2023-03-03 at 19.00.59 · CleanShot Cloud

1 Like