@@ -72,11 +72,11 @@ export const TodoNodeItem = memo(
7272 useEditable ( contentEditable , handleContentChange , { disabled : isLoading || isNoteFocused } )
7373
7474 const focusClosestNode = useCallback (
75- async (
75+ (
7676 { caretPosition, direction, id, parentId } : TodoNodeItemFocusClosestNodeParams ,
7777 event ?: React . KeyboardEvent
7878 ) => {
79- const closestNodeId = await getClosestNodeId ( { direction, id, parentId } )
79+ const closestNodeId = getClosestNodeId ( { direction, id, parentId } )
8080
8181 if ( ! closestNodeId ) {
8282 return
@@ -194,64 +194,42 @@ export const TodoNodeItem = memo(
194194 }
195195
196196 function handleContentPasteCapture ( event : React . ClipboardEvent ) {
197- event . preventDefault ( )
198- event . stopPropagation ( )
197+ if ( ! node || ! contentEditable . current ) {
198+ return
199+ }
199200
200201 const clipboardData = event . clipboardData . getData ( 'text/plain' )
201202
202- if ( node && contentEditable . current && clipboardData . includes ( '\n' ) ) {
203- const caretIndex = getContentEditableCaretIndex ( contentEditable . current )
204-
205- const update = { id : node . id , parentId : node . parentId }
206-
207- const newNodesContent = clipboardData
208- . split ( '\n' )
209- . filter ( ( line ) => line . trim ( ) . length > 0 )
210- . reverse ( )
211-
212- for ( const [ index , newNodeContent ] of newNodesContent . entries ( ) ) {
213- const content = newNodeContent . trim ( )
214-
215- // If the current node is empty, fill it with the first line of the pasted content.
216- if ( index === newNodesContent . length - 1 && node . content . trim ( ) . length === 0 ) {
217- updateContent ( { id : node . id , content } )
218-
219- continue
220- }
221-
222- const newId = cuid ( )
223-
224- addNode ( {
225- ...update ,
226- direction : caretIndex === 0 && node . content . length > 0 ? 'up' : 'down' ,
227- newId,
228- content,
229- } )
230- }
231-
203+ // If the clipboard data contains a single line, we let `useEditable` handle the paste event.
204+ if ( ! clipboardData . includes ( '\n' ) ) {
232205 return
233206 }
234207
235- const text = clipboardData . replaceAll ( / \n / gm, ' ' )
236- const range = window . getSelection ( ) ?. getRangeAt ( 0 )
208+ // Otherwise, we need to prevent the default behavior and handle the paste event manually.
209+ event . preventDefault ( )
210+ event . stopPropagation ( )
237211
238- if ( range && node ?. id ) {
239- updateContent ( {
240- id : node . id ,
241- content :
242- node . content . slice ( 0 , range . startOffset ) +
243- text . replaceAll ( / \n / gm, ' ' ) +
244- node . content . slice ( range . endOffset ) ,
245- } )
212+ // We need to handle differently the first line and all the other lines.
213+ const [ firstLine , ...otherLines ] = clipboardData . split ( '\n' )
246214
247- const nextCaretIndex = range . startOffset + text . length
215+ // We first need to create new nodes for all lines except the first one.
216+ const newNodesContent = otherLines . filter ( ( line ) => line . trim ( ) . length > 0 ) . reverse ( )
248217
249- // Pasting large content may lead to a loss of focus, we can safely prevent that by refocusing the current
250- // node and having the caret being placed right after the pasted content.
251- requestAnimationFrame ( ( ) => {
252- focusContent ( nextCaretIndex )
218+ const caretIndex = getContentEditableCaretIndex ( contentEditable . current )
219+ const update = { id : node . id , parentId : node . parentId }
220+
221+ for ( const newNodeContent of newNodesContent . values ( ) ) {
222+ addNode ( {
223+ ...update ,
224+ direction : caretIndex === 0 && node . content . length > 0 ? 'up' : 'down' ,
225+ newId : cuid ( ) ,
226+ content : newNodeContent . trim ( ) ,
253227 } )
254228 }
229+
230+ // Then, we update the current node with the first line as if it was a regular paste event.
231+ // https://twitter.com/LeaVerou/status/1462122134256422913
232+ document . execCommand ( 'insertText' , false , firstLine )
255233 }
256234
257235 function preserveCaret ( callback : ( ) => void ) {
0 commit comments