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 { AddEditComponent } from './vault/add-edit.component';
|
||||||
import { AttachmentsComponent } from './vault/attachments.component';
|
import { AttachmentsComponent } from './vault/attachments.component';
|
||||||
import { CiphersComponent } from './vault/ciphers.component';
|
import { CiphersComponent } from './vault/ciphers.component';
|
||||||
|
import { CollectionsComponent } from './vault/collections.component';
|
||||||
import { ExportComponent } from './vault/export.component';
|
import { ExportComponent } from './vault/export.component';
|
||||||
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
|
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
|
||||||
import { GroupingsComponent } from './vault/groupings.component';
|
import { GroupingsComponent } from './vault/groupings.component';
|
||||||
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component';
|
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component';
|
||||||
import { PasswordGeneratorComponent } from './vault/password-generator.component';
|
import { PasswordGeneratorComponent } from './vault/password-generator.component';
|
||||||
import { PasswordHistoryComponent } from './vault/password-history.component';
|
import { PasswordHistoryComponent } from './vault/password-history.component';
|
||||||
|
import { ShareComponent } from './vault/share.component';
|
||||||
import { VaultComponent } from './vault/vault.component';
|
import { VaultComponent } from './vault/vault.component';
|
||||||
import { ViewComponent } from './vault/view.component';
|
import { ViewComponent } from './vault/view.component';
|
||||||
|
|
||||||
@@ -138,6 +140,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
CiphersComponent,
|
CiphersComponent,
|
||||||
|
CollectionsComponent,
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
ExportComponent,
|
ExportComponent,
|
||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
@@ -156,6 +159,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||||||
RegisterComponent,
|
RegisterComponent,
|
||||||
SearchCiphersPipe,
|
SearchCiphersPipe,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
|
ShareComponent,
|
||||||
StopClickDirective,
|
StopClickDirective,
|
||||||
StopPropDirective,
|
StopPropDirective,
|
||||||
TrueFalseValueDirective,
|
TrueFalseValueDirective,
|
||||||
@@ -166,6 +170,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AttachmentsComponent,
|
AttachmentsComponent,
|
||||||
|
CollectionsComponent,
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
ExportComponent,
|
ExportComponent,
|
||||||
FolderAddEditComponent,
|
FolderAddEditComponent,
|
||||||
@@ -175,6 +180,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||||||
PasswordHistoryComponent,
|
PasswordHistoryComponent,
|
||||||
PremiumComponent,
|
PremiumComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
|
ShareComponent,
|
||||||
TwoFactorOptionsComponent,
|
TwoFactorOptionsComponent,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@@ -240,6 +240,11 @@
|
|||||||
<div class="row-main">{{'attachments' | i18n}}</div>
|
<div class="row-main">{{'attachments' | i18n}}</div>
|
||||||
<i class="fa fa-chevron-right row-sub-icon"></i>
|
<i class="fa fa-chevron-right row-sub-icon"></i>
|
||||||
</a>
|
</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>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
@@ -309,6 +314,10 @@
|
|||||||
{{'cancel' | i18n}}
|
{{'cancel' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
<div class="right">
|
<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"
|
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
|
||||||
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||||
[appApiAction]="deletePromise">
|
[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)"
|
(onDeletedCipher)="deletedCipher($event)"
|
||||||
(onEditAttachments)="editCipherAttachments($event)"
|
(onEditAttachments)="editCipherAttachments($event)"
|
||||||
(onCancelled)="cancelledAddEdit($event)"
|
(onCancelled)="cancelledAddEdit($event)"
|
||||||
|
(onShareCipher)="shareCipher($event)"
|
||||||
|
(onEditCollections)="cipherCollections($event)"
|
||||||
(onGeneratePassword)="openPasswordGenerator(true)">
|
(onGeneratePassword)="openPasswordGenerator(true)">
|
||||||
</app-vault-add-edit>
|
</app-vault-add-edit>
|
||||||
<div id="logo" *ngIf="action !== 'add' && action !== 'edit' && action !== 'view'">
|
<div id="logo" *ngIf="action !== 'add' && action !== 'edit' && action !== 'view'">
|
||||||
@@ -41,6 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<ng-template #passwordGenerator></ng-template>
|
<ng-template #passwordGenerator></ng-template>
|
||||||
<ng-template #attachments></ng-template>
|
<ng-template #attachments></ng-template>
|
||||||
|
<ng-template #collections></ng-template>
|
||||||
|
<ng-template #share></ng-template>
|
||||||
<ng-template #folderAddEdit></ng-template>
|
<ng-template #folderAddEdit></ng-template>
|
||||||
<ng-template #passwordHistory></ng-template>
|
<ng-template #passwordHistory></ng-template>
|
||||||
<ng-template #exportVault></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 { AddEditComponent } from './add-edit.component';
|
||||||
import { AttachmentsComponent } from './attachments.component';
|
import { AttachmentsComponent } from './attachments.component';
|
||||||
import { CiphersComponent } from './ciphers.component';
|
import { CiphersComponent } from './ciphers.component';
|
||||||
|
import { CollectionsComponent } from './collections.component';
|
||||||
import { ExportComponent } from './export.component';
|
import { ExportComponent } from './export.component';
|
||||||
import { FolderAddEditComponent } from './folder-add-edit.component';
|
import { FolderAddEditComponent } from './folder-add-edit.component';
|
||||||
import { GroupingsComponent } from './groupings.component';
|
import { GroupingsComponent } from './groupings.component';
|
||||||
import { PasswordGeneratorComponent } from './password-generator.component';
|
import { PasswordGeneratorComponent } from './password-generator.component';
|
||||||
import { PasswordHistoryComponent } from './password-history.component';
|
import { PasswordHistoryComponent } from './password-history.component';
|
||||||
|
import { ShareComponent } from './share.component';
|
||||||
|
|
||||||
import { CipherType } from 'jslib/enums/cipherType';
|
import { CipherType } from 'jslib/enums/cipherType';
|
||||||
|
|
||||||
@@ -58,6 +60,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
@ViewChild('folderAddEdit', { read: ViewContainerRef }) folderAddEditModalRef: ViewContainerRef;
|
@ViewChild('folderAddEdit', { read: ViewContainerRef }) folderAddEditModalRef: ViewContainerRef;
|
||||||
@ViewChild('passwordHistory', { read: ViewContainerRef }) passwordHistoryModalRef: ViewContainerRef;
|
@ViewChild('passwordHistory', { read: ViewContainerRef }) passwordHistoryModalRef: ViewContainerRef;
|
||||||
@ViewChild('exportVault', { read: ViewContainerRef }) exportVaultModalRef: ViewContainerRef;
|
@ViewChild('exportVault', { read: ViewContainerRef }) exportVaultModalRef: ViewContainerRef;
|
||||||
|
@ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef;
|
||||||
|
@ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef;
|
||||||
|
|
||||||
action: string;
|
action: string;
|
||||||
cipherId: string = null;
|
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) {
|
viewCipherPasswordHistory(cipher: CipherView) {
|
||||||
if (this.modal != null) {
|
if (this.modal != null) {
|
||||||
this.modal.close();
|
this.modal.close();
|
||||||
|
|||||||
@@ -38,6 +38,18 @@
|
|||||||
"shared": {
|
"shared": {
|
||||||
"message": "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": {
|
"attachments": {
|
||||||
"message": "Attachments"
|
"message": "Attachments"
|
||||||
},
|
},
|
||||||
@@ -1100,5 +1112,11 @@
|
|||||||
},
|
},
|
||||||
"exportMasterPassword": {
|
"exportMasterPassword": {
|
||||||
"message": "Enter your master password to export your vault data."
|
"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