1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-27 07:39:49 +01:00

Merge pull request #446 from bitwarden/soft-delete

Soft delete feature
This commit is contained in:
Chad Scharf 2020-05-08 11:17:13 -04:00 committed by GitHub
commit 2a38350b99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 20 deletions

View File

@ -39,8 +39,8 @@
</ng-container> </ng-container>
</div> </div>
<div class="footer"> <div class="footer">
<button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()" class="block primary" <button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()"
appA11yTitle="{{'addItem' | i18n}}"> class="block primary" appA11yTitle="{{'addItem' | i18n}}" [disabled]="deleted">
<i class="fa fa-plus fa-lg" aria-hidden="true"></i> <i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button> </button>
</div> </div>

View File

@ -13,6 +13,11 @@
<i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{'favorites' | i18n}} <i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{'favorites' | i18n}}
</a> </a>
</li> </li>
<li [ngClass]="{active: selectedTrash}">
<a href="#" appStopClick appBlurClick (click)="selectTrash()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{'trash' | i18n}}
</a>
</li>
</ul> </ul>
<h2>{{'types' | i18n}}</h2> <h2>{{'types' | i18n}}</h2>
<ul> <ul>

View File

@ -2,7 +2,7 @@
<app-vault-groupings id="groupings" (onAllClicked)="clearGroupingFilters()" (onFavoritesClicked)="filterFavorites()" <app-vault-groupings id="groupings" (onAllClicked)="clearGroupingFilters()" (onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)" (onFolderClicked)="filterFolder($event.id)" (onCipherTypeClicked)="filterCipherType($event)" (onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()" (onEditFolder)="editFolder($event.id)" (onAddFolder)="addFolder()" (onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)"> (onCollectionClicked)="filterCollection($event.id)" (onTrashClicked)="filterDeleted()">
</app-vault-groupings> </app-vault-groupings>
<app-vault-ciphers id="items" [activeCipherId]="cipherId" (onCipherClicked)="viewCipher($event)" <app-vault-ciphers id="items" [activeCipherId]="cipherId" (onCipherClicked)="viewCipher($event)"
(onCipherRightClicked)="viewCipherMenu($event)" (onAddCipher)="addCipher($event)" (onCipherRightClicked)="viewCipherMenu($event)" (onAddCipher)="addCipher($event)"
@ -10,7 +10,8 @@
</app-vault-ciphers> </app-vault-ciphers>
<app-vault-view id="details" *ngIf="cipherId && action === 'view'" [cipherId]="cipherId" <app-vault-view id="details" *ngIf="cipherId && action === 'view'" [cipherId]="cipherId"
(onCloneCipher)="cloneCipher($event)" (onEditCipher)="editCipher($event)" (onCloneCipher)="cloneCipher($event)" (onEditCipher)="editCipher($event)"
(onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)"> (onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" (onRestoredCipher)="restoredCipher($event)"
(onDeletedCipher)="deletedCipher($event)">
</app-vault-view> </app-vault-view>
<app-vault-add-edit id="details" *ngIf="action === 'add' || action === 'edit' || action === 'clone'" <app-vault-add-edit id="details" *ngIf="action === 'add' || action === 'edit' || action === 'clone'"
[cloneMode]="action === 'clone'" [cloneMode]="action === 'clone'"

View File

@ -76,6 +76,7 @@ export class VaultComponent implements OnInit, OnDestroy {
addOrganizationId: string = null; addOrganizationId: string = null;
addCollectionIds: string[] = null; addCollectionIds: string[] = null;
showingModal = false; showingModal = false;
deleted = false;
private modal: ModalComponent = null; private modal: ModalComponent = null;
@ -218,7 +219,10 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.addCipher(); await this.addCipher();
} }
if (params.favorites) { if (params.deleted) {
this.groupingsComponent.selectedTrash = true;
await this.filterDeleted();
} else if (params.favorites) {
this.groupingsComponent.selectedFavorites = true; this.groupingsComponent.selectedFavorites = true;
await this.filterFavorites(); await this.filterFavorites();
} else if (params.type) { } else if (params.type) {
@ -263,18 +267,20 @@ export class VaultComponent implements OnInit, OnDestroy {
this.viewCipher(cipher); this.viewCipher(cipher);
}), }),
})); }));
menu.append(new remote.MenuItem({ if (!cipher.isDeleted) {
label: this.i18nService.t('edit'), menu.append(new remote.MenuItem({
click: () => this.functionWithChangeDetection(() => { label: this.i18nService.t('edit'),
this.editCipher(cipher); click: () => this.functionWithChangeDetection(() => {
}), this.editCipher(cipher);
})); }),
menu.append(new remote.MenuItem({ }));
label: this.i18nService.t('clone'), menu.append(new remote.MenuItem({
click: () => this.functionWithChangeDetection(() => { label: this.i18nService.t('clone'),
this.cloneCipher(cipher); click: () => this.functionWithChangeDetection(() => {
}), this.cloneCipher(cipher);
})); }),
}));
}
switch (cipher.type) { switch (cipher.type) {
case CipherType.Login: case CipherType.Login:
@ -402,6 +408,13 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.ciphersComponent.refresh(); await this.ciphersComponent.refresh();
} }
async restoredCipher(cipher: CipherView) {
this.cipherId = null;
this.action = null;
this.go();
await this.ciphersComponent.refresh();
}
editCipherAttachments(cipher: CipherView) { editCipherAttachments(cipher: CipherView) {
if (this.modal != null) { if (this.modal != null) {
this.modal.close(); this.modal.close();
@ -498,6 +511,15 @@ export class VaultComponent implements OnInit, OnDestroy {
this.go(); this.go();
} }
async filterDeleted() {
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchTrash');
this.ciphersComponent.deleted = true;
await this.ciphersComponent.reload(null, true);
this.clearFilters();
this.deleted = true;
this.go();
}
async filterCipherType(type: CipherType) { async filterCipherType(type: CipherType) {
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchType'); this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchType');
await this.ciphersComponent.reload((c) => c.type === type); await this.ciphersComponent.reload((c) => c.type === type);
@ -630,6 +652,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.addCollectionIds = null; this.addCollectionIds = null;
this.addType = null; this.addType = null;
this.addOrganizationId = null; this.addOrganizationId = null;
this.deleted = false;
} }
private go(queryParams: any = null) { private go(queryParams: any = null) {
@ -641,6 +664,7 @@ export class VaultComponent implements OnInit, OnDestroy {
type: this.type, type: this.type,
folderId: this.folderId, folderId: this.folderId,
collectionId: this.collectionId, collectionId: this.collectionId,
deleted: this.deleted ? true : null,
}; };
} }

View File

@ -273,10 +273,21 @@
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
<button appBlurClick class="primary" (click)="edit()" appA11yTitle="{{'edit' | i18n}}"> <button appBlurClick class="primary" (click)="edit()" appA11yTitle="{{'edit' | i18n}}" *ngIf="!cipher.isDeleted">
<i class="fa fa-pencil fa-fw fa-lg" aria-hidden="true"></i> <i class="fa fa-pencil fa-fw fa-lg" aria-hidden="true"></i>
</button> </button>
<button appBlurClick class="primary" *ngIf="!cipher?.organizationId" (click)="clone()" appA11yTitle="{{'clone' | i18n}}"> <button appBlurClick class="primary" (click)="restore()" appA11yTitle="{{'restore' | i18n}}"
*ngIf="cipher.isDeleted">
<i class="fa fa-undo fa-fw fa-lg" aria-hidden="true"></i>
</button>
<button appBlurClick class="primary" *ngIf="!cipher?.organizationId && !cipher.isDeleted" (click)="clone()"
appA11yTitle="{{'clone' | i18n}}">
<i class="fa fa-clone fa-fw fa-lg" aria-hidden="true"></i> <i class="fa fa-clone fa-fw fa-lg" aria-hidden="true"></i>
</button> </button>
<div class="right">
<button appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{(cipher.isDeleted ? 'permanentlyDelete' : 'delete') | i18n}}">
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
</div>
</div> </div>

View File

@ -343,7 +343,7 @@
"message": "Are you sure you want to delete this item?" "message": "Are you sure you want to delete this item?"
}, },
"deletedItem": { "deletedItem": {
"message": "Deleted item" "message": "Sent item to trash"
}, },
"overwritePasswordConfirmation": { "overwritePasswordConfirmation": {
"message": "Are you sure you want to overwrite the current password?" "message": "Are you sure you want to overwrite the current password?"
@ -1299,6 +1299,34 @@
"message": "Lock", "message": "Lock",
"description": "Verb form: to make secure or inaccesible by" "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"
},
"permanentlyDelete": {
"message": "Permanently Delete"
},
"vaultTimeoutLogOutConfirmation": { "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?" "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?"
}, },