1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

Merge pull request #710 from bitwarden/Send

This commit is contained in:
Addison Beck
2021-02-05 14:21:52 -05:00
committed by GitHub
15 changed files with 309 additions and 25 deletions

2
jslib

Submodule jslib updated: d1c46e6bdc...a16d8f7de7

View File

@@ -14,6 +14,8 @@ import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component'; import { SsoComponent } from './accounts/sso.component';
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from './accounts/two-factor.component';
import { SendComponent } from './send/send.component';
import { VaultComponent } from './vault/vault.component'; import { VaultComponent } from './vault/vault.component';
const routes: Routes = [ const routes: Routes = [
@@ -30,6 +32,11 @@ const routes: Routes = [
{ path: 'hint', component: HintComponent }, { path: 'hint', component: HintComponent },
{ path: 'set-password', component: SetPasswordComponent }, { path: 'set-password', component: SetPasswordComponent },
{ path: 'sso', component: SsoComponent }, { path: 'sso', component: SsoComponent },
{
path: 'send',
component: SendComponent,
canActivate: [AuthGuardService],
},
]; ];
@NgModule({ @NgModule({

View File

@@ -10,6 +10,7 @@ import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services.module'; import { ServicesModule } from './services.module';
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from '@angular/cdk/drag-drop';
import { DatePipe } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
@@ -62,6 +63,11 @@ 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';
import { AddEditComponent as SendAddEditComponent } from './send/add-edit.component';
import { SendComponent } from './send/send.component';
import { NavComponent } from './layout/nav.component';
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import localeBe from '@angular/common/locales/be'; import localeBe from '@angular/common/locales/be';
import localeBg from '@angular/common/locales/bg'; import localeBg from '@angular/common/locales/bg';
@@ -177,6 +183,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
LockComponent, LockComponent,
LoginComponent, LoginComponent,
ModalComponent, ModalComponent,
NavComponent,
PasswordGeneratorComponent, PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent, PasswordGeneratorHistoryComponent,
PasswordHistoryComponent, PasswordHistoryComponent,
@@ -184,6 +191,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
RegisterComponent, RegisterComponent,
SearchCiphersPipe, SearchCiphersPipe,
SelectCopyDirective, SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SetPasswordComponent, SetPasswordComponent,
SettingsComponent, SettingsComponent,
ShareComponent, ShareComponent,
@@ -209,9 +218,10 @@ registerLocaleData(localeZhTw, 'zh-TW');
PremiumComponent, PremiumComponent,
SettingsComponent, SettingsComponent,
ShareComponent, ShareComponent,
SendAddEditComponent,
TwoFactorOptionsComponent, TwoFactorOptionsComponent,
], ],
providers: [], providers: [DatePipe],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule { }

View File

@@ -0,0 +1,5 @@
<ng-container *ngFor="let item of items">
<a [routerLink]="item.link" class="btn primary" routerLinkActive="active" [title]="item.label">
<i class="fa" [ngClass]="item.icon"></i>{{ item.label }}
</a>
</ng-container>

View File

@@ -0,0 +1,23 @@
import { Component } from '@angular/core';
import { I18nService } from 'jslib/abstractions/i18n.service';
@Component({
selector: 'app-nav',
templateUrl: 'nav.component.html',
})
export class NavComponent {
items: any[] = [
{
link: '/vault',
icon: 'fa-lock',
label: this.i18nService.translate('myVault'),
},
{
link: '/send',
icon: 'fa-paper-plane',
label: 'Send',
},
];
constructor(private i18nService: I18nService) {}
}

View File

@@ -0,0 +1,15 @@
<div class="content">
<div class="inner-content">
<div class="box">
<div class="box-header">
{{'sendInformation' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row">
<span class="row-label">{{'name' | i18n}}</span>
{{send.name}}
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
import { DatePipe } from '@angular/common';
import { Component } from '@angular/core';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component';
@Component({
selector: 'app-send-add-edit',
templateUrl: 'add-edit.component.html',
})
export class AddEditComponent extends BaseAddEditComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, datePipe: DatePipe,
sendService: SendService, userService: UserService,
messagingService: MessagingService) {
super(i18nService, platformUtilsService, environmentService,
datePipe, sendService, userService, messagingService);
}
}

View File

@@ -0,0 +1,78 @@
<div id="sends" class="vault">
<div class="groupings">
<div class="mac-bar"></div>
<div class="content">
<div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedAll}">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allSends' | i18n}}
</a>
</li>
</ul>
<h2>{{'types' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedType === sendType.Text}">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.Text)">
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>&nbsp;{{'sendTypeText' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === sendType.File}">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.File)">
<i class="fa fa-fw fa-file-o" aria-hidden="true"></i>&nbsp;{{'sendTypeFile' | i18n}}
</a>
</li>
</ul>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</div>
</div>
<div id="items" class="items">
<div class="header header-search">
<div class="search">
<input type="search" placeholder="{{'searchSends' | i18n}}" id="search"
[(ngModel)]="searchText" (input)="searchTextChanged()" autocomplete="off" appAutofocus>
<i class="fa fa-search" aria-hidden="true"></i>
</div>
</div>
<div class="content">
<div class="list" *ngIf="filteredSends.length" infiniteScroll [infiniteScrollDistance]="1"
[infiniteScrollContainer]="'#items .content'" [fromRoot]="true" (scrolled)="loadMore()">
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s)"
href="#" title="{{'viewItem' | i18n}}"
[ngClass]="{'active': s.id === activeSendId}">
<div class="icon" aria-hidden="true">
<i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-o' : 'fa-file-text-o'"></i>
</div>
<span class="text">
{{s.name}}
</span>
<span class="detail">{{s.deletionDate | date}}</span>
</a>
</div>
<div class="no-items" *ngIf="!filteredSends.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{'noItemsInList' | i18n}}</p>
</ng-container>
</div>
</div>
<div class="footer">
<button appBlurClick (click)="addSend()" class="block primary" appA11yTitle="{{'addItem' | i18n}}">
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div>
</div>
<app-send-add-edit class="details" *ngIf="action == 'add' || action == 'edit'" [sendId]="sendId" [type]="selectedSendType"></app-send-add-edit>
<div class="logo" *ngIf="!action">
<div class="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,58 @@
import {
Component,
NgZone,
OnInit,
} from '@angular/core';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { SendService } from 'jslib/abstractions/send.service';
import { SendComponent as BaseSendComponent } from 'jslib/angular/components/send/send.component';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { SendView } from 'jslib/models/view/sendView';
enum Action {
None = '',
Add = 'add',
Edit = 'edit',
}
@Component({
selector: 'app-send',
templateUrl: 'send.component.html',
})
export class SendComponent extends BaseSendComponent implements OnInit {
sendId: string;
action: Action = Action.None;
constructor(sendService: SendService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
broadcasterService: BroadcasterService, ngZone: NgZone,
searchService: SearchService) {
super(sendService, i18nService, platformUtilsService,
environmentService, broadcasterService, ngZone, searchService);
}
addSend() {
this.sendId = null;
this.action = Action.Add;
}
editSend(send: SendView) {
return;
}
selectSend(send: SendView) {
this.sendId = send.id;
this.action = Action.Edit;
}
get selectedSendType() {
return this.sends.find((s) => s.id === this.sendId).type;
}
}

View File

@@ -98,4 +98,7 @@
</div> </div>
</ng-container> </ng-container>
</div> </div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</div> </div>

View File

@@ -1,19 +1,19 @@
<div id="vault" attr.aria-hidden="{{showingModal}}"> <div id="vault" class="vault" attr.aria-hidden="{{showingModal}}">
<app-vault-groupings id="groupings" (onAllClicked)="clearGroupingFilters()" (onFavoritesClicked)="filterFavorites()" <app-vault-groupings id="groupings" class="groupings" (onAllClicked)="clearGroupingFilters()" (onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)" (onFolderClicked)="filterFolder($event.id)" (onCipherTypeClicked)="filterCipherType($event)" (onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()" (onEditFolder)="editFolder($event.id)" (onAddFolder)="addFolder()" (onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)" (onTrashClicked)="filterDeleted()"> (onCollectionClicked)="filterCollection($event.id)" (onTrashClicked)="filterDeleted()">
</app-vault-groupings> </app-vault-groupings>
<app-vault-ciphers id="items" [activeCipherId]="cipherId" (onCipherClicked)="viewCipher($event)" <app-vault-ciphers id="items" class="items" [activeCipherId]="cipherId" (onCipherClicked)="viewCipher($event)"
(onCipherRightClicked)="viewCipherMenu($event)" (onAddCipher)="addCipher($event)" (onCipherRightClicked)="viewCipherMenu($event)" (onAddCipher)="addCipher($event)"
(onAddCipherOptions)="addCipherOptions()"> (onAddCipherOptions)="addCipherOptions()">
</app-vault-ciphers> </app-vault-ciphers>
<app-vault-view id="details" *ngIf="cipherId && action === 'view'" [cipherId]="cipherId" <app-vault-view id="details" class="details" *ngIf="cipherId && action === 'view'" [cipherId]="cipherId"
(onCloneCipher)="cloneCipher($event)" (onEditCipher)="editCipher($event)" (onCloneCipher)="cloneCipher($event)" (onEditCipher)="editCipher($event)"
(onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" (onRestoredCipher)="restoredCipher($event)" (onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" (onRestoredCipher)="restoredCipher($event)"
(onDeletedCipher)="deletedCipher($event)"> (onDeletedCipher)="deletedCipher($event)">
</app-vault-view> </app-vault-view>
<app-vault-add-edit id="details" *ngIf="action === 'add' || action === 'edit' || action === 'clone'" <app-vault-add-edit id="addEdit" class="details" *ngIf="action === 'add' || action === 'edit' || action === 'clone'"
[cloneMode]="action === 'clone'" [cloneMode]="action === 'clone'"
[folderId]="action === 'add' && folderId !== 'none' ? folderId : null" [folderId]="action === 'add' && folderId !== 'none' ? folderId : null"
[organizationId]="action === 'add' ? addOrganizationId : null" [organizationId]="action === 'add' ? addOrganizationId : null"
@@ -24,7 +24,7 @@
(onShareCipher)="shareCipher($event)" (onEditCollections)="cipherCollections($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' && action !== 'clone'"> <div id="logo" class="logo" *ngIf="action !== 'add' && action !== 'edit' && action !== 'view' && action !== 'clone'">
<div class="content"> <div class="content">
<div class="inner-content"> <div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" /> <img class="logo-image" alt="Bitwarden" aria-hidden="true" />

View File

@@ -1497,5 +1497,26 @@
}, },
"personalOwnershipPolicyInEffect": { "personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options." "message": "An organization policy is affecting your ownership options."
},
"allSends": {
"message": "All Sends",
"description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendTypeFile": {
"message": "File"
},
"sendTypeText": {
"message": "Text"
},
"searchSends": {
"message": "Search Sends",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendInformation": {
"message": "Send Information",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"myVault": {
"message": "My Vault"
} }
} }

View File

@@ -1,6 +1,6 @@
@import "variables.scss"; @import "variables.scss";
.btn, #vault .footer button, .modal-footer button { .btn, .vault .footer button, .modal-footer button {
border-radius: $border-radius; border-radius: $border-radius;
padding: 7px 15px; padding: 7px 15px;
border: 1px solid #000000; border: 1px solid #000000;

View File

@@ -33,7 +33,7 @@ html.os_macos {
} }
} }
#vault .header-search { .vault .header-search {
-webkit-app-region: drag; -webkit-app-region: drag;
input, i { input, i {
@@ -41,12 +41,12 @@ html.os_macos {
} }
} }
#vault .mac-bar { .vault .mac-bar {
height: 48px; height: 48px;
-webkit-app-region: drag; -webkit-app-region: drag;
} }
#vault > #groupings > .content > .inner-content { .vault > .groupings > .content > .inner-content {
padding-top: 0; padding-top: 0;
} }
} }

View File

@@ -1,10 +1,10 @@
@import "variables.scss"; @import "variables.scss";
#vault { .vault {
height: 100%; height: 100%;
display: flex; display: flex;
> #groupings, > #items, > #details, > #logo { > .groupings, > .items, > .details, > .logo {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -13,7 +13,7 @@
} }
} }
> #groupings { > .groupings {
width: 22%; width: 22%;
min-width: 175px; min-width: 175px;
max-width: 250px; max-width: 250px;
@@ -24,13 +24,24 @@
border-right-color: themed('borderColor'); border-right-color: themed('borderColor');
} }
.inner-content { .content {
padding-bottom: 0; display: flex;
padding-right: 5px; flex-direction: column;
user-select: none; flex-grow: 1;
justify-content: space-between;
> ul, > div > ul { .footer {
margin: 0 0 15px 0; padding: 0;
}
.inner-content {
padding-bottom: 0;
padding-right: 5px;
user-select: none;
> ul, > div > ul {
margin: 0 0 15px 0;
}
} }
} }
@@ -254,7 +265,7 @@
} }
} }
> #items { > .items {
width: 28%; width: 28%;
min-width: 200px; min-width: 200px;
max-width: 350px; max-width: 350px;
@@ -284,7 +295,7 @@
} }
} }
> #details { > .details {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
@@ -316,7 +327,7 @@
} }
} }
> #logo { > .logo {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
@@ -414,7 +425,7 @@
} }
.footer { .footer {
height: 50px; height: 55px;
flex: 0 0 auto; flex: 0 0 auto;
border-top: 1px solid #000000; border-top: 1px solid #000000;
display: flex; display: flex;
@@ -439,4 +450,31 @@
display: flex; display: flex;
} }
} }
.nav {
height: 100%;
width: 100%;
display: flex;
.btn {
width: 100%;
font-size: $font-size-base * 0.8;
flex: 1;
border: 0;
border-radius: 0;
padding-bottom: 4px;
&:not(.active) {
@include themify($themes) {
background-color: themed('backgroundColorAlt');
}
}
i {
font-size: $font-size-base * 1.5;
display: block;
margin-bottom: 2px;
text-align: center;
}
}
}
} }