feat: implement inline rename for files and directories with state preservation
- Replace placeholder inline rename with full implementation - Add input validation for file/directory names with character restrictions - Handle file extensions separately during rename operations - Update current path when renaming open files or parent directories - Replace refreshVaultTree calls with refreshSidebarTreePreservingState to maintain tree expansion state - Add keyboard shortcuts (Enter to submit, Escape to cancel)
This commit is contained in:
parent
32c1bad1a1
commit
628a664c59
117
frontend/app.js
117
frontend/app.js
@ -5721,7 +5721,7 @@
|
|||||||
|
|
||||||
showToast(`Dossier "${name}" créé`, 'success');
|
showToast(`Dossier "${name}" créé`, 'success');
|
||||||
this._closeModal(overlay);
|
this._closeModal(overlay);
|
||||||
await refreshVaultTree();
|
await refreshSidebarTreePreservingState();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(err.message || 'Erreur lors de la création', 'error');
|
showToast(err.message || 'Erreur lors de la création', 'error');
|
||||||
createBtn.disabled = false;
|
createBtn.disabled = false;
|
||||||
@ -5831,7 +5831,7 @@
|
|||||||
|
|
||||||
showToast(`Fichier "${name}" créé`, 'success');
|
showToast(`Fichier "${name}" créé`, 'success');
|
||||||
this._closeModal(overlay);
|
this._closeModal(overlay);
|
||||||
await refreshVaultTree();
|
await refreshSidebarTreePreservingState();
|
||||||
openFile(vault, path);
|
openFile(vault, path);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(err.message || 'Erreur lors de la création', 'error');
|
showToast(err.message || 'Erreur lors de la création', 'error');
|
||||||
@ -5850,7 +5850,114 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
async startInlineRename(vault, path, type) {
|
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) {
|
confirmDeleteDirectory(vault, path) {
|
||||||
@ -5900,7 +6007,7 @@
|
|||||||
|
|
||||||
showToast(`Dossier supprimé (${result.deleted_count} fichiers)`, 'success');
|
showToast(`Dossier supprimé (${result.deleted_count} fichiers)`, 'success');
|
||||||
this._closeModal(overlay);
|
this._closeModal(overlay);
|
||||||
await refreshVaultTree();
|
await refreshSidebarTreePreservingState();
|
||||||
|
|
||||||
if (currentVault === vault && currentPath && currentPath.startsWith(path)) {
|
if (currentVault === vault && currentPath && currentPath.startsWith(path)) {
|
||||||
showWelcome();
|
showWelcome();
|
||||||
@ -5962,7 +6069,7 @@
|
|||||||
|
|
||||||
showToast('Fichier supprimé', 'success');
|
showToast('Fichier supprimé', 'success');
|
||||||
this._closeModal(overlay);
|
this._closeModal(overlay);
|
||||||
await refreshVaultTree();
|
await refreshSidebarTreePreservingState();
|
||||||
|
|
||||||
if (currentVault === vault && currentPath === path) {
|
if (currentVault === vault && currentPath === path) {
|
||||||
showWelcome();
|
showWelcome();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user