mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
share and collection management
This commit is contained in:
2
jslib
2
jslib
Submodule jslib updated: 8e377050e9...2f510a7988
@@ -43,12 +43,14 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||
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 { ExportComponent } from './vault/export.component';
|
||||
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
|
||||
import { GroupingsComponent } from './vault/groupings.component';
|
||||
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component';
|
||||
import { PasswordGeneratorComponent } from './vault/password-generator.component';
|
||||
import { PasswordHistoryComponent } from './vault/password-history.component';
|
||||
import { ShareComponent } from './vault/share.component';
|
||||
import { VaultComponent } from './vault/vault.component';
|
||||
import { ViewComponent } from './vault/view.component';
|
||||
|
||||
@@ -138,6 +140,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
BlurClickDirective,
|
||||
BoxRowDirective,
|
||||
CiphersComponent,
|
||||
CollectionsComponent,
|
||||
EnvironmentComponent,
|
||||
ExportComponent,
|
||||
FallbackSrcDirective,
|
||||
@@ -156,6 +159,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
RegisterComponent,
|
||||
SearchCiphersPipe,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
StopClickDirective,
|
||||
StopPropDirective,
|
||||
TrueFalseValueDirective,
|
||||
@@ -166,6 +170,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
],
|
||||
entryComponents: [
|
||||
AttachmentsComponent,
|
||||
CollectionsComponent,
|
||||
EnvironmentComponent,
|
||||
ExportComponent,
|
||||
FolderAddEditComponent,
|
||||
@@ -175,6 +180,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
PasswordHistoryComponent,
|
||||
PremiumComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
TwoFactorOptionsComponent,
|
||||
],
|
||||
providers: [],
|
||||
|
||||
@@ -240,6 +240,11 @@
|
||||
<div class="row-main">{{'attachments' | i18n}}</div>
|
||||
<i class="fa fa-chevron-right row-sub-icon"></i>
|
||||
</a>
|
||||
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
|
||||
(click)="editCollections()" *ngIf="editMode && cipher.organizationId">
|
||||
<div class="row-main">{{'collections' | i18n}}</div>
|
||||
<i class="fa fa-chevron-right row-sub-icon"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
@@ -309,6 +314,10 @@
|
||||
{{'cancel' | i18n}}
|
||||
</button>
|
||||
<div class="right">
|
||||
<button appBlurClick type="button" (click)="share()" title="{{'shareItem' | i18n}}"
|
||||
*ngIf="editMode && cipher && !cipher.organizationId">
|
||||
<i class="fa fa-share-alt fa-lg fa-fw"></i>
|
||||
</button>
|
||||
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
|
||||
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
[appApiAction]="deletePromise">
|
||||
|
||||
31
src/app/vault/collections.component.html
Normal file
31
src/app/vault/collections.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-body">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
{{'collections' | i18n}}
|
||||
</div>
|
||||
<div class="box-content" *ngIf="!collections || !collections.length">
|
||||
{{'noCollectionsInList' | i18n}}
|
||||
</div>
|
||||
<div class="box-content" *ngIf="collections && collections.length">
|
||||
<div class="box-content-row box-content-row-checkbox"
|
||||
*ngFor="let c of collections; let i = index" appBoxRow>
|
||||
<label for="collection_{{i}}">{{c.name}}</label>
|
||||
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
|
||||
name="Collection[{{i}}].Checked">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}" [disabled]="form.loading">
|
||||
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
||||
</button>
|
||||
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
19
src/app/vault/collections.component.ts
Normal file
19
src/app/vault/collections.component.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from 'jslib/angular/components/collections.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-collections',
|
||||
templateUrl: 'collections.component.html',
|
||||
})
|
||||
export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
constructor(cipherService: CipherService, i18nService: I18nService,
|
||||
collectionService: CollectionService, platformUtilsService: PlatformUtilsService) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService);
|
||||
}
|
||||
}
|
||||
52
src/app/vault/share.component.html
Normal file
52
src/app/vault/share.component.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-body">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
{{'share' | i18n}}
|
||||
</div>
|
||||
<div class="box-content" *ngIf="!organizations || !organizations.length">
|
||||
{{'noOrganizationsList' | i18n}}
|
||||
</div>
|
||||
<div class="box-content" *ngIf="organizations && organizations.length">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="organization">{{'organization' | i18n}}</label>
|
||||
<select id="organization" name="OrganizationId" [(ngModel)]="organizationId"
|
||||
(change)="filterCollections()">
|
||||
<option *ngFor="let o of organizations" [ngValue]="o.id">{{o.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{'shareDesc' | i18n}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="organizations && organizations.length">
|
||||
<div class="box-header">
|
||||
{{'collections' | i18n}}
|
||||
</div>
|
||||
<div class="box-content" *ngIf="!collections || !collections.length">
|
||||
{{'noCollectionsInList' | i18n}}
|
||||
</div>
|
||||
<div class="box-content" *ngIf="collections && collections.length">
|
||||
<div class="box-content-row box-content-row-checkbox"
|
||||
*ngFor="let c of collections; let i = index" appBoxRow>
|
||||
<label for="collection_{{i}}">{{c.name}}</label>
|
||||
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
|
||||
name="Collection[{{i}}].Checked">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}"
|
||||
[disabled]="form.loading || !canSave" *ngIf="organizations && organizations.length">
|
||||
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
||||
</button>
|
||||
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
21
src/app/vault/share.component.ts
Normal file
21
src/app/vault/share.component.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { ShareComponent as BaseShareComponent } from 'jslib/angular/components/share.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-share',
|
||||
templateUrl: 'share.component.html',
|
||||
})
|
||||
export class ShareComponent extends BaseShareComponent {
|
||||
constructor(cipherService: CipherService, i18nService: I18nService,
|
||||
collectionService: CollectionService, userService: UserService,
|
||||
platformUtilsService: PlatformUtilsService) {
|
||||
super(collectionService, platformUtilsService, i18nService, userService, cipherService);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@
|
||||
(onDeletedCipher)="deletedCipher($event)"
|
||||
(onEditAttachments)="editCipherAttachments($event)"
|
||||
(onCancelled)="cancelledAddEdit($event)"
|
||||
(onShareCipher)="shareCipher($event)"
|
||||
(onEditCollections)="cipherCollections($event)"
|
||||
(onGeneratePassword)="openPasswordGenerator(true)">
|
||||
</app-vault-add-edit>
|
||||
<div id="logo" *ngIf="action !== 'add' && action !== 'edit' && action !== 'view'">
|
||||
@@ -41,6 +43,8 @@
|
||||
</div>
|
||||
<ng-template #passwordGenerator></ng-template>
|
||||
<ng-template #attachments></ng-template>
|
||||
<ng-template #collections></ng-template>
|
||||
<ng-template #share></ng-template>
|
||||
<ng-template #folderAddEdit></ng-template>
|
||||
<ng-template #passwordHistory></ng-template>
|
||||
<ng-template #exportVault></ng-template>
|
||||
|
||||
@@ -26,11 +26,13 @@ import { ModalComponent } from 'jslib/angular/components/modal.component';
|
||||
import { AddEditComponent } from './add-edit.component';
|
||||
import { AttachmentsComponent } from './attachments.component';
|
||||
import { CiphersComponent } from './ciphers.component';
|
||||
import { CollectionsComponent } from './collections.component';
|
||||
import { ExportComponent } from './export.component';
|
||||
import { FolderAddEditComponent } from './folder-add-edit.component';
|
||||
import { GroupingsComponent } from './groupings.component';
|
||||
import { PasswordGeneratorComponent } from './password-generator.component';
|
||||
import { PasswordHistoryComponent } from './password-history.component';
|
||||
import { ShareComponent } from './share.component';
|
||||
|
||||
import { CipherType } from 'jslib/enums/cipherType';
|
||||
|
||||
@@ -58,6 +60,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('folderAddEdit', { read: ViewContainerRef }) folderAddEditModalRef: ViewContainerRef;
|
||||
@ViewChild('passwordHistory', { read: ViewContainerRef }) passwordHistoryModalRef: ViewContainerRef;
|
||||
@ViewChild('exportVault', { read: ViewContainerRef }) exportVaultModalRef: ViewContainerRef;
|
||||
@ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef;
|
||||
@ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef;
|
||||
|
||||
action: string;
|
||||
cipherId: string = null;
|
||||
@@ -353,6 +357,45 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
shareCipher(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<ShareComponent>(ShareComponent, this.shareModalRef);
|
||||
childComponent.cipherId = cipher.id;
|
||||
|
||||
childComponent.onSharedCipher.subscribe(async () => {
|
||||
this.modal.close();
|
||||
this.viewCipher(cipher);
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
this.modal.onClosed.subscribe(async () => {
|
||||
this.modal = null;
|
||||
});
|
||||
}
|
||||
|
||||
cipherCollections(cipher: CipherView) {
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
}
|
||||
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||
this.modal = this.collectionsModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<CollectionsComponent>(CollectionsComponent, this.collectionsModalRef);
|
||||
childComponent.cipherId = cipher.id;
|
||||
|
||||
childComponent.onSavedCollections.subscribe(() => {
|
||||
this.modal.close();
|
||||
this.viewCipher(cipher);
|
||||
});
|
||||
this.modal.onClosed.subscribe(async () => {
|
||||
this.modal = null;
|
||||
});
|
||||
}
|
||||
|
||||
viewCipherPasswordHistory(cipher: CipherView) {
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
|
||||
@@ -38,6 +38,18 @@
|
||||
"shared": {
|
||||
"message": "Shared"
|
||||
},
|
||||
"share": {
|
||||
"message": "Share"
|
||||
},
|
||||
"shareItem": {
|
||||
"message": "Share Item"
|
||||
},
|
||||
"sharedItem": {
|
||||
"message": "Shared Item"
|
||||
},
|
||||
"shareDesc": {
|
||||
"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."
|
||||
},
|
||||
"attachments": {
|
||||
"message": "Attachments"
|
||||
},
|
||||
@@ -1100,5 +1112,11 @@
|
||||
},
|
||||
"exportMasterPassword": {
|
||||
"message": "Enter your master password to export your vault data."
|
||||
},
|
||||
"noOrganizationsList": {
|
||||
"message": "You do not belong to any organizations. Organizations allow you to securely share items with other users."
|
||||
},
|
||||
"noCollectionsInList": {
|
||||
"message": "There are no collections to list."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user