diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index ac23a7ffbe..4d29f09924 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -2814,7 +2814,7 @@ -
+
{ e.preventDefault(); - // Check if a file is being dragged. - if (e.dataTransfer?.types?.includes('Files')) { + // Check if a file or a sidebar chat item is being dragged. + if ( + e.dataTransfer?.types?.includes('Files') || + e.dataTransfer?.types?.includes('text/plain') + ) { dragged = true; } else { dragged = false; @@ -825,6 +829,35 @@ e.preventDefault(); console.log(e); + // Check if the dropped data is a sidebar chat item + const textData = e.dataTransfer?.getData('text/plain'); + if (textData) { + try { + const data = JSON.parse(textData); + if (data.type === 'chat' && data.id) { + // Fetch the chat to get its title, then add as a reference chat + const chat = await getChatById(localStorage.token, data.id); + if (chat) { + const chatItem = { + type: 'chat', + id: chat.id, + name: chat.title, + collection_name: '', + status: 'processed' + }; + if (!files.find((f) => f.id === chatItem.id)) { + files = [...files, chatItem]; + } + } + dragged = false; + e.stopPropagation(); + return; + } + } catch (_) { + // Not valid JSON — fall through to file handling + } + } + if (e.dataTransfer?.files) { const inputFiles = Array.from(e.dataTransfer?.files); if (inputFiles && inputFiles.length > 0) { @@ -1022,8 +1055,8 @@ dropzoneElement = document.getElementById('chat-pane'); if (dropzoneElement) { - dropzoneElement.addEventListener('dragover', onDragOver); - dropzoneElement.addEventListener('drop', onDrop); + dropzoneElement.addEventListener('dragover', onDragOver, true); + dropzoneElement.addEventListener('drop', onDrop, true); dropzoneElement.addEventListener('dragleave', onDragLeave); } @@ -1041,8 +1074,8 @@ window.removeEventListener('blur', onBlur); if (dropzoneElement) { - dropzoneElement.removeEventListener('dragover', onDragOver); - dropzoneElement.removeEventListener('drop', onDrop); + dropzoneElement.removeEventListener('dragover', onDragOver, true); + dropzoneElement.removeEventListener('drop', onDrop, true); dropzoneElement.removeEventListener('dragleave', onDragLeave); } }; diff --git a/src/lib/components/common/DragGhost.svelte b/src/lib/components/common/DragGhost.svelte index 526a66ae2e..0ed98eeb0f 100644 --- a/src/lib/components/common/DragGhost.svelte +++ b/src/lib/components/common/DragGhost.svelte @@ -29,7 +29,7 @@
diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte index 488200d900..6a470d779c 100644 --- a/src/lib/components/common/RichTextInput.svelte +++ b/src/lib/components/common/RichTextInput.svelte @@ -801,8 +801,8 @@ if (!editor || !editor.view || editor.isDestroyed) { return false; } - // default logic - return from !== to; + // Only show when editor is focused and text is selected + return view.hasFocus() && from !== to; } }), FloatingMenu.configure({ @@ -905,6 +905,24 @@ }, editorProps: { attributes: { id }, + handleDrop: (view, event) => { + // Intercept sidebar chat item drops to prevent ProseMirror + // from inserting the raw JSON as text. The actual handling + // (adding as Reference Chat) is done by MessageInput's onDrop. + const textData = event.dataTransfer?.getData('text/plain'); + if (textData) { + try { + const data = JSON.parse(textData); + if (data.type === 'chat' && data.id) { + // Swallow the drop — let the parent handler deal with it + return true; + } + } catch (_) { + // Not JSON, let ProseMirror handle normally + } + } + return false; + }, handlePaste: (view, event) => { // Force plain-text pasting when richText === false if (!richText) { @@ -1170,6 +1188,18 @@ } }, onSelectionUpdate: onSelectionUpdate, + onBlur: () => { + // Force-hide floating menus when editor loses focus. + // shouldShow alone isn't enough because it only runs on transactions. + if (bubbleMenuElement) { + bubbleMenuElement.style.visibility = 'hidden'; + bubbleMenuElement.style.opacity = '0'; + } + if (floatingMenuElement) { + floatingMenuElement.style.visibility = 'hidden'; + floatingMenuElement.style.opacity = '0'; + } + }, enableInputRules: richText, enablePasteRules: richText });