@@ -97,8 +97,29 @@ class PasteStructuredContentEditorCommand extends EditCommand {
9797 return ;
9898 }
9999
100- final (upstreamNodeId, _) = _splitPasteParagraph (
101- executor, currentNodeWithSelection.id, (pastePosition.nodePosition as TextNodePosition ).offset);
100+ late final String upstreamNodeId;
101+ DocumentPosition ? caretPositionAfterPaste;
102+
103+ if (currentNodeWithSelection.text.isEmpty ||
104+ (pastePosition.nodePosition as TextNodePosition ).offset == currentNodeWithSelection.text.length) {
105+ // We're pasting into an empty node, or pasting at the very end of a non-empty `TextNode`.
106+ // We already know we can't combine the pasted content with this node. We'll paste below
107+ // this node.
108+ upstreamNodeId = currentNodeWithSelection.id;
109+ } else {
110+ // We're pasting into the middle of a non-empty text node. We already know we can't combine
111+ // the pasted content with this node. Split the selected node before pasting.
112+ final (splitUpstreamNodeId, splitDownstreamNodeId) = _splitPasteParagraph (
113+ executor, currentNodeWithSelection.id, (pastePosition.nodePosition as TextNodePosition ).offset);
114+ upstreamNodeId = splitUpstreamNodeId;
115+
116+ // Since we split a non-empty paragraph, we'll insert the caret at the start
117+ // of the 2nd half of the split text.
118+ caretPositionAfterPaste = DocumentPosition (
119+ nodeId: splitDownstreamNodeId,
120+ nodePosition: const TextNodePosition (offset: 0 ),
121+ );
122+ }
102123
103124 // Insert the pasted node after the split upstream node.
104125 document.insertNodeAfter (
@@ -108,17 +129,53 @@ class PasteStructuredContentEditorCommand extends EditCommand {
108129 executor.logChanges ([
109130 DocumentEdit (
110131 NodeInsertedEvent (pastedNode.id, document.getNodeIndexById (pastedNode.id)),
111- )
132+ ),
112133 ]);
113134
135+ // Maybe delete the original selected node, and maybe insert empty paragraph at end.
136+ if (currentNodeWithSelection.text.isEmpty) {
137+ // We pasted content below the selected node, but the selected node was empty.
138+ // As a UX policy, let's delete that empty paragraph because a user won't expect
139+ // it to stay around.
140+ document.deleteNode (currentNodeWithSelection.id);
141+ executor.logChanges ([
142+ DocumentEdit (
143+ NodeRemovedEvent (pastedNode.id, currentNodeWithSelection),
144+ ),
145+ ]);
146+
147+ if (pastedNode is ! TextNode ) {
148+ // The pasted content isn't text. It might be an image, table, etc. As a UX
149+ // policy, we insert an empty paragraph after the pasted content because users
150+ // typically expect to be able to start typing after pasting.
151+ final newNodeId = Editor .createNodeId ();
152+ document.insertNodeAfter (
153+ existingNodeId: pastedNode.id,
154+ newNode: ParagraphNode (id: newNodeId, text: AttributedText ()),
155+ );
156+ executor.logChanges ([
157+ DocumentEdit (
158+ NodeInsertedEvent (newNodeId, document.getNodeIndexById (newNodeId)),
159+ ),
160+ ]);
161+
162+ caretPositionAfterPaste = DocumentPosition (nodeId: newNodeId, nodePosition: const TextNodePosition (offset: 0 ));
163+ }
164+ }
165+
166+ // We didn't split a non-empty paragraph, and we didn't insert a new empty paragraph
167+ // at the end of the pasted content. Therefore, place the caret at the end of the pasted
168+ // content.
169+ caretPositionAfterPaste ?? = DocumentPosition (
170+ nodeId: pastedNode.id,
171+ nodePosition: pastedNode.endPosition,
172+ );
173+
114174 // Place the caret at the end of the pasted content.
115175 executor.executeCommand (
116176 ChangeSelectionCommand (
117177 DocumentSelection .collapsed (
118- position: DocumentPosition (
119- nodeId: pastedNode.id,
120- nodePosition: pastedNode.endPosition,
121- ),
178+ position: caretPositionAfterPaste,
122179 ),
123180 SelectionChangeType .insertContent,
124181 SelectionReason .userInteraction,
@@ -163,14 +220,16 @@ class PasteStructuredContentEditorCommand extends EditCommand {
163220
164221 // We've pasted the first new node. Remove it from the nodes to insert.
165222 nodesToInsert.removeAt (0 );
166- }
167- if (currentNodeWithSelection.text.length == 0 ) {
223+ } else if (currentNodeWithSelection.text.length == 0 ) {
168224 // The node with the selection is an empty text node. After we use that node's
169225 // position to insert other nodes, we want to delete that first node, as if the
170226 // pasted content replaced it.
171227 deleteInitiallySelectedNode = true ;
172228 }
173229
230+ // The caret position we want after the paste.
231+ DocumentPosition ? pasteEndPosition;
232+
174233 // (Possibly) merge or delete the downstream split node.
175234 if (nodesToInsert.isNotEmpty) {
176235 final lastPastedNode = nodesToInsert.last;
@@ -193,6 +252,13 @@ class PasteStructuredContentEditorCommand extends EditCommand {
193252
194253 // We've pasted the last new node. Remove it from the nodes to insert.
195254 nodesToInsert.removeLast ();
255+
256+ // Since we combined the last paste node with the 2nd half of the original
257+ // node, the caret position sits in the middle of that combined node.
258+ pasteEndPosition = DocumentPosition (
259+ nodeId: downstreamSplitNode.id,
260+ nodePosition: TextNodePosition (offset: lastPastedNode.text.length),
261+ );
196262 }
197263 }
198264
@@ -212,6 +278,10 @@ class PasteStructuredContentEditorCommand extends EditCommand {
212278 )
213279 ]);
214280 }
281+ pasteEndPosition ?? = DocumentPosition (
282+ nodeId: previousNode.id,
283+ nodePosition: previousNode.endPosition,
284+ );
215285
216286 if (deleteInitiallySelectedNode) {
217287 document.deleteNode (currentNodeWithSelection.id);
@@ -225,12 +295,7 @@ class PasteStructuredContentEditorCommand extends EditCommand {
225295 // Place the caret at the end of the pasted content.
226296 executor.executeCommand (
227297 ChangeSelectionCommand (
228- DocumentSelection .collapsed (
229- position: DocumentPosition (
230- nodeId: previousNode.id,
231- nodePosition: previousNode.endPosition,
232- ),
233- ),
298+ DocumentSelection .collapsed (position: pasteEndPosition),
234299 SelectionChangeType .insertContent,
235300 SelectionReason .userInteraction,
236301 ),
0 commit comments