diff --git a/frontend/app.js b/frontend/app.js index 8eaa8d7..9df24d9 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -5721,7 +5721,7 @@ showToast(`Dossier "${name}" créé`, 'success'); this._closeModal(overlay); - await refreshVaultTree(); + await refreshSidebarTreePreservingState(); } catch (err) { showToast(err.message || 'Erreur lors de la création', 'error'); createBtn.disabled = false; @@ -5831,7 +5831,7 @@ showToast(`Fichier "${name}" créé`, 'success'); this._closeModal(overlay); - await refreshVaultTree(); + await refreshSidebarTreePreservingState(); openFile(vault, path); } catch (err) { showToast(err.message || 'Erreur lors de la création', 'error'); @@ -5850,7 +5850,114 @@ }, async startInlineRename(vault, path, type) { - showToast('Fonctionnalité de renommage inline en cours de développement', 'info'); + const item = document.querySelector(`.tree-item[data-vault="${CSS.escape(vault)}"][data-path="${CSS.escape(path)}"]`); + if (!item) { + showToast('Élément introuvable dans l’arborescence', 'error'); + return; + } + + const textNode = Array.from(item.childNodes).find((node) => node.nodeType === Node.TEXT_NODE && node.textContent.trim()); + if (!textNode) { + showToast('Impossible de renommer cet élément', 'error'); + return; + } + + const originalText = textNode.textContent; + const trimmedOriginal = originalText.trim(); + const currentName = path.split('/').pop() || trimmedOriginal; + const baseName = type === 'file' ? currentName.replace(/(\.[^./\\]+)$/i, '') : currentName; + const extension = type === 'file' ? (currentName.match(/(\.[^./\\]+)$/i)?.[1] || '') : ''; + + const input = document.createElement('input'); + input.type = 'text'; + input.className = 'sidebar-item-input'; + input.value = baseName; + + textNode.textContent = ' '; + const badge = item.querySelector('.badge-small'); + if (badge) { + item.insertBefore(input, badge); + } else { + item.appendChild(input); + } + + const restore = () => { + input.remove(); + textNode.textContent = originalText; + }; + + const validateName = (name) => { + if (!name.trim()) return 'Le nom ne peut pas être vide'; + if (/[/\\:*?"<>|]/.test(name)) return 'Caractères interdits: / \\ : * ? " < > |'; + return null; + }; + + const submit = async () => { + const name = input.value.trim(); + const error = validateName(name); + if (error) { + showToast(error, 'error'); + input.focus(); + input.select(); + return; + } + + const newName = `${name}${extension}`; + if (newName === currentName) { + restore(); + return; + } + + input.disabled = true; + try { + const endpoint = type === 'directory' ? `/api/directory/${encodeURIComponent(vault)}` : `/api/file/${encodeURIComponent(vault)}`; + const result = await api(endpoint, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ path, new_name: newName }), + }); + + const nextPath = result.new_path; + await refreshSidebarTreePreservingState(); + + if (type === 'file' && currentVault === vault && currentPath === path) { + await openFile(vault, nextPath); + } else if (type === 'directory' && currentVault === vault && currentPath && (currentPath === path || currentPath.startsWith(`${path}/`))) { + const suffix = currentPath === path ? '' : currentPath.slice(path.length); + currentPath = `${nextPath}${suffix}`; + await focusPathInSidebar(vault, currentPath, { alignToTop: false }); + } + + showToast(type === 'directory' ? 'Dossier renommé' : 'Fichier renommé', 'success'); + } catch (err) { + input.disabled = false; + showToast(err.message || 'Erreur lors du renommage', 'error'); + input.focus(); + input.select(); + return; + } + }; + + input.addEventListener('click', (e) => e.stopPropagation()); + input.addEventListener('keydown', async (e) => { + e.stopPropagation(); + if (e.key === 'Enter') { + e.preventDefault(); + await submit(); + } + if (e.key === 'Escape') { + e.preventDefault(); + restore(); + } + }); + input.addEventListener('blur', async () => { + if (!input.disabled) { + await submit(); + } + }); + + input.focus(); + input.setSelectionRange(0, input.value.length); }, confirmDeleteDirectory(vault, path) { @@ -5900,7 +6007,7 @@ showToast(`Dossier supprimé (${result.deleted_count} fichiers)`, 'success'); this._closeModal(overlay); - await refreshVaultTree(); + await refreshSidebarTreePreservingState(); if (currentVault === vault && currentPath && currentPath.startsWith(path)) { showWelcome(); @@ -5962,7 +6069,7 @@ showToast('Fichier supprimé', 'success'); this._closeModal(overlay); - await refreshVaultTree(); + await refreshSidebarTreePreservingState(); if (currentVault === vault && currentPath === path) { showWelcome();