1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-17 01:31:25 +01:00

finished CRUD operations for Send

This commit is contained in:
addison 2021-02-16 11:52:23 -05:00
parent 12532f3cc4
commit 1290ff2c40
7 changed files with 230 additions and 91 deletions

2
jslib

@ -1 +1 @@
Subproject commit ee164bebc65aa56e41a122eb4ece8971eb23119b Subproject commit 7941664a59f90a1b510b13d37062b90210da3b3c

View File

@ -1,106 +1,159 @@
<ng-container *ngIf="send"> <form (ngSubmit)="submit()" [appApiAction]="formPromise">
<form (ngSubmit)="submit()" [appApiAction]="formPromise"> <div class="content">
<div class="content" *ngIf="send"> <div class="inner-content" *ngIf="send">
<div class="inner-content"> <div class="box">
<div class="box"> <div class="box-header">
<div class="box-header"> {{title}}
{{'editSend' | i18n}} </div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="send.name" appAutofocus>
</div> </div>
<div class="box-content"> </div>
<div class="box-content-row" appBoxRow> </div>
<label for="name">{{'name' | i18n}}</label> <div class="box">
<input id="name" type="text" name="Name" [(ngModel)]="send.name" appAutofocus> <div class="box-content">
<ng-container *ngIf="!editMode">
<div class="box-content-row box-content-row-radio">
<label class="radio-header">{{'whatTypeOfSend' | i18n}}</label>
<div class="item" *ngFor="let o of typeOptions">
<input type="radio" class="radio" [(ngModel)]="send.type" name="Type_{{o.value}}"
id="type_{{o.value}}" [value]="o.value" (change)="typeChanged(o)"
[checked]="send.type === o.value">
<label class="label-hack" for="type_{{o.value}}">
{{o.name}}
</label>
</div>
</div> </div>
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.File"> <div *ngIf="send.type === sendType.File" class="box-content-row" appBowRow>
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" class="form-control-file" name="file" required
[disabled]="disableSend">
<div class="subtext">{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}}</div>
</div>
</ng-container>
<ng-container *ngIf="editMode && send.type === sendType.File">
<div class="box-content-row" appBoxRow>
<label for="file">{{'file' | i18n}}</label> <label for="file">{{'file' | i18n}}</label>
<input id="file" type="text" name="file" [(ngModel)]="send.file.fileName" readonly> <input id="file" type="text" name="file" [(ngModel)]="send.file.fileName" readonly>
</div> </div>
</ng-container>
<ng-container *ngIf="send.type === sendType.Text">
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text"> <div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
<label for="text">{{'text' | i18n}}</label> <label for="text">{{'text' | i18n}}</label>
<input id="text" type="text" name="text" [(ngModel)]="send.text.text"> <textarea id="text" name="text" [(ngModel)]="send.text.text" rows="6"></textarea>
<div class="subtext">{{'sendTextDesc' | i18n}}</div>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="send.type === sendType.Text"> <div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="send.type === sendType.Text">
<label for="hideText">{{'textHiddenByDefault' | i18n}}</label> <label for="hideText">{{'textHiddenByDefault' | i18n}}</label>
<input id="hideText" name="hideText" type="checkbox" [(ngModel)]="send.text.hidden"> <input id="hideText" name="hideText" type="checkbox" [(ngModel)]="send.text.hidden">
</div> </div>
</ng-container>
</div>
</div>
<div class="box">
<div class="box-header">
{{'options' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
<input id="deletionDate" type="datetime-local" name="deletionDate"
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM" *ngIf="editMode">
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect" required *ngIf="!editMode">
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<div class="subtext">{{'deletionDateDesc' | i18n}}</div>
</div>
<div class="box-content-row" *ngIf="deletionDateSelect === 0">
<input id="deletionDateCustom" type="datetime-local" name="deletionDate"
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
</div>
<div class="box-content-row" appBoxRow>
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
<input id="expirationDate" type="datetime-local" name="expirationDate" [(ngModel)]="expirationDate" *ngIf="editMode">
<select id="expirationDate" name="expirationDateSelect" [(ngModel)]="expirationDateSelect" required *ngIf="!editMode">
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
</option>
</select>
<div class="subtext">{{'expirationDateDesc' | i18n}}</div>
</div>
<div class="box-content-row" *ngIf="expirationDateSelect === 0">
<input id="expirationDateCustom" type="datetime-local" name="expirationDate"
[(ngModel)]="expirationDate" required placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
</div> </div>
</div> </div>
<div class="box"> </div>
<div class="box-header"> <div class="box">
{{'options' | i18n}} <div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
<input id="maxAccessCount" type="number" name="maxAccessCount" [(ngModel)]="send.maxAccessCount">
<div class="subtext">{{'maxAccessCountDesc' | i18n}}</div>
</div> </div>
<div class="box-content"> <div *ngIf="editMode" class="box-content-row" appBoxRow>
<div class="box-content-row" appBoxRow *ngIf="editMode"> <label for="accessCount">{{'currentAccessCount' | i18n}}</label>
<label for="deletionDate">{{'deletionDate' | i18n}}</label> <input id="accessCount" type="text" name="accessCount" [(ngModel)]="send.accessCount" readonly>
<input id="deletionDate" type="datetime-local" name="deletionDate"
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
<div class="subtext">{{'deletionDateDesc' | i18n}}</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
<input id="expirationDate" type="datetime-local" name="expirationDate" [(ngModel)]="expirationDate">
<div class="subtext">{{'expirationDateDesc' | i18n}}</div>
</div>
</div> </div>
</div> </div>
<div class="box"> </div>
<div class="box-content"> <div class="box">
<div class="box-content-row" appBoxRow> <div class="box-content">
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label> <div class="box-content-row" appBoxRow>
<input id="maxAccessCount" type="number" name="maxAccessCount" [(ngModel)]="send.maxAccessCount"> <label for="password">{{(hasPassword ? 'newPassword' : 'password') | i18n}}</label>
<div class="subtext">{{'maxAccessCountDesc' | i18n}}</div> <input id="password" type="password" name="password" [(ngModel)]="password">
</div> <div class="subtext">{{'sendPasswordDesc' | i18n}}</div>
<div class="box-content-row" appBoxRow>
<label for="accessCount">{{'currentAccessCount' | i18n}}</label>
<input id="accessCount" type="text" name="accessCount" [(ngModel)]="send.accessCount" readonly>
</div>
</div> </div>
</div> </div>
<div class="box"> </div>
<div class="box-content"> <div class="box">
<div class="box-content-row" appBoxRow> <div class="box-header">
<label for="password">{{(hasPassword ? 'newPassword' : 'password') | i18n}}</label> {{'notes' | i18n}}
<input id="password" type="password" name="password" [(ngModel)]="password"> </div>
<div class="subtext">{{'sendPasswordDesc' | i18n}}</div> <div class="box-content">
</div> <div class="box-content-row" appBoxRow>
<textarea id="notes" name="notes" [(ngModel)]="send.notes" rows="6"></textarea>
<small class="subtext">{{'sendNotesDesc' | i18n}}</small>
</div> </div>
</div> </div>
<div class="box"> </div>
<div class="box-header"> <div class="box">
{{'notes' | i18n}} <div class="box-content">
</div> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<div class="box-content"> <label for="disabled">{{'disableSend' | i18n}}</label>
<div class="box-content-row" appBoxRow> <input id="disabled" type="checkbox" name="disabled" [(ngModel)]="send.disabled">
<textarea id="notes" name="notes" [(ngModel)]="send.notes" rows="6"></textarea>
<small class="subtext">{{'sendNotesDesc' | i18n}}</small>
</div>
</div> </div>
</div> </div>
<div class="box"> </div>
<div class="box-content"> <div class="box" *ngIf="editMode">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-header">
<label for="disabled">{{'disableSend' | i18n}}</label> {{'share' | i18n}}
<input id="disabled" type="checkbox" name="disabled" [(ngModel)]="send.disabled">
</div>
</div>
</div> </div>
<div class="box"> <div class="box-content">
<div class="box-header"> <div class="box-content-row" appBoxRow>
{{'share' | i18n}} <label for="url">{{'sendLink' | i18n}}</label>
</div> <input id="url" name="url" [ngModel]="link" readonly>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="url">{{'sendLink' | i18n}}</label>
<input id="url" name="url" [ngModel]="link" readonly>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="footer"> </div>
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"> <div class="footer">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i> <button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button appBlurClick type="button" (click)="cancel()">
{{'cancel' | i18n}}
</button>
<div class="right">
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" aria-hidden="true"></i>
</button> </button>
</div> </div>
</form> </div>
</ng-container> </form>

View File

@ -28,9 +28,14 @@ export class AddEditComponent extends BaseAddEditComponent {
async refresh() { async refresh() {
const send = await this.loadSend(); const send = await this.loadSend();
this.send = await send.decrypt(); this.send = await send.decrypt();
this.hasPassword = this.send.password != null && this.send.password.trim() !== ''; this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
this.deletionDate = this.dateToString(this.send.deletionDate); this.deletionDate = this.dateToString(this.send.deletionDate);
this.expirationDate = this.dateToString(this.send.expirationDate); this.expirationDate = this.dateToString(this.send.expirationDate);
this.password = null; }
cancel() {
this.onCancelled.emit(this.send);
} }
} }

View File

@ -67,7 +67,8 @@
</button> </button>
</div> </div>
</div> </div>
<app-send-add-edit id="addEdit" class="details" *ngIf="action == 'add' || action == 'edit'" [sendId]="sendId" [type]="selectedSendType" (onSavedSend)="refresh()"></app-send-add-edit> <app-send-add-edit id="addEdit" class="details" *ngIf="action == 'add' || action == 'edit'" [sendId]="sendId" [type]="selectedSendType"
(onSavedSend)="refresh()" (onCancelled)="cancel()"></app-send-add-edit>
<div class="logo" *ngIf="!action"> <div class="logo" *ngIf="!action">
<div class="content"> <div class="content">
<div class="inner-content"> <div class="inner-content">

View File

@ -1,6 +1,7 @@
import { import {
Component, Component,
NgZone, NgZone,
OnDestroy,
OnInit, OnInit,
ViewChild, ViewChild,
} from '@angular/core'; } from '@angular/core';
@ -27,11 +28,13 @@ enum Action {
Edit = 'edit', Edit = 'edit',
} }
const BroadcasterSubscriptionId = 'SendComponent';
@Component({ @Component({
selector: 'app-send', selector: 'app-send',
templateUrl: 'send.component.html', templateUrl: 'send.component.html',
}) })
export class SendComponent extends BaseSendComponent implements OnInit { export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy {
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent; @ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
sendId: string; sendId: string;
@ -39,34 +42,63 @@ export class SendComponent extends BaseSendComponent implements OnInit {
constructor(sendService: SendService, i18nService: I18nService, constructor(sendService: SendService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
broadcasterService: BroadcasterService, ngZone: NgZone, private broadcasterService: BroadcasterService, ngZone: NgZone,
searchService: SearchService, policyService: PolicyService, searchService: SearchService, policyService: PolicyService,
userService: UserService) { userService: UserService) {
super(sendService, i18nService, platformUtilsService, super(sendService, i18nService, platformUtilsService,
environmentService, broadcasterService, ngZone, searchService, environmentService, ngZone, searchService,
policyService, userService); policyService, userService);
} }
async ngOnInit() { async ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
if (message.successfully) {
await this.load();
}
break;
case 'syncCompleted':
await this.load();
break;
}
});
});
await this.load(); await this.load();
} }
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
addSend() { addSend() {
this.sendId = null;
this.action = Action.Add; this.action = Action.Add;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = null;
this.addEditComponent.send = null;
this.addEditComponent.load();
}
} }
editSend(send: SendView) { editSend(send: SendView) {
return; return;
} }
async selectSend(sendId: string) { cancel(s: SendView) {
this.sendId = sendId; this.action = Action.None;
this.action = Action.Edit; this.sendId = null;
}
async selectSend(sendId: string) {
if (sendId === this.sendId) {
return;
}
this.action = Action.Edit;
this.sendId = sendId;
if (this.addEditComponent != null) { if (this.addEditComponent != null) {
this.addEditComponent.sendId = this.sendId; this.addEditComponent.sendId = sendId;
await this.addEditComponent.refresh(); await this.addEditComponent.refresh();
} }
} }

View File

@ -1583,5 +1583,38 @@
}, },
"newPassword": { "newPassword": {
"message": "New Password" "message": "New Password"
},
"whatTypeOfSend": {
"message": "What type of Send is this?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"createSend": {
"message": "Create Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendTextDesc": {
"message": "The text you want to send."
},
"sendFileDesc": {
"message": "The file you want to send."
},
"days": {
"message": "$DAYS$ days",
"placeholders": {
"days": {
"content": "$1",
"example": "1"
}
}
},
"oneDay": {
"message": "1 day"
},
"custom": {
"message": "Custom"
},
"deleteSendConfirmation": {
"message": "Are you sure you want to delete this Send?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
} }
} }

View File

@ -102,7 +102,7 @@
&:hover, &:focus, &.active { &:hover, &:focus, &.active {
@include themify($themes) { @include themify($themes) {
background-color: themed('boxBackgroundHoverColor'); background-color: themed('boxBackgroundHoverColor');
> * { textarea {
background-color: themed('boxBackgroundHoverColor'); background-color: themed('boxBackgroundHoverColor');
} }
} }
@ -118,7 +118,7 @@
overflow-x: auto; overflow-x: auto;
} }
.row-label, label { .row-label, label:not(.label-hack) {
font-size: $font-size-small; font-size: $font-size-small;
display: block; display: block;
width: 100%; width: 100%;
@ -169,7 +169,7 @@
&.box-content-row-multi { &.box-content-row-multi {
width: 100%; width: 100%;
input:not([type="checkbox"]) { input:not([type="checkbox"]):not([type="checkbox"]) {
width: 100%; width: 100%;
} }
@ -250,7 +250,22 @@
} }
} }
input:not([type="checkbox"]), textarea { &.box-content-row-radio {
display: grid;
grid-template-columns: 1fr;
.item {
display: flex;
flex: 1;
align-items: center;
> .radio {
margin-right: 5px;
margin-top: 0;
}
}
}
input:not([type="checkbox"]):not([type="radio"]), textarea {
border: none; border: none;
width: 100%; width: 100%;
background-color: transparent; background-color: transparent;