From b528678900f1abd5fe21937e487efbee66339f17 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 1 Apr 2026 12:35:32 +0200 Subject: [PATCH] Update recursive folder delete logic (#1695) --- .../src/i18n/locales/en.json | 2 +- .../utils/db/repositories/FolderRepository.ts | 27 ++-------- .../Main/Pages/Items/Home.razor | 51 +++++++++++++++++-- .../Folders/DeleteFolderModal.en.resx | 2 +- .../Services/FolderService.cs | 1 + 5 files changed, 55 insertions(+), 28 deletions(-) diff --git a/apps/browser-extension/src/i18n/locales/en.json b/apps/browser-extension/src/i18n/locales/en.json index 2873333f5..4be1ad5c6 100644 --- a/apps/browser-extension/src/i18n/locales/en.json +++ b/apps/browser-extension/src/i18n/locales/en.json @@ -211,7 +211,7 @@ "allItemsInFolders": "All your items are organized in folders. Click a folder above to view your credentials, or use the search to find specific items.", "deleteFolder": "Delete Folder", "deleteFolderKeepItems": "Delete folder only", - "deleteFolderKeepItemsDescription": "Items will be moved back to the main list.", + "deleteFolderKeepItemsDescription": "Items will be moved to the parent folder.", "deleteFolderAndItems": "Delete folder and all items", "deleteFolderAndItemsDescription": "{{count}} item(s) will be moved to Recently Deleted.", "filters": { diff --git a/apps/browser-extension/src/utils/db/repositories/FolderRepository.ts b/apps/browser-extension/src/utils/db/repositories/FolderRepository.ts index e19b252d6..0ed75034f 100644 --- a/apps/browser-extension/src/utils/db/repositories/FolderRepository.ts +++ b/apps/browser-extension/src/utils/db/repositories/FolderRepository.ts @@ -109,9 +109,10 @@ export class FolderRepository extends BaseRepository { /** * Delete a folder (soft delete). - * Recursively handles child folders and items: - * - All items in this folder and child folders are moved to the parent folder (or root if no parent) - * - All child folders are moved to the parent of the deleted folder + * Handles child folders and items: + * - Items in this folder only are moved to the parent folder (or root if no parent) + * - Items in child folders stay in their respective folders (since child folders are moved to parent) + * - All direct child folders are moved to the parent of the deleted folder * @param folderId - The ID of the folder to delete * @returns The number of rows updated */ @@ -123,10 +124,7 @@ export class FolderRepository extends BaseRepository { const folder = this.getById(folderId); const targetParentId = folder?.ParentFolderId || null; - // Get all child folder IDs recursively - const allChildFolderIds = this.getAllChildFolderIds(folderId); - - // Move all items in this folder and all child folders to the parent folder (or root if no parent) + // Move only items in this folder to the parent folder (or root if no parent) if (targetParentId) { // Has parent: move items to parent folder this.client.executeUpdate(FolderQueries.MOVE_ITEMS_TO_FOLDER, [ @@ -134,27 +132,12 @@ export class FolderRepository extends BaseRepository { currentDateTime, folderId ]); - - for (const childFolderId of allChildFolderIds) { - this.client.executeUpdate(FolderQueries.MOVE_ITEMS_TO_FOLDER, [ - targetParentId, - currentDateTime, - childFolderId - ]); - } } else { // No parent: move items to root (NULL) this.client.executeUpdate(FolderQueries.CLEAR_ITEMS_FOLDER, [ currentDateTime, folderId ]); - - for (const childFolderId of allChildFolderIds) { - this.client.executeUpdate(FolderQueries.CLEAR_ITEMS_FOLDER, [ - currentDateTime, - childFolderId - ]); - } } // Move direct child folders to the parent of the deleted folder diff --git a/apps/server/AliasVault.Client/Main/Pages/Items/Home.razor b/apps/server/AliasVault.Client/Main/Pages/Items/Home.razor index 4c05b3032..d6fbcc22d 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Items/Home.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Items/Home.razor @@ -321,7 +321,7 @@ else OnDeleteFolderOnly="DeleteFolderOnlyAsync" OnDeleteFolderAndContents="DeleteFolderAndContentsAsync" FolderName="@CurrentFolderName" - ItemCount="@FilteredAndSortedItems.Count()" /> + ItemCount="@CurrentFolderItemCount" /> @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Items.Home", "AliasVault.Client"); @@ -392,6 +392,25 @@ else /// private string CurrentFolderName { get; set; } = string.Empty; + /// + /// Gets the total item count in the current folder tree (includes items in all subfolders recursively). + /// Used for the delete folder modal to determine if "delete with contents" option should be shown. + /// + private int CurrentFolderItemCount + { + get + { + if (!FolderId.HasValue) + { + return FilteredAndSortedItems.Count(); + } + + // Find the current folder in the Folders list and return its recursive count + var currentFolder = Folders.FirstOrDefault(f => f.Id == FolderId.Value); + return currentFolder?.ItemCount ?? 0; + } + } + /// /// Gets or sets whether the settings dropdown is shown. /// @@ -889,16 +908,28 @@ else } /// - /// Delete the current folder only (move items to root). + /// Delete the current folder only (move items to parent folder). /// private async Task DeleteFolderOnlyAsync() { if (FolderId.HasValue) { + // Get the parent folder ID before deletion + var folder = await FolderService.GetByIdAsync(FolderId.Value); + var parentFolderId = folder?.ParentFolderId; + var success = await FolderService.DeleteAsync(FolderId.Value); if (success) { - NavigationManager.NavigateTo("/items"); + // Navigate to parent folder if it exists, otherwise root + if (parentFolderId.HasValue) + { + NavigationManager.NavigateTo($"/items/folder/{parentFolderId.Value}"); + } + else + { + NavigationManager.NavigateTo("/items"); + } } else { @@ -914,10 +945,22 @@ else { if (FolderId.HasValue) { + // Get the parent folder ID before deletion + var folder = await FolderService.GetByIdAsync(FolderId.Value); + var parentFolderId = folder?.ParentFolderId; + var success = await FolderService.DeleteWithContentsAsync(FolderId.Value); if (success) { - NavigationManager.NavigateTo("/items"); + // Navigate to parent folder if it exists, otherwise root + if (parentFolderId.HasValue) + { + NavigationManager.NavigateTo($"/items/folder/{parentFolderId.Value}"); + } + else + { + NavigationManager.NavigateTo("/items"); + } } else { diff --git a/apps/server/AliasVault.Client/Resources/Components/Folders/DeleteFolderModal.en.resx b/apps/server/AliasVault.Client/Resources/Components/Folders/DeleteFolderModal.en.resx index 44cace688..71bff6b97 100644 --- a/apps/server/AliasVault.Client/Resources/Components/Folders/DeleteFolderModal.en.resx +++ b/apps/server/AliasVault.Client/Resources/Components/Folders/DeleteFolderModal.en.resx @@ -72,7 +72,7 @@ Title for delete folder only option - Items in this folder will be moved to root + Items will be moved to the parent folder Description for delete folder only option diff --git a/apps/server/AliasVault.Client/Services/FolderService.cs b/apps/server/AliasVault.Client/Services/FolderService.cs index 1a193e518..48843cfab 100644 --- a/apps/server/AliasVault.Client/Services/FolderService.cs +++ b/apps/server/AliasVault.Client/Services/FolderService.cs @@ -233,6 +233,7 @@ public sealed class FolderService(DbService dbService) foreach (var item in itemsInFolders) { item.DeletedAt = currentDateTime; + item.FolderId = null; item.UpdatedAt = currentDateTime; }