diff --git a/jslib b/jslib
index b3f71ed8e4..1021f23277 160000
--- a/jslib
+++ b/jslib
@@ -1 +1 @@
-Subproject commit b3f71ed8e406008987c29b29b0366a7c0f246765
+Subproject commit 1021f23277db5a47ae9b7be8e8021ad5005a8c10
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 395a25751a..053d1da6d8 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -36,6 +36,7 @@ import { ToolsComponent } from './tools/tools.component';
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
import { CiphersComponent } from './vault/ciphers.component';
+import { CollectionsComponent } from './vault/collections.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
import { GroupingsComponent } from './vault/groupings.component';
import { OrganizationsComponent } from './vault/organizations.component';
@@ -81,6 +82,7 @@ import { Folder } from 'jslib/models/domain';
BlurClickDirective,
BoxRowDirective,
CiphersComponent,
+ CollectionsComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
@@ -113,6 +115,7 @@ import { Folder } from 'jslib/models/domain';
entryComponents: [
AddEditComponent,
AttachmentsComponent,
+ CollectionsComponent,
FolderAddEditComponent,
ModalComponent,
ShareComponent,
diff --git a/src/app/vault/collections.component.html b/src/app/vault/collections.component.html
new file mode 100644
index 0000000000..41255fd1f4
--- /dev/null
+++ b/src/app/vault/collections.component.html
@@ -0,0 +1,48 @@
+
diff --git a/src/app/vault/collections.component.ts b/src/app/vault/collections.component.ts
new file mode 100644
index 0000000000..bd2b86cab5
--- /dev/null
+++ b/src/app/vault/collections.component.ts
@@ -0,0 +1,85 @@
+import {
+ Component,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ OnInit,
+ Output,
+} from '@angular/core';
+
+import { ToasterService } from 'angular2-toaster';
+import { Angulartics2 } from 'angulartics2';
+
+import { CipherService } from 'jslib/abstractions/cipher.service';
+import { CollectionService } from 'jslib/abstractions/collection.service';
+import { I18nService } from 'jslib/abstractions/i18n.service';
+
+import { CipherView } from 'jslib/models/view/cipherView';
+import { CollectionView } from 'jslib/models/view/collectionView';
+
+import { Cipher } from 'jslib/models/domain/cipher';
+
+@Component({
+ selector: 'app-vault-collections',
+ templateUrl: 'collections.component.html',
+})
+export class CollectionsComponent implements OnInit, OnDestroy {
+ @Input() cipherId: string;
+ @Output() onSavedCollections = new EventEmitter();
+
+ formPromise: Promise;
+ cipher: CipherView;
+ collections: CollectionView[] = [];
+
+ private cipherDomain: Cipher;
+
+ constructor(private collectionService: CollectionService, private analytics: Angulartics2,
+ private toasterService: ToasterService, private i18nService: I18nService,
+ private cipherService: CipherService) { }
+
+ async ngOnInit() {
+ this.cipherDomain = await this.cipherService.get(this.cipherId);
+ this.cipher = await this.cipherDomain.decrypt();
+ const allCollections = await this.collectionService.getAllDecrypted();
+ this.collections = allCollections.filter((c) =>
+ !c.readOnly && c.organizationId === this.cipher.organizationId);
+
+ this.unselectAll();
+ if (this.cipherDomain.collectionIds != null) {
+ for (const collection of this.collections) {
+ (collection as any).checked = this.cipherDomain.collectionIds.indexOf(collection.id) > -1;
+ }
+ }
+ }
+
+ ngOnDestroy() {
+ this.unselectAll();
+ }
+
+ async submit() {
+ this.cipherDomain.collectionIds = this.collections
+ .filter((c) => !!(c as any).checked)
+ .map((c) => c.id);
+ this.formPromise = this.cipherService.saveCollectionsWithServer(this.cipherDomain);
+ await this.formPromise;
+ this.onSavedCollections.emit();
+ this.analytics.eventTrack.next({ action: 'Edited Cipher Collections' });
+ this.toasterService.popAsync('success', null, this.i18nService.t('editedItem'));
+ }
+
+ check(c: CollectionView) {
+ (c as any).checked = !(c as any).checked;
+ }
+
+ selectAll() {
+ for (const c of this.collections) {
+ (c as any).checked = true;
+ }
+ }
+
+ unselectAll() {
+ for (const c of this.collections) {
+ (c as any).checked = false;
+ }
+ }
+}
diff --git a/src/app/vault/vault.component.html b/src/app/vault/vault.component.html
index 4105ea03d4..f1cca02744 100644
--- a/src/app/vault/vault.component.html
+++ b/src/app/vault/vault.component.html
@@ -45,7 +45,7 @@
+ (onShareClicked)="shareCipher($event)" (onCollectionsClicked)="editCipherCollections($event)">
diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts
index 0d2ea05871..0865143e7e 100644
--- a/src/app/vault/vault.component.ts
+++ b/src/app/vault/vault.component.ts
@@ -21,13 +21,14 @@ import { ModalComponent } from '../modal.component';
import { AddEditComponent } from './add-edit.component';
import { AttachmentsComponent } from './attachments.component';
import { CiphersComponent } from './ciphers.component';
+import { CollectionsComponent } from './collections.component';
import { FolderAddEditComponent } from './folder-add-edit.component';
import { GroupingsComponent } from './groupings.component';
import { OrganizationsComponent } from './organizations.component';
+import { ShareComponent } from './share.component';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { SyncService } from 'jslib/abstractions/sync.service';
-import { ShareComponent } from './share.component';
@Component({
selector: 'app-vault',
@@ -177,6 +178,26 @@ export class VaultComponent implements OnInit {
});
}
+ editCipherCollections(cipher: CipherView) {
+ if (this.modal != null) {
+ this.modal.close();
+ }
+
+ const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
+ this.modal = this.shareModalRef.createComponent(factory).instance;
+ const childComponent = this.modal.show(CollectionsComponent, this.collectionsModalRef);
+
+ childComponent.cipherId = cipher.id;
+ childComponent.onSavedCollections.subscribe(async () => {
+ this.modal.close();
+ await this.ciphersComponent.refresh();
+ });
+
+ this.modal.onClosed.subscribe(async () => {
+ this.modal = null;
+ });
+ }
+
async addFolder() {
if (this.modal != null) {
this.modal.close();
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index ea5334b77d..8ef34bcb8e 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -660,6 +660,9 @@
"message": "Organizations"
},
"shareDesc": {
- "message": "Choose an organization that you wish to share this item with. Sharing an item transfers ownership of that item to the organization. You will no longer be the direct owner of this item once it has been shared."
+ "message": "Choose an organization that you wish to share this item with. Sharing transfers ownership of the item to the organization. You will no longer be the direct owner of this item once it has been shared."
+ },
+ "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."
}
}