Skip to content

Commit 3bca111

Browse files
authored
Merge pull request #135 from HiDeoo/hd-bug-todo-copy
2 parents e7613f0 + 6b2b447 commit 3bca111

3 files changed

Lines changed: 34 additions & 50 deletions

File tree

.github/workflows/integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- name: Install pnpm
2828
uses: pnpm/action-setup@v2.2.2
2929
with:
30-
version: latest
30+
version: 7.13.0
3131
run_install: false
3232

3333
- name: Get pnpm store directory path

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## v1.0.1
6+
7+
### 🐞 Bug Fixes
8+
9+
- Fix a potential issue when pasting content in a todo node that could cause the clipboard content to be lost.
10+
511
## v1.0.0
612

713
### 💄 UI

src/components/todo/TodoNodeItem.tsx

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)