From 411630296536340c4db998b1dd96faacc619e59e Mon Sep 17 00:00:00 2001 From: Chad Scharf <3904944+cscharf@users.noreply.github.com> Date: Mon, 13 Apr 2020 10:26:11 -0400 Subject: [PATCH] [Soft Delete] - Trash bin in browser extension --- src/_locales/en/messages.json | 25 +++++++++++++++++++++ src/popup/vault/ciphers.component.ts | 9 +++++++- src/popup/vault/groupings.component.html | 17 ++++++++++++++ src/popup/vault/groupings.component.ts | 19 ++++++++++++++-- src/popup/vault/view.component.html | 28 ++++++++++++++++++++---- src/popup/vault/view.component.ts | 25 +++++++++++++++++++++ 6 files changed, 116 insertions(+), 7 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 4bdb0c6edd..669bb879c1 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1257,5 +1257,30 @@ "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccesible by" + }, + "trash": { + "message": "Trash", + "description": "Noun: a special folder to hold deleted items" + }, + "searchTrash": { + "message": "Search trash" + }, + "permanentlyDeleteItem": { + "message": "Permanently Delete Item" + }, + "permanentlyDeleteItemConfirmation": { + "message": "Are you sure you want to permanently delete this item?" + }, + "permanentlyDeletedItem": { + "message": "Permanently Deleted item" + }, + "restoreItem": { + "message": "Restore Item" + }, + "restoreItemConfirmation": { + "message": "Are you sure you want to restore this item?" + }, + "restoredItem": { + "message": "Restored Item" } } diff --git a/src/popup/vault/ciphers.component.ts b/src/popup/vault/ciphers.component.ts index b64ea9ba61..cdeb6fe90a 100644 --- a/src/popup/vault/ciphers.component.ts +++ b/src/popup/vault/ciphers.component.ts @@ -79,7 +79,11 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On } } - if (params.type) { + if (params.deleted) { + this.groupingTitle = this.i18nService.t('trash'); + this.searchPlaceholder = this.i18nService.t('searchTrash'); + await this.load(null, true); + } else if (params.type) { this.searchPlaceholder = this.i18nService.t('searchType'); this.type = parseInt(params.type, null); switch (this.type) { @@ -198,6 +202,9 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On } addCipher() { + if (this.deleted) { + return false; + } super.addCipher(); this.router.navigate(['/add-cipher'], { queryParams: { diff --git a/src/popup/vault/groupings.component.html b/src/popup/vault/groupings.component.html index 68ab75c880..d576d19e87 100644 --- a/src/popup/vault/groupings.component.html +++ b/src/popup/vault/groupings.component.html @@ -121,6 +121,23 @@ (onSelected)="selectCipher($event)" (onDoubleSelected)="launchCipher($event)"> +
+
+ {{'trash' | i18n}} + {{deletedCount}} +
+
+ +
+
+ {{'trash' | i18n}} +
+ {{deletedCount}} + +
+
+
diff --git a/src/popup/vault/groupings.component.ts b/src/popup/vault/groupings.component.ts index ba54e9c106..009695af95 100644 --- a/src/popup/vault/groupings.component.ts +++ b/src/popup/vault/groupings.component.ts @@ -57,6 +57,7 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit showLeftHeader = true; searchPending = false; searchTypeSearch = false; + deletedCount: number = 0; private loadedTimeout: number; private selectedTimeout: number; @@ -167,6 +168,7 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit if (!this.hasLoadedAllCiphers) { this.hasLoadedAllCiphers = !this.searchService.isSearchable(this.searchText); } + this.deletedCount = this.allCiphers.filter((c) => c.isDeleted).length; await this.search(null); let favoriteCiphers: CipherView[] = null; let noFolderCiphers: CipherView[] = null; @@ -175,6 +177,9 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit const typeCounts = new Map(); this.ciphers.forEach((c) => { + if (c.isDeleted) { + return; + } if (c.favorite) { if (favoriteCiphers == null) { favoriteCiphers = []; @@ -224,9 +229,10 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit if (this.searchTimeout != null) { clearTimeout(this.searchTimeout); } + const filterDeleted = (c: CipherView) => !c.isDeleted; if (timeout == null) { this.hasSearched = this.searchService.isSearchable(this.searchText); - this.ciphers = await this.searchService.searchCiphers(this.searchText, null, this.allCiphers); + this.ciphers = await this.searchService.searchCiphers(this.searchText, filterDeleted, this.allCiphers); return; } this.searchPending = true; @@ -235,7 +241,7 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit if (!this.hasLoadedAllCiphers && !this.hasSearched) { await this.loadCiphers(); } else { - this.ciphers = await this.searchService.searchCiphers(this.searchText, null, this.allCiphers); + this.ciphers = await this.searchService.searchCiphers(this.searchText, filterDeleted, this.allCiphers); } this.searchPending = false; }, timeout); @@ -256,6 +262,11 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit this.router.navigate(['/ciphers'], { queryParams: { collectionId: collection.id } }); } + async selectTrash() { + super.selectTrash(); + this.router.navigate(['/ciphers'], { queryParams: { deleted: true } }); + } + async selectCipher(cipher: CipherView) { this.selectedTimeout = window.setTimeout(() => { if (!this.preventSelected) { @@ -305,6 +316,7 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit typeCounts: this.typeCounts, folders: this.folders, collections: this.collections, + deletedCount: this.deletedCount, }; await this.stateService.save(ScopeStateId, this.scopeState); } @@ -339,6 +351,9 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit if (this.scopeState.collections != null) { this.collections = this.scopeState.collections; } + if (this.scopeState.deletedCiphers != null) { + this.deletedCount = this.scopeState.deletedCount; + } return true; } diff --git a/src/popup/vault/view.component.html b/src/popup/vault/view.component.html index c7b0779b52..6de6725658 100644 --- a/src/popup/vault/view.component.html +++ b/src/popup/vault/view.component.html @@ -6,7 +6,7 @@ {{'viewItem' | i18n}}
- +
@@ -259,8 +259,8 @@ -
-
+ - \ No newline at end of file + diff --git a/src/popup/vault/view.component.ts b/src/popup/vault/view.component.ts index 83e83dfe8a..f74ed34fe0 100644 --- a/src/popup/vault/view.component.ts +++ b/src/popup/vault/view.component.ts @@ -60,11 +60,17 @@ export class ViewComponent extends BaseViewComponent { } edit() { + if (this.cipher.isDeleted) { + return false; + } super.edit(); this.router.navigate(['/edit-cipher'], { queryParams: { cipherId: this.cipher.id } }); } clone() { + if (this.cipher.isDeleted) { + return false; + } super.clone(); this.router.navigate(['/clone-cipher'], { queryParams: { @@ -74,6 +80,25 @@ export class ViewComponent extends BaseViewComponent { }); } + async restore() { + if (!this.cipher.isDeleted) { + return false; + } + if (await super.restore()) { + this.close(); + return true; + } + return false; + } + + async delete() { + if (await super.delete()) { + this.close(); + return true; + } + return false; + } + close() { this.location.back(); }