diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 053d1da6d8..a62eea6336 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -35,6 +35,7 @@ import { ToolsComponent } from './tools/tools.component';
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
+import { BulkDeleteComponent } from './vault/bulk-delete.component';
import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
@@ -81,6 +82,7 @@ import { Folder } from 'jslib/models/domain';
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
+ BulkDeleteComponent,
CiphersComponent,
CollectionsComponent,
ExportComponent,
@@ -115,6 +117,7 @@ import { Folder } from 'jslib/models/domain';
entryComponents: [
AddEditComponent,
AttachmentsComponent,
+ BulkDeleteComponent,
CollectionsComponent,
FolderAddEditComponent,
ModalComponent,
diff --git a/src/app/vault/bulk-delete.component.html b/src/app/vault/bulk-delete.component.html
new file mode 100644
index 0000000000..0b26cec73c
--- /dev/null
+++ b/src/app/vault/bulk-delete.component.html
@@ -0,0 +1,25 @@
+
diff --git a/src/app/vault/bulk-delete.component.ts b/src/app/vault/bulk-delete.component.ts
new file mode 100644
index 0000000000..ae15150143
--- /dev/null
+++ b/src/app/vault/bulk-delete.component.ts
@@ -0,0 +1,34 @@
+import {
+ Component,
+ EventEmitter,
+ Input,
+ Output,
+} from '@angular/core';
+
+import { ToasterService } from 'angular2-toaster';
+import { Angulartics2 } from 'angulartics2';
+
+import { CipherService } from 'jslib/abstractions/cipher.service';
+import { I18nService } from 'jslib/abstractions/i18n.service';
+
+@Component({
+ selector: 'app-vault-bulk-delete',
+ templateUrl: 'bulk-delete.component.html',
+})
+export class BulkDeleteComponent {
+ @Input() cipherIds: string[] = [];
+ @Output() onDeleted = new EventEmitter();
+
+ formPromise: Promise;
+
+ constructor(private analytics: Angulartics2, private cipherService: CipherService,
+ private toasterService: ToasterService, private i18nService: I18nService) { }
+
+ async submit() {
+ this.formPromise = this.cipherService.deleteManyWithServer(this.cipherIds);
+ await this.formPromise;
+ this.onDeleted.emit();
+ this.analytics.eventTrack.next({ action: 'Bulk Deleted Items' });
+ this.toasterService.popAsync('success', null, this.i18nService.t('deletedItems'));
+ }
+}
diff --git a/src/app/vault/ciphers.component.ts b/src/app/vault/ciphers.component.ts
index 89ccc21f57..1bc374de93 100644
--- a/src/app/vault/ciphers.component.ts
+++ b/src/app/vault/ciphers.component.ts
@@ -17,6 +17,8 @@ import { CipherType } from 'jslib/enums/cipherType';
import { CipherView } from 'jslib/models/view/cipherView';
+const MaxCheckedCount = 500;
+
@Component({
selector: 'app-vault-ciphers',
templateUrl: 'ciphers.component.html',
@@ -25,6 +27,7 @@ export class CiphersComponent extends BaseCiphersComponent {
@Output() onAttachmentsClicked = new EventEmitter();
@Output() onShareClicked = new EventEmitter();
@Output() onCollectionsClicked = new EventEmitter();
+
cipherType = CipherType;
constructor(cipherService: CipherService, private analytics: Angulartics2,
@@ -37,6 +40,23 @@ export class CiphersComponent extends BaseCiphersComponent {
(c as any).checked = !(c as any).checked;
}
+ selectAll(select: boolean) {
+ if (select) {
+ this.selectAll(false);
+ }
+ const selectCount = select && this.ciphers.length > MaxCheckedCount ? MaxCheckedCount : this.ciphers.length;
+ for (let i = 0; i < selectCount; i++) {
+ (this.ciphers[i] as any).checked = select;
+ }
+ }
+
+ getSelected(): string[] {
+ if (this.ciphers == null) {
+ return [];
+ }
+ return this.ciphers.filter((c) => !!(c as any).checked).map((c) => c.id);
+ }
+
attachments(c: CipherView) {
this.onAttachmentsClicked.emit(c);
}
diff --git a/src/app/vault/vault.component.html b/src/app/vault/vault.component.html
index f1cca02744..499ab5a277 100644
--- a/src/app/vault/vault.component.html
+++ b/src/app/vault/vault.component.html
@@ -11,35 +11,35 @@
{{'myVault' | i18n}}
-
-
-
@@ -71,3 +71,6 @@
+
+
+
diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts
index 0865143e7e..f7fa729091 100644
--- a/src/app/vault/vault.component.ts
+++ b/src/app/vault/vault.component.ts
@@ -20,6 +20,7 @@ import { ModalComponent } from '../modal.component';
import { AddEditComponent } from './add-edit.component';
import { AttachmentsComponent } from './attachments.component';
+import { BulkDeleteComponent } from './bulk-delete.component';
import { CiphersComponent } from './ciphers.component';
import { CollectionsComponent } from './collections.component';
import { FolderAddEditComponent } from './folder-add-edit.component';
@@ -43,6 +44,7 @@ export class VaultComponent implements OnInit {
@ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditModalRef: ViewContainerRef;
@ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef;
@ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef;
+ @ViewChild('bulkDeleteTemplate', { read: ViewContainerRef }) bulkDeleteModalRef: ViewContainerRef;
cipherId: string = null;
favorites: boolean = false;
@@ -277,6 +279,39 @@ export class VaultComponent implements OnInit {
return childComponent;
}
+ bulkDelete() {
+ if (this.modal != null) {
+ this.modal.close();
+ }
+
+ const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
+ this.modal = this.bulkDeleteModalRef.createComponent(factory).instance;
+ const childComponent = this.modal.show
(
+ BulkDeleteComponent, this.bulkDeleteModalRef);
+
+ childComponent.cipherIds = this.ciphersComponent.getSelected();
+ childComponent.onDeleted.subscribe(async () => {
+ this.modal.close();
+ await this.ciphersComponent.refresh();
+ });
+
+ this.modal.onClosed.subscribe(() => {
+ this.modal = null;
+ });
+ }
+
+ bulkShare() {
+ //
+ }
+
+ bulkMove() {
+ //
+ }
+
+ selectAll(select: boolean) {
+ this.ciphersComponent.selectAll(select);
+ }
+
private clearFilters() {
this.folderId = null;
this.collectionId = null;
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index 8ef34bcb8e..c08293f28e 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -419,6 +419,9 @@
"sharedItem": {
"message": "Shared item"
},
+ "sharedItems": {
+ "message": "Shared items"
+ },
"deleteItem": {
"message": "Delete Item"
},
@@ -434,6 +437,12 @@
"deletedItem": {
"message": "Deleted item"
},
+ "deletedItems": {
+ "message": "Deleted items"
+ },
+ "movedItems": {
+ "message": "Moved items"
+ },
"overwritePasswordConfirmation": {
"message": "Are you sure you want to overwrite the current password?"
},
@@ -664,5 +673,14 @@
},
"collectionsDesc": {
"message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item."
+ },
+ "deleteSelectedItemsDesc": {
+ "message": "You have selected $COUNT$ item(s) to delete. Are you sure you want to delete all of these items?",
+ "placeholders": {
+ "count": {
+ "content": "$1",
+ "example": "150"
+ }
+ }
}
}