diff --git a/jslib b/jslib
index 72e3893f8e..e9db844285 160000
--- a/jslib
+++ b/jslib
@@ -1 +1 @@
-Subproject commit 72e3893f8eee79f1e3678839aa194f1096c343ea
+Subproject commit e9db844285e21525f5152e782063f04e02543553
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)">
+
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);