From ce7930bcc40e06d4667679711eab757037667f43 Mon Sep 17 00:00:00 2001 From: Cory Date: Tue, 23 Oct 2018 06:25:44 -0700 Subject: [PATCH] Added share functionality to cipher add-edit component (#758) --- src/_locales/en/messages.json | 6 ++ src/popup/app-routing.module.ts | 7 ++ src/popup/app.module.ts | 2 + src/popup/vault/add-edit.component.html | 14 +++ src/popup/vault/add-edit.component.ts | 4 + src/popup/vault/share.component.html | 63 ++++++++++++ src/popup/vault/share.component.ts | 123 ++++++++++++++++++++++++ 7 files changed, 219 insertions(+) create mode 100644 src/popup/vault/share.component.html create mode 100644 src/popup/vault/share.component.ts diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index bc30dd45a1..09bbde1698 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -548,6 +548,12 @@ "shareVaultConfirmation": { "message": "Bitwarden allows you to share your vault with others by using an organization account. Would you like to visit the bitwarden.com website to learn more?" }, + "shareItem": { + "message": "Share Item" + }, + "sharedItem": { + "message": "Shared Item" + }, "learnMore": { "message": "Learn more" }, diff --git a/src/popup/app-routing.module.ts b/src/popup/app-routing.module.ts index eb432645e8..ae44e51c58 100644 --- a/src/popup/app-routing.module.ts +++ b/src/popup/app-routing.module.ts @@ -29,6 +29,7 @@ import { SyncComponent } from './settings/sync.component'; import { TabsComponent } from './tabs.component'; import { AddEditComponent } from './vault/add-edit.component'; import { AttachmentsComponent } from './vault/attachments.component'; +import { ShareComponent } from './vault/share.component'; import { CiphersComponent } from './vault/ciphers.component'; import { CurrentTabComponent } from './vault/current-tab.component'; import { GroupingsComponent } from './vault/groupings.component'; @@ -123,6 +124,12 @@ const routes: Routes = [ canActivate: [AuthGuardService], data: { state: 'edit-cipher' }, }, + { + path: 'share', + component: ShareComponent, + canActivate: [AuthGuardService], + data: { state: 'share' }, + }, { path: 'attachments', component: AttachmentsComponent, diff --git a/src/popup/app.module.ts b/src/popup/app.module.ts index 3fa4ec7606..77c30a0758 100644 --- a/src/popup/app.module.ts +++ b/src/popup/app.module.ts @@ -36,6 +36,7 @@ import { SyncComponent } from './settings/sync.component'; import { TabsComponent } from './tabs.component'; import { AddEditComponent } from './vault/add-edit.component'; import { AttachmentsComponent } from './vault/attachments.component'; +import { ShareComponent } from './vault/share.component'; import { CiphersComponent } from './vault/ciphers.component'; import { CurrentTabComponent } from './vault/current-tab.component'; import { GroupingsComponent } from './vault/groupings.component'; @@ -177,6 +178,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); RegisterComponent, SearchCiphersPipe, SettingsComponent, + ShareComponent, StopClickDirective, StopPropDirective, SyncComponent, diff --git a/src/popup/vault/add-edit.component.html b/src/popup/vault/add-edit.component.html index e3308ccd63..026a242410 100644 --- a/src/popup/vault/add-edit.component.html +++ b/src/popup/vault/add-edit.component.html @@ -316,6 +316,20 @@ +
+ +
+
+
+ +
+
+ {{'share' | i18n}} +
+
+ +
+
+ +
+
+ {{'itemInformation' | i18n}} +
+
+
+
+ + +
+
+
+
+
+
+ {{'organization' | i18n}} +
+
+ {{'noOrganizationsList' | i18n}} +
+
+

{{'shareDesc' | i18n}}

+
+ +
+
+
+
+
+ {{'collections' | i18n}} +
+
+ {{'noCollectionsInList' | i18n}} +
+
+
+
+ + +
+
+
+
+
+ \ No newline at end of file diff --git a/src/popup/vault/share.component.ts b/src/popup/vault/share.component.ts new file mode 100644 index 0000000000..e6c177b8b4 --- /dev/null +++ b/src/popup/vault/share.component.ts @@ -0,0 +1,123 @@ +import { Location } from '@angular/common'; +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { + ActivatedRoute, + Router, +} from '@angular/router'; + +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 { UserService } from 'jslib/abstractions/user.service'; +import { AuditService } from 'jslib/abstractions/audit.service'; +import { FolderService } from 'jslib/abstractions/folder.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { StateService } from 'jslib/abstractions/state.service'; + +import { Organization } from 'jslib/models/domain/organization'; +import { CipherView } from 'jslib/models/view/cipherView'; +import { CollectionView } from 'jslib/models/view/collectionView'; + +@Component({ + selector: 'app-vault-share', + templateUrl: 'share.component.html', +}) +export class ShareComponent implements OnInit, OnDestroy { + formPromise: Promise; + cipher: CipherView; + cipherId: string; + organizationId: string; + collections: CollectionView[] = []; + organizations: Organization[] = []; + + private writeableCollections: CollectionView[] = []; + + constructor(private cipherService: CipherService, private collectionService: CollectionService, + private userService: UserService, private i18nService: I18nService, + private route: ActivatedRoute, private location: Location, + private toasterService: ToasterService, private analytics: Angulartics2) { + } + + async ngOnInit() { + this.route.queryParams.subscribe(async (params) => { + if (params.cipherId) { + this.cipherId = params.cipherId; + } + }); + + + const cipherDomain = await this.cipherService.get(this.cipherId); + this.cipher = await cipherDomain.decrypt(); + const allCollections = await this.collectionService.getAllDecrypted(); + this.writeableCollections = allCollections.filter((c) => !c.readOnly); + this.organizations = await this.userService.getAllOrganizations(); + if (this.organizationId == null && this.organizations.length > 0) { + this.organizationId = this.organizations[0].id; + } + this.filterCollections(); + } + + ngOnDestroy() { + } + + filterCollections() { + if (this.organizationId == null || this.writeableCollections.length === 0) { + this.collections = []; + } else { + this.collections = this.writeableCollections.filter((c) => c.organizationId === this.organizationId); + } + } + + async submit() { + const cipherDomain = await this.cipherService.get(this.cipherId); + const cipherView = await cipherDomain.decrypt(); + + const attachmentPromises: Array> = []; + if (cipherView.attachments != null) { + for (const attachment of cipherView.attachments) { + const promise = this.cipherService.shareAttachmentWithServer(attachment, + cipherView.id, this.organizationId); + attachmentPromises.push(promise); + } + } + + const checkedCollectionIds = this.collections.filter((c) => (c as any).checked).map((c) => c.id); + try { + this.formPromise = Promise.all(attachmentPromises).then(async () => { + await this.cipherService.shareWithServer(cipherView, this.organizationId, checkedCollectionIds); + this.analytics.eventTrack.next({ action: 'Shared Cipher' }); + this.toasterService.popAsync('success', null, this.i18nService.t('sharedItem')); + }); + await this.formPromise; + } catch { } + } + + cancel() { + this.location.back(); + } + + check(c: CollectionView, select?: boolean) { + (c as any).checked = select == null ? !(c as any).checked : select; + } + + get canSave() { + if (this.collections != null) { + for (let i = 0; i < this.collections.length; i++) { + if ((this.collections[i] as any).checked) { + return true; + } + } + } + return false; + } +}