diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index dbd60de8ae..5ba5ad3f55 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -498,7 +498,7 @@ "message": "Are you sure you want to delete this item?" }, "deletedItem": { - "message": "Deleted item" + "message": "Sent item to trash" }, "overwritePassword": { "message": "Overwrite Password" @@ -1258,6 +1258,31 @@ "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" + }, "vaultTimeoutLogOutConfirmation": { "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" }, 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..ec4053beb2 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 = 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..8b989c4c6a 100644 --- a/src/popup/vault/view.component.html +++ b/src/popup/vault/view.component.html @@ -6,7 +6,7 @@ {{'viewItem' | i18n}}
- +
@@ -259,9 +259,10 @@ - - \ 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(); } diff --git a/src/services/browserPlatformUtils.service.spec.ts b/src/services/browserPlatformUtils.service.spec.ts index 8a33f811d0..01cbbb219b 100644 --- a/src/services/browserPlatformUtils.service.spec.ts +++ b/src/services/browserPlatformUtils.service.spec.ts @@ -24,7 +24,7 @@ describe('Browser Utils Service', () => { it('should detect chrome', () => { Object.defineProperty(navigator, 'userAgent', { configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', }); const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); @@ -34,7 +34,7 @@ describe('Browser Utils Service', () => { it('should detect firefox', () => { Object.defineProperty(navigator, 'userAgent', { configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0' + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0', }); const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); @@ -44,12 +44,12 @@ describe('Browser Utils Service', () => { it('should detect opera', () => { Object.defineProperty(navigator, 'userAgent', { configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3175.3 Safari/537.36 OPR/49.0.2695.0 (Edition developer)' + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3175.3 Safari/537.36 OPR/49.0.2695.0 (Edition developer)', }); Object.defineProperty(window, 'opr', { configurable: true, - value: {} + value: {}, }); const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); @@ -59,7 +59,7 @@ describe('Browser Utils Service', () => { it('should detect edge', () => { Object.defineProperty(navigator, 'userAgent', { configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ServiceUI 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063' + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ServiceUI 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063', }); const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null); @@ -69,7 +69,7 @@ describe('Browser Utils Service', () => { it('should detect safari', () => { Object.defineProperty(navigator, 'userAgent', { configurable: true, - value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8' + value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8', }); Object.defineProperty(window, 'safariAppExtension', { @@ -89,7 +89,7 @@ describe('Browser Utils Service', () => { it('should detect vivaldi', () => { Object.defineProperty(navigator, 'userAgent', { configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.97 Safari/537.36 Vivaldi/1.94.1008.40' + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.97 Safari/537.36 Vivaldi/1.94.1008.40', }); const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);