fix(dragdrop): arborescence récursive, recherche/filtre de dossiers et saisie de sous-dossier (#89)
This commit is contained in:
+54
-10
@@ -250,8 +250,9 @@ export async function promptTargetFolder(initialVault, initialDir = '', items =
|
|||||||
if (vaultFoldersMap[v]) return vaultFoldersMap[v];
|
if (vaultFoldersMap[v]) return vaultFoldersMap[v];
|
||||||
try {
|
try {
|
||||||
const res = await api(`/api/vault/${encodeURIComponent(v)}/paths?limit=5000`);
|
const res = await api(`/api/vault/${encodeURIComponent(v)}/paths?limit=5000`);
|
||||||
if (res && Array.isArray(res.paths)) {
|
const list = res?.results || res?.paths || [];
|
||||||
vaultFoldersMap[v] = res.paths
|
if (Array.isArray(list)) {
|
||||||
|
vaultFoldersMap[v] = list
|
||||||
.filter((p) => p.type === 'directory')
|
.filter((p) => p.type === 'directory')
|
||||||
.map((p) => p.path)
|
.map((p) => p.path)
|
||||||
.sort();
|
.sort();
|
||||||
@@ -275,6 +276,8 @@ export async function promptTargetFolder(initialVault, initialDir = '', items =
|
|||||||
const fileCount = items.filter((x) => !x.isDir).length;
|
const fileCount = items.filter((x) => !x.isDir).length;
|
||||||
const dirCount = items.filter((x) => x.isDir).length;
|
const dirCount = items.filter((x) => x.isDir).length;
|
||||||
|
|
||||||
|
let filterText = '';
|
||||||
|
|
||||||
function renderVaultOptions() {
|
function renderVaultOptions() {
|
||||||
return vaultsList.map((v) => {
|
return vaultsList.map((v) => {
|
||||||
const vName = typeof v === 'string' ? v : v.name;
|
const vName = typeof v === 'string' ? v : v.name;
|
||||||
@@ -283,17 +286,26 @@ export async function promptTargetFolder(initialVault, initialDir = '', items =
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFolderOptions(folders, curDir) {
|
function renderFolderOptions(folders, curDir, filter = '') {
|
||||||
|
let filtered = folders;
|
||||||
|
if (filter.trim()) {
|
||||||
|
const q = filter.trim().toLowerCase();
|
||||||
|
filtered = folders.filter((f) => f.toLowerCase().includes(q));
|
||||||
|
}
|
||||||
|
|
||||||
let html = `<option value="">${escapeHtml(t('upload.vault_root') || '/ (Racine de la voute)')}</option>`;
|
let html = `<option value="">${escapeHtml(t('upload.vault_root') || '/ (Racine de la voute)')}</option>`;
|
||||||
for (const folder of folders) {
|
for (const folder of filtered) {
|
||||||
const sel = folder === curDir ? 'selected' : '';
|
const sel = folder === curDir ? 'selected' : '';
|
||||||
html += `<option value="${escapeHtml(folder)}" ${sel}>📁 ${escapeHtml(folder)}</option>`;
|
// Indent subfolder depth for a clean hierarchical visual tree
|
||||||
|
const depth = (folder.match(/\//g) || []).length;
|
||||||
|
const indent = ' '.repeat(depth);
|
||||||
|
html += `<option value="${escapeHtml(folder)}" ${sel}>${indent}📁 ${escapeHtml(folder)}</option>`;
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay.innerHTML = `
|
overlay.innerHTML = `
|
||||||
<div class="obsigate-modal" style="min-width: 440px; max-width: 520px;">
|
<div class="obsigate-modal" style="min-width: 460px; max-width: 560px;">
|
||||||
<div class="obsigate-modal-header">
|
<div class="obsigate-modal-header">
|
||||||
<span class="obsigate-modal-title">📁 ${escapeHtml(t('upload.choose_target_folder') || "Destination de l'importation")}</span>
|
<span class="obsigate-modal-title">📁 ${escapeHtml(t('upload.choose_target_folder') || "Destination de l'importation")}</span>
|
||||||
<button class="modal-close modal-btn" type="button" aria-label="Fermer" style="padding: 4px 8px; line-height: 1;">×</button>
|
<button class="modal-close modal-btn" type="button" aria-label="Fermer" style="padding: 4px 8px; line-height: 1;">×</button>
|
||||||
@@ -316,10 +328,18 @@ export async function promptTargetFolder(initialVault, initialDir = '', items =
|
|||||||
<label style="display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 6px; color: var(--text-primary);">
|
<label style="display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 6px; color: var(--text-primary);">
|
||||||
${escapeHtml(t('upload.target_directory') || 'Sous-répertoire cible')}
|
${escapeHtml(t('upload.target_directory') || 'Sous-répertoire cible')}
|
||||||
</label>
|
</label>
|
||||||
<select id="obsigate-upload-folder-select" class="modal-select modal-input">
|
<input type="text" id="obsigate-upload-folder-filter" class="modal-input" placeholder="${escapeHtml(t('upload.filter_folders') || 'Filtrer ou rechercher un dossier...')}" style="margin-bottom: 6px; font-size: 0.85rem;" />
|
||||||
|
<select id="obsigate-upload-folder-select" class="modal-select modal-input" size="5" style="height: 120px; font-family: monospace; font-size: 0.85rem;">
|
||||||
${renderFolderOptions(vaultFoldersMap[selectedVault] || [], initialDir)}
|
${renderFolderOptions(vaultFoldersMap[selectedVault] || [], initialDir)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-form-group" style="margin-top: 14px;">
|
||||||
|
<label style="display: block; font-weight: 500; font-size: 0.8rem; margin-bottom: 4px; color: var(--text-muted);">
|
||||||
|
${escapeHtml(t('upload.custom_folder') || 'Ou saisir un nouveau sous-dossier...')}
|
||||||
|
</label>
|
||||||
|
<input type="text" id="obsigate-upload-custom-folder" class="modal-input" placeholder="ex: dossier/sous-dossier" style="font-size: 0.85rem;" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="obsigate-modal-footer">
|
<div class="obsigate-modal-footer">
|
||||||
<button type="button" class="modal-btn modal-cancel-btn">${escapeHtml(t('button.cancel') || 'Annuler')}</button>
|
<button type="button" class="modal-btn modal-cancel-btn">${escapeHtml(t('button.cancel') || 'Annuler')}</button>
|
||||||
@@ -331,12 +351,35 @@ export async function promptTargetFolder(initialVault, initialDir = '', items =
|
|||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
const vaultSelect = overlay.querySelector('#obsigate-upload-vault-select');
|
const vaultSelect = overlay.querySelector('#obsigate-upload-vault-select');
|
||||||
|
const folderFilter = overlay.querySelector('#obsigate-upload-folder-filter');
|
||||||
const folderSelect = overlay.querySelector('#obsigate-upload-folder-select');
|
const folderSelect = overlay.querySelector('#obsigate-upload-folder-select');
|
||||||
|
const customFolderInput = overlay.querySelector('#obsigate-upload-custom-folder');
|
||||||
|
|
||||||
|
const updateFoldersView = async () => {
|
||||||
|
const fList = await fetchFoldersForVault(selectedVault);
|
||||||
|
folderSelect.innerHTML = renderFolderOptions(fList, folderSelect.value, filterText);
|
||||||
|
};
|
||||||
|
|
||||||
vaultSelect.addEventListener('change', async () => {
|
vaultSelect.addEventListener('change', async () => {
|
||||||
selectedVault = vaultSelect.value;
|
selectedVault = vaultSelect.value;
|
||||||
const fList = await fetchFoldersForVault(selectedVault);
|
await updateFoldersView();
|
||||||
folderSelect.innerHTML = renderFolderOptions(fList, '');
|
});
|
||||||
|
|
||||||
|
folderFilter.addEventListener('input', () => {
|
||||||
|
filterText = folderFilter.value;
|
||||||
|
updateFoldersView();
|
||||||
|
});
|
||||||
|
|
||||||
|
folderSelect.addEventListener('change', () => {
|
||||||
|
if (folderSelect.value) {
|
||||||
|
customFolderInput.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
customFolderInput.addEventListener('input', () => {
|
||||||
|
if (customFolderInput.value.trim()) {
|
||||||
|
folderSelect.selectedIndex = -1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const close = (val) => {
|
const close = (val) => {
|
||||||
@@ -352,7 +395,8 @@ export async function promptTargetFolder(initialVault, initialDir = '', items =
|
|||||||
|
|
||||||
overlay.querySelector('.modal-confirm-btn').addEventListener('click', () => {
|
overlay.querySelector('.modal-confirm-btn').addEventListener('click', () => {
|
||||||
const v = vaultSelect.value;
|
const v = vaultSelect.value;
|
||||||
const d = folderSelect.value;
|
const customPath = customFolderInput.value.trim().replace(/^[\/\\]+|[\/\\]+$/g, '');
|
||||||
|
const d = customPath || folderSelect.value || '';
|
||||||
close({ vault: v, targetDir: d });
|
close({ vault: v, targetDir: d });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -645,6 +645,8 @@
|
|||||||
"upload.target_vault": "Target Vault",
|
"upload.target_vault": "Target Vault",
|
||||||
"upload.vault_root": "/ (Vault root)",
|
"upload.vault_root": "/ (Vault root)",
|
||||||
"upload.target_directory": "Target subdirectory",
|
"upload.target_directory": "Target subdirectory",
|
||||||
|
"upload.filter_folders": "Filter or search a folder...",
|
||||||
|
"upload.custom_folder": "Or enter a new subfolder path...",
|
||||||
"upload.import_summary": "Items to import: {files} file(s), {dirs} folder(s)",
|
"upload.import_summary": "Items to import: {files} file(s), {dirs} folder(s)",
|
||||||
"upload.import_button": "Import",
|
"upload.import_button": "Import",
|
||||||
"editor.cancel": "Cancel",
|
"editor.cancel": "Cancel",
|
||||||
|
|||||||
@@ -645,6 +645,8 @@
|
|||||||
"upload.target_vault": "Voute cible",
|
"upload.target_vault": "Voute cible",
|
||||||
"upload.vault_root": "/ (Racine de la voute)",
|
"upload.vault_root": "/ (Racine de la voute)",
|
||||||
"upload.target_directory": "Sous-répertoire cible",
|
"upload.target_directory": "Sous-répertoire cible",
|
||||||
|
"upload.filter_folders": "Filtrer ou rechercher un dossier...",
|
||||||
|
"upload.custom_folder": "Ou saisir un nouveau sous-dossier...",
|
||||||
"upload.import_summary": "Éléments à importer : {files} fichier(s), {dirs} dossier(s)",
|
"upload.import_summary": "Éléments à importer : {files} fichier(s), {dirs} dossier(s)",
|
||||||
"upload.import_button": "Importer",
|
"upload.import_button": "Importer",
|
||||||
"editor.cancel": "Annuler",
|
"editor.cancel": "Annuler",
|
||||||
|
|||||||
Reference in New Issue
Block a user