diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb0a31af..01efbc54e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ ### Fixed +- Let desktop users select and copy AI chat text without replacing it with the selected canvas layers. (#538) - Restore visible above, below, and child drop feedback while dragging layers in the Layers panel. - Place editor-created instances beside nested source components in world space, including transformed source and destination parents. - Harden collaboration node synchronization against malformed remote source metadata and geometry while excluding derived text-renderer caches. diff --git a/src/app/shell/keyboard/clipboard.ts b/src/app/shell/keyboard/clipboard.ts index a65c65108..7d62cbb24 100644 --- a/src/app/shell/keyboard/clipboard.ts +++ b/src/app/shell/keyboard/clipboard.ts @@ -7,7 +7,7 @@ import { copySelectionToTauriClipboard, pasteFromTauriClipboard } from '@/app/editor/clipboard/system' -import { isEditing } from '@/app/shell/keyboard/focus' +import { hasDocumentTextSelection, isEditing } from '@/app/shell/keyboard/focus' import { isTauri } from '@/app/tauri/env' function cursorPosition(store: EditorStore) { @@ -17,7 +17,7 @@ function cursorPosition(store: EditorStore) { export function bindEditorClipboard(store: EditorStore) { useEventListener(window, 'copy', (e: ClipboardEvent) => { - if (isEditing(e)) return + if (isEditing(e) || hasDocumentTextSelection()) return e.preventDefault() if (isTauri()) { void copySelectionToTauriClipboard(store) diff --git a/src/app/shell/keyboard/focus.ts b/src/app/shell/keyboard/focus.ts index f093554f4..e86e6b55c 100644 --- a/src/app/shell/keyboard/focus.ts +++ b/src/app/shell/keyboard/focus.ts @@ -10,6 +10,11 @@ export function isEditing(event: Event) { return event.composedPath().some(isEditableTarget) } +export function hasDocumentTextSelection(): boolean { + const selection = window.getSelection() + return selection !== null && !selection.isCollapsed && selection.toString().length > 0 +} + export function isInputElement(element: EventTarget | null | undefined): boolean { return ( element instanceof HTMLInputElement || diff --git a/src/components/chat/ChatMessage.vue b/src/components/chat/ChatMessage.vue index 37c667986..9f562782d 100644 --- a/src/components/chat/ChatMessage.vue +++ b/src/components/chat/ChatMessage.vue @@ -52,7 +52,10 @@ function partKey(part: UIMessagePart, index: number): stri v-test-id="`chat-message-${message.role}`" :class="message.role === 'user' ? 'flex justify-end' : ''" > -
+