feat(dragdrop): drag & drop complet de fichiers/dossiers & intégration IA (#89)
CI / lint (push) Successful in 1m20s
CI / security (push) Successful in 50s
CI / test (push) Successful in 2m42s
CI / build (push) Successful in 51s
CI / e2e (push) Successful in 10m29s

This commit is contained in:
2026-09-14 15:31:12 -04:00
parent 150b57d538
commit 1db3e0ad2f
13 changed files with 990 additions and 10 deletions
+93
View File
@@ -965,6 +965,12 @@ class BooksLM {
this._sendMessage();
});
// Setup Drag & Drop for internal items and external files (#89)
this._setupDropZones(panel);
// Save singleton reference on window
window.BooksLMInstance = this;
return panel;
}
@@ -981,6 +987,93 @@ class BooksLM {
return Math.max(PANEL_MIN_WIDTH, Math.min(PANEL_MAX_WIDTH, width));
}
_setupDropZones(panel) {
const composer = panel.querySelector('.bookslm-composer') || panel;
const onDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = 'copy';
composer.classList.add('bookslm-drop-over');
};
const onDragLeave = (e) => {
if (composer.contains(e.relatedTarget)) return;
composer.classList.remove('bookslm-drop-over');
};
const onDrop = async (e) => {
e.preventDefault();
e.stopPropagation();
composer.classList.remove('bookslm-drop-over');
// Check if internal tree drag
const treeData = e.dataTransfer.getData('tree:path') || e.dataTransfer.getData('text/plain');
const treeVault = e.dataTransfer.getData('tree:vault');
const treeType = e.dataTransfer.getData('tree:type');
if (treeData && treeVault) {
if (treeType === 'directory') {
this._addAdhocDir(treeData, treeVault);
} else {
this._addAdhocFile(treeData, treeVault);
}
return;
}
// External files
const files = Array.from(e.dataTransfer.files || []);
if (files.length > 0) {
this.handleExternalDroppedItems(files.map((f) => ({ path: f.name, file: f, isDir: false })));
}
};
composer.addEventListener('dragover', onDragOver);
composer.addEventListener('dragleave', onDragLeave);
composer.addEventListener('drop', onDrop);
}
/**
* Handle files/folders dropped from outside into BooksLM (#89).
*/
async handleExternalDroppedItems(items) {
if (!this._isOpen) {
this.open();
}
for (const item of items) {
if (item.isDir) {
continue;
}
const file = item.file;
if (!file) continue;
if (file.type && file.type.startsWith('image/')) {
this._addImageFile(file);
} else {
// Read text/markdown file content
try {
const text = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ''));
reader.onerror = reject;
reader.readAsText(file);
});
// Insert into context files or append to composer
this._adhocFiles.push({
path: item.path,
vault: null,
inlineContent: text,
});
this._renderAttachments();
} catch (err) {
console.warn('Could not read dropped file as text:', err);
}
}
}
}
_startResize(e) {
if (!this._panel || this._isFullscreen) return;
e.preventDefault();