Add drag'n'drop for inner files

This commit is contained in:
dimitrievgs 2026-05-28 00:32:22 +03:00
parent 205040a599
commit 768d93700e

View File

@ -1446,55 +1446,141 @@ export class ChatPanel {
/** /**
* Обрабатывает событие сброса внешних файлов (Drag-and-Drop) из ОС * Обрабатывает событие сброса внешних файлов (Drag-and-Drop) из ОС
* и внутренних файлов из файлового менеджера Obsidian
*/ */
private async handleDrop(e: DragEvent): Promise<void> { private async handleDrop(e: DragEvent): Promise<void> {
if (!e.dataTransfer?.files || e.dataTransfer.files.length === 0) { if (!e.dataTransfer) {
return; // Оброшен не файл (например, просто выделенный текст) return; // Игнорируем пустые события
} }
const isCtrlPressed = e.ctrlKey || e.metaKey; const isCtrlPressed = e.ctrlKey || e.metaKey;
const shouldBePermanent = !isCtrlPressed; const shouldBePermanent = !isCtrlPressed;
const files = Array.from(e.dataTransfer.files);
let addedCount = 0; let addedCount = 0;
for (const file of files) { // 1. Проверяем перетаскивание внешних файлов из файловой системы ОС
let absolutePath = ''; 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' (иногда оно есть, но не перечисляемо) // 1. Попытка через скрытое свойство 'path' (иногда оно есть, но не перечисляемо)
absolutePath = (file as any).path; absolutePath = (file as any).path;
// 2. Попытка через Electron (если доступен window.require) // 2. Попытка через Electron (если доступен window.require)
if (!absolutePath && window.require) { if (!absolutePath && window.require) {
try { try {
const electron = window.require('electron'); const electron = window.require('electron');
// В некоторых версиях путь можно достать через webUtils из модуля // В некоторых версиях путь можно достать через webUtils из модуля
if (electron.webUtils) { if (electron.webUtils) {
absolutePath = electron.webUtils.getPathForFile(file); absolutePath = electron.webUtils.getPathForFile(file);
} }
} catch (err) {} } catch (err) {}
} }
try { try {
// Кэшируем файл (копируем в папку плагина) и получаем метаинформацию // Кэшируем файл (копируем в папку плагина) и получаем метаинформацию
const cacheResult = await this.cacheManager.cacheExternalFile(absolutePath); const cacheResult = await this.cacheManager.cacheExternalFile(absolutePath);
// Формируем Attachment объект // Формируем Attachment объект
const newAttachment: Attachment = { const newAttachment: Attachment = {
type: 'external', type: 'external',
name: cacheResult.name, name: cacheResult.name,
sourcePath: absolutePath, sourcePath: absolutePath,
cachedPath: cacheResult.cachedPath, cachedPath: cacheResult.cachedPath,
uuid: cacheResult.uuid, uuid: cacheResult.uuid,
permanent: shouldBePermanent, // D&D файлы прикрепляем разово (как $файл, а не !$) permanent: shouldBePermanent,
mimeType: cacheResult.mimeType mimeType: cacheResult.mimeType
}; };
this.currentAttachments.push(newAttachment); this.currentAttachments.push(newAttachment);
addedCount++; addedCount++;
} catch (error: any) { } catch (error: any) {
console.error(`Ошибка обработки D&D файла ${file.name}:`, error); console.error(`Ошибка обработки D&D файла ${file.name}:`, error);
new Notice(`Не удалось прикрепить ${file.name}. ${error.message || 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}.`);
}
}
}
} }
} }