feat(vault): add folder rename action in sidebar

This commit is contained in:
Shuai
2026-03-31 00:25:15 +08:00
parent 882fa2e8c8
commit 1184cb8d9a
8 changed files with 95 additions and 5 deletions
+2
View File
@@ -74,6 +74,7 @@ export interface AppMainRoutesProps {
onBulkMoveVaultItems: (ids: string[], folderId: string | null) => Promise<void>;
onVerifyMasterPassword: (email: string, password: string) => Promise<void>;
onCreateFolder: (name: string) => Promise<void>;
onRenameFolder: (folderId: string, name: string) => Promise<void>;
onDeleteFolder: (folderId: string) => Promise<void>;
onBulkDeleteFolders: (folderIds: string[]) => Promise<void>;
onDownloadVaultAttachment: (cipher: Cipher, attachmentId: string) => Promise<void>;
@@ -192,6 +193,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
onVerifyMasterPassword={props.onVerifyMasterPassword}
onNotify={props.onNotify}
onCreateFolder={props.onCreateFolder}
onRenameFolder={props.onRenameFolder}
onDeleteFolder={props.onDeleteFolder}
onBulkDeleteFolders={props.onBulkDeleteFolders}
onDownloadAttachment={props.onDownloadVaultAttachment}
+32 -4
View File
@@ -50,6 +50,7 @@ interface VaultPageProps {
onVerifyMasterPassword: (email: string, password: string) => Promise<void>;
onNotify: (type: 'success' | 'error' | 'warning', text: string) => void;
onCreateFolder: (name: string) => Promise<void>;
onRenameFolder: (folderId: string, name: string) => Promise<void>;
onDeleteFolder: (folderId: string) => Promise<void>;
onBulkDeleteFolders: (folderIds: string[]) => Promise<void>;
onDownloadAttachment: (cipher: Cipher, attachmentId: string) => Promise<void>;
@@ -91,6 +92,8 @@ export default function VaultPage(props: VaultPageProps) {
const [moveFolderId, setMoveFolderId] = useState('__none__');
const [createFolderOpen, setCreateFolderOpen] = useState(false);
const [newFolderName, setNewFolderName] = useState('');
const [pendingRenameFolder, setPendingRenameFolder] = useState<Folder | null>(null);
const [renameFolderName, setRenameFolderName] = useState('');
const [pendingDeleteFolder, setPendingDeleteFolder] = useState<Folder | null>(null);
const [deleteAllFoldersOpen, setDeleteAllFoldersOpen] = useState(false);
const [totpLive, setTotpLive] = useState<{ code: string; remain: number } | null>(null);
@@ -699,6 +702,23 @@ function folderName(id: string | null | undefined): string {
}
}
async function confirmRenameFolder(): Promise<void> {
if (!pendingRenameFolder) return;
const nextName = renameFolderName.trim();
if (!nextName) {
props.onNotify('error', t('txt_folder_name_is_required'));
return;
}
setBusy(true);
try {
await props.onRenameFolder(pendingRenameFolder.id, nextName);
setPendingRenameFolder(null);
setRenameFolderName('');
} finally {
setBusy(false);
}
}
async function confirmBulkRestore(): Promise<void> {
const ids = Object.entries(selectedMap)
.filter(([, selected]) => selected)
@@ -806,6 +826,10 @@ function folderName(id: string | null | undefined): string {
onChangeFilter={setSidebarFilter}
onOpenDeleteAllFolders={() => setDeleteAllFoldersOpen(true)}
onOpenCreateFolder={() => setCreateFolderOpen(true)}
onOpenRenameFolder={(folder) => {
setPendingRenameFolder(folder);
setRenameFolderName(folder.decName || folder.name || '');
}}
onOpenDeleteFolder={setPendingDeleteFolder}
/>
@@ -986,6 +1010,8 @@ function folderName(id: string | null | undefined): string {
folders={props.folders}
createFolderOpen={createFolderOpen}
newFolderName={newFolderName}
renameFolderOpen={!!pendingRenameFolder}
renameFolderName={renameFolderName}
pendingDeleteFolder={pendingDeleteFolder}
deleteAllFoldersOpen={deleteAllFoldersOpen}
repromptOpen={repromptOpen}
@@ -1036,6 +1062,12 @@ function folderName(id: string | null | undefined): string {
setNewFolderName('');
}}
onNewFolderNameChange={setNewFolderName}
onConfirmRenameFolder={() => void confirmRenameFolder()}
onCancelRenameFolder={() => {
setPendingRenameFolder(null);
setRenameFolderName('');
}}
onRenameFolderNameChange={setRenameFolderName}
onConfirmDeleteFolder={() => void confirmDeleteFolder()}
onCancelDeleteFolder={() => setPendingDeleteFolder(null)}
onConfirmDeleteAllFolders={() => void confirmDeleteAllFolders()}
@@ -1051,7 +1083,3 @@ function folderName(id: string | null | undefined): string {
);
}
@@ -19,6 +19,8 @@ interface VaultDialogsProps {
folders: Folder[];
createFolderOpen: boolean;
newFolderName: string;
renameFolderOpen: boolean;
renameFolderName: string;
pendingDeleteFolder: Folder | null;
deleteAllFoldersOpen: boolean;
repromptOpen: boolean;
@@ -42,6 +44,9 @@ interface VaultDialogsProps {
onConfirmCreateFolder: () => void;
onCancelCreateFolder: () => void;
onNewFolderNameChange: (value: string) => void;
onConfirmRenameFolder: () => void;
onCancelRenameFolder: () => void;
onRenameFolderNameChange: (value: string) => void;
onConfirmDeleteFolder: () => void;
onCancelDeleteFolder: () => void;
onConfirmDeleteAllFolders: () => void;
@@ -150,6 +155,13 @@ export default function VaultDialogs(props: VaultDialogsProps) {
</label>
</ConfirmDialog>
<ConfirmDialog open={props.renameFolderOpen} title={t('txt_edit')} message={t('txt_enter_a_folder_name')} confirmText={t('txt_save')} cancelText={t('txt_cancel')} onConfirm={props.onConfirmRenameFolder} onCancel={props.onCancelRenameFolder}>
<label className="field">
<span>{t('txt_folder_name')}</span>
<input className="input" value={props.renameFolderName} onInput={(e) => props.onRenameFolderNameChange((e.currentTarget as HTMLInputElement).value)} />
</label>
</ConfirmDialog>
<ConfirmDialog
open={!!props.pendingDeleteFolder}
title={t('txt_delete_folder')}
@@ -8,6 +8,7 @@ import {
Globe,
KeyRound,
LayoutGrid,
Pencil,
ShieldUser,
Star,
StickyNote,
@@ -28,6 +29,7 @@ interface VaultSidebarProps {
onChangeFilter: (filter: SidebarFilter) => void;
onOpenDeleteAllFolders: () => void;
onOpenCreateFolder: () => void;
onOpenRenameFolder: (folder: Folder) => void;
onOpenDeleteFolder: (folder: Folder) => void;
}
@@ -113,6 +115,20 @@ export default function VaultSidebar(props: VaultSidebarProps) {
{folder.decName || folder.name || folder.id}
</span>
</button>
<button
type="button"
className="folder-delete-btn folder-edit-btn"
title={t('txt_edit')}
aria-label={t('txt_edit')}
disabled={props.busy}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
props.onOpenRenameFolder(folder);
}}
>
<Pencil size={12} />
</button>
<button
type="button"
className="folder-delete-btn"