From 768d93700e716e7f1ff9e50a9cf7b6bf9988dd28 Mon Sep 17 00:00:00 2001 From: dimitrievgs Date: Thu, 28 May 2026 00:32:22 +0300 Subject: [PATCH] Add drag'n'drop for inner files --- src/components/ChatPanel.ts | 156 ++++++++++++++++++++++++++++-------- 1 file changed, 121 insertions(+), 35 deletions(-) diff --git a/src/components/ChatPanel.ts b/src/components/ChatPanel.ts index e26f2fb..9738824 100644 --- a/src/components/ChatPanel.ts +++ b/src/components/ChatPanel.ts @@ -1445,56 +1445,142 @@ export class ChatPanel { } /** - * Обрабатывает событие сброса внешних файлов (Drag-and-Drop) из ОС + * Обрабатывает событие сброса внешних файлов (Drag-and-Drop) из ОС + * и внутренних файлов из файлового менеджера Obsidian */ private async handleDrop(e: DragEvent): Promise { - if (!e.dataTransfer?.files || e.dataTransfer.files.length === 0) { - return; // Оброшен не файл (например, просто выделенный текст) + if (!e.dataTransfer) { + 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. Проверяем перетаскивание внешних файлов из файловой системы ОС + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + const files = Array.from(e.dataTransfer.files); + 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'); + // 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) {} - } + if (electron.webUtils) { + absolutePath = electron.webUtils.getPathForFile(file); + } + } catch (err) {} + } - try { + try { // Кэшируем файл (копируем в папку плагина) и получаем метаинформацию - const cacheResult = await this.cacheManager.cacheExternalFile(absolutePath); + 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 - }; + const newAttachment: Attachment = { + type: 'external', + name: cacheResult.name, + sourcePath: absolutePath, + cachedPath: cacheResult.cachedPath, + uuid: cacheResult.uuid, + permanent: shouldBePermanent, + 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}`); + this.currentAttachments.push(newAttachment); + addedCount++; + } catch (error: any) { + console.error(`Ошибка обработки D&D файла ${file.name}:`, error); + new Notice(`Не удалось прикрепить ${file.name}. ${error.message || error}`); + } + } + } + // TODO: эту ветку не разбирал, некогда было + // 2. Проверяем перетаскивание внутренних файлов Obsidian + else { + const textData = e.dataTransfer.getData('text/plain'); + if (textData) { + const pathsToProcess: string[] = []; + + // А) Извлекаем пути, если перетянута системная ссылка Obsidian (из Проводника файлов) + const lines = textData.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('obsidian://open')) { + try { + const url = new URL(trimmed); + const filePath = url.searchParams.get('file'); // Попутно автоматически декодирует %D0... + if (filePath) { + pathsToProcess.push(filePath); + } + } catch (error) { + console.warn("Не удалось распарсить ссылку Obsidian:", trimmed); + } + } + } + + // Б) Оставляем поддержку, если перетащили текст, содержащий [[вики-ссылки]] + const linkRegex = /\[\[(.*?)\]\]/g; + let match; + while ((match = linkRegex.exec(textData)) !== null) { + let linkText = match[1]; + if (linkText.includes('|')) linkText = linkText.split('|')[0]; + pathsToProcess.push(linkText); + } + + // Убираем дубликаты + const uniquePaths = [...new Set(pathsToProcess)]; + + for (const targetPath of uniquePaths) { + // Ищем файл. Если это был obsidian:// URL, у нас сразу есть точный путь + let file = this.props.plugin.app.vault.getAbstractFileByPath(targetPath); + + // Если точный путь не найден (например, это просто имя из вики-ссылки), ищем по кэшу + if (!file) { + file = this.props.plugin.app.metadataCache.getFirstLinkpathDest(targetPath, ''); + } + + if (file instanceof TFile) { + try { + // Отправляем найденный в Vault файл на кеширование + const cacheResult = await this.cacheManager.cacheVaultFile(file); + + const promptsFolder = this.props.plugin.settings.promptsFolder; + const normalizedPromptsPath = promptsFolder ? promptsFolder.replace(/^\/+|\/+$/g, '') : null; + const isPrompt = normalizedPromptsPath && (file.path.startsWith(normalizedPromptsPath + '/') || file.path === normalizedPromptsPath); + + const ext = file.extension.toLowerCase(); + let mimeType = 'application/octet-stream'; + if (['md', 'txt', 'csv'].includes(ext)) mimeType = ext === 'md' ? 'text/markdown' : 'text/plain'; + else if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext)) mimeType = `image/${ext === 'svg' ? 'svg+xml' : ext === 'jpg' ? 'jpeg' : ext}`; + else if (ext === 'pdf') mimeType = 'application/pdf'; + else if (ext === 'json') mimeType = 'application/json'; + + // Формируем Attachment объект + const newAttachment: Attachment = { + type: isPrompt ? 'prompt' : 'file', + name: file.name, + sourcePath: file.path, + cachedPath: cacheResult.cachedPath, + uuid: cacheResult.uuid, + permanent: shouldBePermanent, + mimeType: mimeType + }; + + this.currentAttachments.push(newAttachment); + addedCount++; + } catch (error: any) { + console.error(`Ошибка обработки внутреннего D&D файла ${file.name}:`, error); + new Notice(`Не удалось прикрепить ${file.name}.`); + } + } + } } } @@ -1504,7 +1590,7 @@ export class ChatPanel { new Notice(`Прикреплено файлов: ${addedCount}`, 2000); } } - + // ----------------------------------------------- Scroll Observers --------------------------------------------------------------- /**