From 43d855a27d380b4186b19ce3bf528d62c9b92985 Mon Sep 17 00:00:00 2001 From: dimitrievgs Date: Mon, 11 May 2026 13:49:32 +0300 Subject: [PATCH] Add support for drag'n'dropping files in chat panel --- src/components/ChatPanel.ts | 100 +++++++++++++++++++++++++++++++++++- src/css/styles.css | 19 +++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/components/ChatPanel.ts b/src/components/ChatPanel.ts index f1eeafa..5eca47d 100644 --- a/src/components/ChatPanel.ts +++ b/src/components/ChatPanel.ts @@ -569,8 +569,11 @@ export class ChatPanel { this.hideSuggestions(); } }); - } + // Обработчики Drag-n-Drop для внешних файлов + this.setupDragAndDrop(); + } + // ----------------------------------------------- Reference Processing --------------------------------------------------------------- /** @@ -1159,4 +1162,99 @@ export class ChatPanel { this.renderAttachedFiles(); } } + + // ----------------------------------------------- Drag-and-Drop --------------------------------------------------------------- + + private setupDragAndDrop() + { + const inputContainer = this.container.querySelector('.chat-input-container') as HTMLElement; + if (inputContainer) { + inputContainer.addEventListener('dragover', (e) => { + e.preventDefault(); + // Добавляем класс для создания визуального эффекта (подсветка поля) + inputContainer.classList.add('drag-over'); + }); + + inputContainer.addEventListener('dragenter', (e) => { + e.preventDefault(); + inputContainer.classList.add('drag-over'); + }); + + inputContainer.addEventListener('dragleave', (e) => { + e.preventDefault(); + const relatedTarget = e.relatedTarget as Node | null; + // Проверяем, что курсор действительно покинул зону, а не перешел на дочерний элемент (текст, кнопку) + if (!inputContainer.contains(relatedTarget)) { + inputContainer.classList.remove('drag-over'); + } + }); + + inputContainer.addEventListener('drop', async (e) => { + e.preventDefault(); + inputContainer.classList.remove('drag-over'); + await this.handleDrop(e); + }); + } + } + + /** + * Обрабатывает событие сброса внешних файлов (Drag-and-Drop) из ОС + */ + private async handleDrop(e: DragEvent): Promise { + if (!e.dataTransfer?.files || e.dataTransfer.files.length === 0) { + return; // Оброшен не файл (например, просто выделенный текст) + } + + const isCtrlPressed = e.ctrlKey || e.metaKey; + const shouldBePermanent = !isCtrlPressed; + + const files = Array.from(e.dataTransfer.files); + let addedCount = 0; + + for (const file of files) { + let absolutePath = ''; + + // 1. Попытка через скрытое свойство 'path' (иногда оно есть, но не перечисляемо) + absolutePath = (file as any).path; + + // 2. Попытка через Electron (если доступен window.require) + if (!absolutePath && window.require) { + try { + const electron = window.require('electron'); + // В некоторых версиях путь можно достать через webUtils из модуля + if (electron.webUtils) { + absolutePath = electron.webUtils.getPathForFile(file); + } + } catch (err) {} + } + + try { + // Кэшируем файл (копируем в папку плагина) и получаем метаинформацию + const cacheResult = await this.cacheManager.cacheExternalFile(absolutePath); + + // Формируем Attachment объект + const newAttachment: Attachment = { + type: 'external', + name: cacheResult.name, + sourcePath: absolutePath, + cachedPath: cacheResult.cachedPath, + uuid: cacheResult.uuid, + permanent: shouldBePermanent, // D&D файлы прикрепляем разово (как $файл, а не !$) + mimeType: cacheResult.mimeType + }; + + this.currentAttachments.push(newAttachment); + addedCount++; + } catch (error: any) { + console.error(`Ошибка обработки D&D файла ${file.name}:`, error); + new Notice(`Не удалось прикрепить ${file.name}. ${error.message || error}`); + } + } + + if (addedCount > 0) { + // Перерисовываем UI "Скрепки" над полем ввода + this.renderAttachedFiles(); + new Notice(`Прикреплено файлов: ${addedCount}`, 2000); + } + } } diff --git a/src/css/styles.css b/src/css/styles.css index 9f5a01d..8d2e12f 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -231,6 +231,25 @@ border-top: 1px solid var(--background-modifier-border); padding: 8px 10px 4px 10px; position: relative; + /* Добавляем плавность для эффекта Drag-n-Drop */ + transition: background-color 0.2s ease, border-color 0.2s ease, border-radius 0.2s ease; +} + +/* Состояние при перетаскивании файлов (Drag-n-Drop) над полем чата */ +.chat-input-container.drag-over { + border: 2px dashed var(--interactive-accent) !important; + border-top: 2px dashed var(--interactive-accent) !important; + background-color: var(--background-modifier-hover); + border-radius: var(--node-border-radius); /* Используем вашу переменную из :root */ + z-index: 10; +} + +/* Опционально: Добавляем легкое свечение, чтобы ясно дать понять, что зона активна */ +.theme-dark .chat-input-container.drag-over { + box-shadow: 0 0 15px rgba(106, 168, 79, 0.15); /* Ваш --text-accent: #6aa84f */ +} +.theme-light .chat-input-container.drag-over { + box-shadow: 0 0 15px rgba(0, 123, 255, 0.15); /* Ваш --text-accent: #007bff */ } .scroll-container {