mirror of
https://github.com/bitwarden/browser.git
synced 2025-03-22 15:19:15 +01:00
[PM-2805] Migrate add edit send to Component Library (#6004)
* Converted add-edit send component dialog into a bit-dialog * Updated Send AddEdit text fields to Component Library * Migrated Share and Options fields to ComponentLibrary on SendAddEdit * Migrated footer buttons to ComponentLibrary on SendAddEdit * Updated web's SendAddEdit component file fields * Replaced file upload with component library * Changed SendAddEdit to use Reactive Forms on web * Changed browser SendAddEdit to use ReactiveForms * Update SendAddEdit on desktop to use ReactiveForms * Added AppA11yTitle to button on web SendAddEdit * Initial efflux-dates web change to ComponentLibrary * Corrected delete button to check if it is in EditMode on SendAddEdit * Using BitLink on options button * Corrected typo on send add edit desktop * Replaced efflux-dates with datetime-local input on SendAddEdit web, browser and desktop * Removed efflux dates * Added firefox custom date popout message on DeletionDate to SendAddEdit browser component * moved desktop's new send data reload from send to SendAddEdit component * removing unnecessary attributes and spans from Send AddEdit web * removed redundant try catch from add edit and unnecessary parameter from close * Added type for date select options * Removed unnecessary classes and swapped bootstrap classes by corresponding tailwind classes * Removed unnecessary code * Added file as required field Submit only closes popup on success * Added pre validations at start of submit * PM-3668 removed expiration date from required * PM-3671 not defaulting maximum access count to 0 * PM-3669 Copying the link from link method * Removed required tag from html and added to formgroup * PM-3679 Checking if is not EditMode before validating if FormGroup file value is set * PM-3691 Moved error validation to web component as browser and desktop need to show popup error * PM-3696 - Disabling hide email when it is unset and has policy to not allow hiding * PM-3694 - Properly setting default value for dates on Desktop when changing from an existing send * Disabling hidden required fields * [PM-3800] Clearing password on new send
This commit is contained in:
parent
86bdfaa7ba
commit
5f78aeaef2
@ -33,7 +33,6 @@ import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.
|
||||
import { GeneratorComponent } from "../tools/popup/generator/generator.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component";
|
||||
import { SendListComponent } from "../tools/popup/send/components/send-list.component";
|
||||
import { EffluxDatesComponent as SendEffluxDatesComponent } from "../tools/popup/send/efflux-dates.component";
|
||||
import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component";
|
||||
import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component";
|
||||
import { SendTypeComponent } from "../tools/popup/send/send-type.component";
|
||||
@ -133,7 +132,6 @@ import "../platform/popup/locales";
|
||||
PrivateModeWarningComponent,
|
||||
RegisterComponent,
|
||||
SendAddEditComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SendGroupingsComponent,
|
||||
SendListComponent,
|
||||
SendTypeComponent,
|
||||
|
@ -1,217 +0,0 @@
|
||||
<ng-container [formGroup]="datesForm">
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<ng-container *ngIf="!editMode">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
|
||||
<select
|
||||
id="deletionDate"
|
||||
name="DeletionDateSelect"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="selectedDeletionDatePreset"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow *ngIf="selectedDeletionDatePreset.value === 0">
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="box-content-row" appBoxRow *ngIf="editMode">
|
||||
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div id="deletionDateHelp" class="box-footer">
|
||||
{{ "deletionDateDesc" | i18n }}
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!inPopout &&
|
||||
browserPath == 'firefox' &&
|
||||
(editMode || (selectedDeletionDatePreset.value === 0 && !editMode))
|
||||
"
|
||||
>
|
||||
<br />{{ "sendFirefoxCustomDatePopoutMessage1" | i18n }}
|
||||
<a (click)="popOutWindow.emit()">{{ "sendFirefoxCustomDatePopoutMessage2" | i18n }}</a>
|
||||
{{ "sendFirefoxCustomDatePopoutMessage3" | i18n }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<ng-container *ngIf="!editMode">
|
||||
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
|
||||
<label for="editExpirationDate">{{ "expirationDate" | i18n }}</label>
|
||||
<select
|
||||
id="expirationDate"
|
||||
name="ExpirationDateSelect"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="selectedExpirationDatePreset"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="box-content-row" *ngIf="selectedExpirationDatePreset.value === 0" appBoxRow>
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="box-content-row" *ngIf="editMode" appBoxRow>
|
||||
<div class="flex-label">
|
||||
<label>{{ "expirationDate" | i18n }}</label>
|
||||
<button type="button" *ngIf="!disabled" appStopClick (click)="clearExpiration()">
|
||||
{{ "clear" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div id="expirationDateHelp" class="box-footer">
|
||||
{{ "expirationDateDesc" | i18n }}
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!inPopout &&
|
||||
browserPath == 'firefox' &&
|
||||
(editMode || (selectedExpirationDatePreset.value === 0 && !editMode))
|
||||
"
|
||||
>
|
||||
<br />{{ "sendFirefoxCustomDatePopoutMessage1" | i18n }}
|
||||
<a (click)="popOutWindow.emit()">{{ "sendFirefoxCustomDatePopoutMessage2" | i18n }}</a>
|
||||
{{ "sendFirefoxCustomDatePopoutMessage3" | i18n }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #deletionDateCustom>
|
||||
<ng-container [ngSwitch]="browserPath">
|
||||
<ng-container *ngSwitchCase="'firefox'">
|
||||
<div class="flex flex-grow">
|
||||
<input
|
||||
id="deletionDateCustomFallback"
|
||||
type="date"
|
||||
name="DeletionDateFallback"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="fallbackDeletionDate"
|
||||
required
|
||||
placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disabled"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<input
|
||||
id="deletionTimeCustomFallback"
|
||||
type="time"
|
||||
name="DeletionTimeDate"
|
||||
formControlName="fallbackDeletionTime"
|
||||
required
|
||||
placeholder="HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'safari'">
|
||||
<div class="flex flex-grow">
|
||||
<input
|
||||
id="deletionDateCustomFallback"
|
||||
type="date"
|
||||
name="DeletionDateFallback"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="fallbackDeletionDate"
|
||||
required
|
||||
placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disabled"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<select
|
||||
id="deletionTimeCustomFallback"
|
||||
formControlName="fallbackDeletionTime"
|
||||
name="SafariDeletionTime"
|
||||
>
|
||||
<option *ngFor="let o of safariDeletionTimePresetOptions" [ngValue]="o.twentyFourHour">
|
||||
{{ o.twelveHour }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<input
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #expirationDateCustom>
|
||||
<ng-container [ngSwitch]="browserPath">
|
||||
<ng-container *ngSwitchCase="'firefox'">
|
||||
<div class="flex flex-grow">
|
||||
<input
|
||||
id="expirationDateCustomFallback"
|
||||
type="date"
|
||||
name="ExpirationDateFallback"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="fallbackExpirationDate"
|
||||
[required]="!editMode"
|
||||
placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disabled"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<input
|
||||
id="expirationTimeCustomFallback"
|
||||
type="time"
|
||||
name="ExpirationTimeFallback"
|
||||
formControlName="fallbackExpirationTime"
|
||||
[required]="!editMode"
|
||||
placeholder="HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'safari'">
|
||||
<div class="flex flex-grow">
|
||||
<input
|
||||
id="expirationDateCustomFallback"
|
||||
type="date"
|
||||
name="ExpirationDateFallback"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="fallbackExpirationDate"
|
||||
[required]="!editMode"
|
||||
placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disabled"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<select
|
||||
id="expirationTimeCustomFallback"
|
||||
formControlName="fallbackExpirationTime"
|
||||
name="SafariExpirationTime"
|
||||
>
|
||||
<option
|
||||
*ngFor="let o of safariExpirationTimePresetOptions"
|
||||
[ngValue]="o.twentyFourHour"
|
||||
>
|
||||
{{ o.twelveHour }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<input
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</ng-container>
|
@ -1,25 +0,0 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { ControlContainer, NgForm } from "@angular/forms";
|
||||
|
||||
import { EffluxDatesComponent as BaseEffluxDatesComponent } from "@bitwarden/angular/tools/send/efflux-dates.component";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-send-efflux-dates",
|
||||
templateUrl: "efflux-dates.component.html",
|
||||
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
|
||||
})
|
||||
export class EffluxDatesComponent extends BaseEffluxDatesComponent {
|
||||
@Input() readonly inPopout: boolean;
|
||||
@Output() popOutWindow = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected datePipe: DatePipe
|
||||
) {
|
||||
super(i18nService, platformUtilsService, datePipe);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" [formGroup]="formGroup">
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" (click)="cancel()">{{ "cancel" | i18n }}</button>
|
||||
@ -7,7 +7,7 @@
|
||||
<span class="title">{{ title }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<button type="submit" [disabled]="form.loading || disableSend">
|
||||
<button type="submit">
|
||||
<span [hidden]="form.loading">{{ "save" | i18n }}</span>
|
||||
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
@ -42,9 +42,8 @@
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
name="Name"
|
||||
formControlName="name"
|
||||
aria-describedby="nameHelp"
|
||||
[(ngModel)]="send.name"
|
||||
[readonly]="disableSend"
|
||||
/>
|
||||
</div>
|
||||
@ -66,12 +65,9 @@
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="send.type"
|
||||
name="Type_{{ o.value }}"
|
||||
formControlName="type"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="typeChanged()"
|
||||
[checked]="send.type === o.value"
|
||||
[readonly]="disableSend"
|
||||
/>
|
||||
<label for="type_{{ o.value }}">
|
||||
@ -82,7 +78,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- File -->
|
||||
<div class="box" *ngIf="send.type === sendType.File && (editMode || showFileSelector)">
|
||||
<div class="box" *ngIf="type === sendType.File && (editMode || showFileSelector)">
|
||||
<div class="box-content no-hover">
|
||||
<div class="box-content-row" *ngIf="editMode">
|
||||
<label for="file">{{ "file" | i18n }}</label>
|
||||
@ -93,9 +89,8 @@
|
||||
<input
|
||||
type="file"
|
||||
id="file"
|
||||
name="file"
|
||||
formControlName="file"
|
||||
aria-describedby="fileHelp"
|
||||
required
|
||||
[readonly]="disableSend"
|
||||
/>
|
||||
</div>
|
||||
@ -105,17 +100,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Text -->
|
||||
<div class="box" *ngIf="send.type === sendType.Text">
|
||||
<div class="box" *ngIf="type === sendType.Text">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="text">{{ "sendTypeText" | i18n }}</label>
|
||||
<textarea
|
||||
id="text"
|
||||
name="Text"
|
||||
formControlName="text"
|
||||
aria-describedby="textHelp"
|
||||
rows="6"
|
||||
[(ngModel)]="send.text.text"
|
||||
[readonly]="disableSend"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -125,13 +118,7 @@
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="hideText">{{ "sendHideText" | i18n }}</label>
|
||||
<input
|
||||
id="hideText"
|
||||
type="checkbox"
|
||||
name="HideText"
|
||||
[(ngModel)]="send.text.hidden"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<input id="hideText" type="checkbox" name="HideText" formControlName="textHidden" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -144,13 +131,7 @@
|
||||
<!-- Copy Link on Save -->
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="copyOnSave">{{ "sendShareDesc" | i18n }}</label>
|
||||
<input
|
||||
id="copyOnSave"
|
||||
type="checkbox"
|
||||
name="CopyOnSave"
|
||||
[(ngModel)]="copyLink"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<input id="copyOnSave" type="checkbox" name="CopyOnSave" formControlName="copyLink" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -170,15 +151,140 @@
|
||||
</h2>
|
||||
</div>
|
||||
<div [hidden]="!showOptions">
|
||||
<app-send-efflux-dates
|
||||
[initialDeletionDate]="send.deletionDate"
|
||||
[initialExpirationDate]="send.expirationDate"
|
||||
[editMode]="editMode"
|
||||
[disabled]="disableSend"
|
||||
(datesChanged)="setDates($event)"
|
||||
(popOutWindow)="popOutWindow()"
|
||||
>
|
||||
</app-send-efflux-dates>
|
||||
<!-- Deletion Date -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<ng-container *ngIf="!editMode">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
|
||||
<select
|
||||
id="deletionDate"
|
||||
name="DeletionDateSelect"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="selectedDeletionDatePreset"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row"
|
||||
appBoxRow
|
||||
*ngIf="formGroup.controls['selectedDeletionDatePreset'].value === 0"
|
||||
>
|
||||
<input
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="box-content-row" appBoxRow *ngIf="editMode">
|
||||
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
|
||||
<input
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="deletionDateHelp" class="box-footer">
|
||||
{{ "deletionDateDesc" | i18n }}
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!inPopout &&
|
||||
isFirefox &&
|
||||
(editMode ||
|
||||
(formGroup.controls['selectedDeletionDatePreset'].value === 0 && !editMode))
|
||||
"
|
||||
>
|
||||
<br />{{ "sendFirefoxCustomDatePopoutMessage1" | i18n }}
|
||||
<a (click)="popOutWindow()">{{ "sendFirefoxCustomDatePopoutMessage2" | i18n }}</a>
|
||||
{{ "sendFirefoxCustomDatePopoutMessage3" | i18n }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expiration Date -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<ng-container *ngIf="!editMode">
|
||||
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
|
||||
<label for="editExpirationDate">{{ "expirationDate" | i18n }}</label>
|
||||
<select
|
||||
id="expirationDate"
|
||||
name="ExpirationDateSelect"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="selectedExpirationDatePreset"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row"
|
||||
*ngIf="formGroup.controls['selectedExpirationDatePreset'].value === 0"
|
||||
appBoxRow
|
||||
>
|
||||
<input
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="box-content-row" *ngIf="editMode" appBoxRow>
|
||||
<div class="flex-label">
|
||||
<label>{{ "expirationDate" | i18n }}</label>
|
||||
<button type="button" *ngIf="!disableSend" appStopClick (click)="clearExpiration()">
|
||||
{{ "clear" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="expirationDateHelp" class="box-footer">
|
||||
{{ "expirationDateDesc" | i18n }}
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!inPopout &&
|
||||
isFirefox &&
|
||||
(editMode ||
|
||||
(formGroup.controls['selectedExpirationDatePreset'].value === 0 && !editMode))
|
||||
"
|
||||
>
|
||||
<br />{{ "sendFirefoxCustomDatePopoutMessage1" | i18n }}
|
||||
<a (click)="popOutWindow()">{{ "sendFirefoxCustomDatePopoutMessage2" | i18n }}</a>
|
||||
{{ "sendFirefoxCustomDatePopoutMessage3" | i18n }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Maximum Access Count -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
@ -190,8 +296,7 @@
|
||||
type="number"
|
||||
name="MaximumAccessCount"
|
||||
aria-describedby="maximumAccessCountHelp"
|
||||
[(ngModel)]="send.maxAccessCount"
|
||||
[readonly]="disableSend"
|
||||
formControlName="maxAccessCount"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -206,10 +311,9 @@
|
||||
<label for="currentAccessCount">{{ "currentAccessCount" | i18n }}</label>
|
||||
<input
|
||||
id="currentAccessCount"
|
||||
readonly
|
||||
type="text"
|
||||
name="CurrentAccessCount"
|
||||
[(ngModel)]="send.accessCount"
|
||||
formControlName="accessCount"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -227,9 +331,8 @@
|
||||
name="Password"
|
||||
aria-describedby="passwordHelp"
|
||||
class="monospaced"
|
||||
[(ngModel)]="password"
|
||||
formControlName="password"
|
||||
appInputVerbatim
|
||||
[readonly]="disableSend"
|
||||
/>
|
||||
</div>
|
||||
<div class="action-buttons" *ngIf="!disableSend">
|
||||
@ -264,8 +367,7 @@
|
||||
name="Notes"
|
||||
aria-describedby="notesHelp"
|
||||
rows="6"
|
||||
[(ngModel)]="send.notes"
|
||||
[readonly]="disableSend"
|
||||
formControlName="notes"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -278,13 +380,7 @@
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="hideEmail">{{ "hideEmail" | i18n }}</label>
|
||||
<input
|
||||
id="hideEmail"
|
||||
type="checkbox"
|
||||
name="HideEmail"
|
||||
[(ngModel)]="send.hideEmail"
|
||||
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"
|
||||
/>
|
||||
<input id="hideEmail" type="checkbox" name="HideEmail" formControlName="hideEmail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -293,13 +389,7 @@
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="disableSend">{{ "sendDisableDesc" | i18n }}</label>
|
||||
<input
|
||||
id="disableSend"
|
||||
type="checkbox"
|
||||
name="DisableSend"
|
||||
[(ngModel)]="send.disabled"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<input id="disableSend" type="checkbox" name="DisableSend" formControlName="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DatePipe, Location } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
@ -47,7 +48,8 @@ export class SendAddEditComponent extends BaseAddEditComponent {
|
||||
private popupUtilsService: PopupUtilsService,
|
||||
logService: LogService,
|
||||
sendApiService: SendApiService,
|
||||
dialogService: DialogService
|
||||
dialogService: DialogService,
|
||||
formBuilder: FormBuilder
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
@ -60,7 +62,8 @@ export class SendAddEditComponent extends BaseAddEditComponent {
|
||||
logService,
|
||||
stateService,
|
||||
sendApiService,
|
||||
dialogService
|
||||
dialogService,
|
||||
formBuilder
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,6 @@ import { ExportComponent } from "./tools/export/export.component";
|
||||
import { GeneratorComponent } from "./tools/generator.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
|
||||
import { AddEditComponent as SendAddEditComponent } from "./tools/send/add-edit.component";
|
||||
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./tools/send/efflux-dates.component";
|
||||
import { SendComponent } from "./tools/send/send.component";
|
||||
|
||||
@NgModule({
|
||||
@ -87,7 +86,6 @@ import { SendComponent } from "./tools/send/send.component";
|
||||
SearchComponent,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SetPasswordComponent,
|
||||
SetPinComponent,
|
||||
SettingsComponent,
|
||||
|
@ -1,4 +1,4 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<form #form [formGroup]="formGroup" (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="content">
|
||||
<div class="inner-content" *ngIf="send">
|
||||
<div class="box">
|
||||
@ -16,14 +16,7 @@
|
||||
<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
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
<input id="name" type="text" name="Name" formControlName="name" appAutofocus />
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-radio" *ngIf="!editMode">
|
||||
<label class="radio-header">{{ "whatTypeOfSend" | i18n }}</label>
|
||||
@ -31,20 +24,16 @@
|
||||
<input
|
||||
type="radio"
|
||||
class="radio"
|
||||
[(ngModel)]="send.type"
|
||||
name="Type_{{ o.value }}"
|
||||
formControlName="type"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="typeChanged()"
|
||||
[checked]="send.type === o.value"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<label class="unstyled" for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBowRow *ngIf="!editMode && send.type === sendType.File">
|
||||
<div class="box-content-row" appBoxRow *ngIf="!editMode && type === sendType.File">
|
||||
<label for="file">{{ "file" | i18n }}</label>
|
||||
<input
|
||||
type="file"
|
||||
@ -53,22 +42,20 @@
|
||||
name="file"
|
||||
aria-describedby="fileHelp"
|
||||
required
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row" appBowRow *ngIf="editMode && send.type === sendType.File">
|
||||
<div class="box-content-row" appBoxRow *ngIf="editMode && type === sendType.File">
|
||||
<label for="file">{{ "file" | i18n }}</label>
|
||||
<div class="row-main">{{ send.file.fileName }} ({{ send.file.sizeName }})</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
|
||||
<div class="box-content-row" appBoxRow *ngIf="type === sendType.Text">
|
||||
<label for="text">{{ "text" | i18n }}</label>
|
||||
<textarea
|
||||
id="text"
|
||||
name="text"
|
||||
aria-describedby="textHelp"
|
||||
[(ngModel)]="send.text.text"
|
||||
formControlName="text"
|
||||
rows="6"
|
||||
[readOnly]="disableSend"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -83,13 +70,7 @@
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="hideText">{{ "textHiddenByDefault" | i18n }}</label>
|
||||
<input
|
||||
id="hideText"
|
||||
name="hideText"
|
||||
type="checkbox"
|
||||
[(ngModel)]="send.text.hidden"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<input id="hideText" name="hideText" type="checkbox" formControlName="textHidden" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -112,14 +93,82 @@
|
||||
</h2>
|
||||
</div>
|
||||
<div [hidden]="!showOptions">
|
||||
<app-send-efflux-dates
|
||||
[initialDeletionDate]="send.deletionDate"
|
||||
[initialExpirationDate]="send.expirationDate"
|
||||
[editMode]="editMode"
|
||||
[disabled]="disableSend"
|
||||
(datesChanged)="setDates($event)"
|
||||
>
|
||||
</app-send-efflux-dates>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow *ngIf="!editMode">
|
||||
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
|
||||
<select
|
||||
id="deletionDate"
|
||||
name="DeletionDateSelect"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="selectedDeletionDatePreset"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
<small id="deletionDateHelp" class="help-block">{{
|
||||
"deletionDateDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row"
|
||||
*ngIf="formGroup.controls['selectedDeletionDatePreset'].value === 0 || editMode"
|
||||
>
|
||||
<label *ngIf="editMode" for="deletionDateCustom">{{ "deletionDate" | i18n }}</label>
|
||||
<input
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="deletionDate"
|
||||
aria-describedby="deletionDateCustomHelp"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
<small id="deletionDateCustomHelp" class="help-block" *ngIf="editMode">{{
|
||||
"deletionDateDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow *ngIf="!editMode">
|
||||
<label for="expirationDate">{{ "expirationDate" | i18n }}</label>
|
||||
<select
|
||||
id="expirationDate"
|
||||
name="expirationDateSelect"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="selectedExpirationDatePreset"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
<small id="expirationDateHelp" class="help-block">{{
|
||||
"expirationDateDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row"
|
||||
*ngIf="formGroup.controls['selectedExpirationDatePreset'].value === 0 || editMode"
|
||||
>
|
||||
<label *ngIf="editMode" for="expirationDateCustom">{{
|
||||
"expirationDate" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="expirationDate"
|
||||
aria-describedby="expirationDateCustomHelp"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
<small *ngIf="editMode" id="expirationDateCustomHelp" class="help-block">{{
|
||||
"expirationDateDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
@ -129,8 +178,7 @@
|
||||
type="number"
|
||||
name="maxAccessCount"
|
||||
aria-describedby="maxAccessCountHelp"
|
||||
[(ngModel)]="send.maxAccessCount"
|
||||
[readOnly]="disableSend"
|
||||
formControlName="maxAccessCount"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -154,8 +202,7 @@
|
||||
name="password"
|
||||
aria-describedby="passwordHelp"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
[(ngModel)]="password"
|
||||
[readOnly]="disableSend"
|
||||
formControlName="password"
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
@ -167,7 +214,6 @@
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
[attr.aria-pressed]="showPassword"
|
||||
(click)="togglePasswordVisible()"
|
||||
[disabled]="disableSend"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
@ -192,9 +238,8 @@
|
||||
id="notes"
|
||||
name="notes"
|
||||
aria-describedby="notesHelp"
|
||||
[(ngModel)]="send.notes"
|
||||
formControlName="notes"
|
||||
rows="6"
|
||||
[readOnly]="disableSend"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -206,13 +251,7 @@
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="hideEmail">{{ "hideEmail" | i18n }}</label>
|
||||
<input
|
||||
id="hideEmail"
|
||||
type="checkbox"
|
||||
name="HideEmail"
|
||||
[(ngModel)]="send.hideEmail"
|
||||
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"
|
||||
/>
|
||||
<input id="hideEmail" type="checkbox" name="HideEmail" formControlName="hideEmail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -220,13 +259,7 @@
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="disabled">{{ "disableSend" | i18n }}</label>
|
||||
<input
|
||||
id="disabled"
|
||||
type="checkbox"
|
||||
name="disabled"
|
||||
[(ngModel)]="send.disabled"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<input id="disabled" type="checkbox" name="disabled" formControlName="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -238,17 +271,11 @@
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow *ngIf="editMode">
|
||||
<label for="link">{{ "sendLinkLabel" | i18n }}</label>
|
||||
<input id="link" name="link" [ngModel]="link" readonly />
|
||||
<input id="link" name="link" formControlName="link" readonly />
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="copyLink">{{ "copySendLinkOnSave" | i18n }}</label>
|
||||
<input
|
||||
id="copyLink"
|
||||
name="copyLink"
|
||||
[(ngModel)]="copyLink"
|
||||
type="checkbox"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<input id="copyLink" name="copyLink" formControlName="copyLink" type="checkbox" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -259,13 +286,12 @@
|
||||
type="submit"
|
||||
class="primary btn-submit"
|
||||
appA11yTitle="{{ 'save' | i18n }}"
|
||||
[disabled]="form.loading"
|
||||
*ngIf="!disableSend"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span><i class="bwi bwi-save-changes bwi-lg bwi-fw" aria-hidden="true"></i></span>
|
||||
</button>
|
||||
<button type="button" (click)="cancel()" [disabled]="form.loading">
|
||||
<button type="button" (click)="cancel()">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div class="right">
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@ -29,7 +30,8 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
policyService: PolicyService,
|
||||
logService: LogService,
|
||||
sendApiService: SendApiService,
|
||||
dialogService: DialogService
|
||||
dialogService: DialogService,
|
||||
formBuilder: FormBuilder
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
@ -42,7 +44,8 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
logService,
|
||||
stateService,
|
||||
sendApiService,
|
||||
dialogService
|
||||
dialogService,
|
||||
formBuilder
|
||||
);
|
||||
}
|
||||
|
||||
@ -50,6 +53,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
this.password = null;
|
||||
const send = await this.loadSend();
|
||||
this.send = await send.decrypt();
|
||||
this.updateFormValues();
|
||||
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
|
||||
}
|
||||
|
||||
@ -65,4 +69,11 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
|
||||
);
|
||||
}
|
||||
|
||||
async resetAndLoad() {
|
||||
this.sendId = null;
|
||||
this.send = null;
|
||||
await this.load();
|
||||
this.updateFormValues();
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
<ng-container [formGroup]="datesForm">
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow *ngIf="!editMode">
|
||||
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
|
||||
<select
|
||||
id="deletionDate"
|
||||
name="DeletionDateSelect"
|
||||
aria-describedby="deletionDateHelp"
|
||||
formControlName="selectedDeletionDatePreset"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small id="deletionDateHelp" class="help-block">{{ "deletionDateDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="box-content-row" *ngIf="selectedDeletionDatePreset.value === 0 || editMode">
|
||||
<label *ngIf="editMode" for="deletionDateCustom">{{ "deletionDate" | i18n }}</label>
|
||||
<input
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="deletionDate"
|
||||
aria-describedby="deletionDateCustomHelp"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
<small id="deletionDateCustomHelp" class="help-block" *ngIf="editMode">{{
|
||||
"deletionDateDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow *ngIf="!editMode">
|
||||
<label for="expirationDate">{{ "expirationDate" | i18n }}</label>
|
||||
<select
|
||||
id="expirationDate"
|
||||
name="expirationDateSelect"
|
||||
aria-describedby="expirationDateHelp"
|
||||
formControlName="selectedExpirationDatePreset"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small id="expirationDateHelp" class="help-block">{{ "expirationDateDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="box-content-row" *ngIf="selectedExpirationDatePreset.value === 0 || editMode">
|
||||
<label *ngIf="editMode" for="expirationDateCustom">{{ "expirationDate" | i18n }}</label>
|
||||
<input
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="expirationDate"
|
||||
aria-describedby="expirationDateCustomHelp"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
<small *ngIf="editMode" id="expirationDateCustomHelp" class="help-block">{{
|
||||
"expirationDateDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
@ -1,38 +0,0 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component, OnChanges } from "@angular/core";
|
||||
import { ControlContainer, NgForm } from "@angular/forms";
|
||||
|
||||
import { EffluxDatesComponent as BaseEffluxDatesComponent } from "@bitwarden/angular/tools/send/efflux-dates.component";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-send-efflux-dates",
|
||||
templateUrl: "efflux-dates.component.html",
|
||||
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
|
||||
})
|
||||
export class EffluxDatesComponent extends BaseEffluxDatesComponent implements OnChanges {
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected datePipe: DatePipe
|
||||
) {
|
||||
super(i18nService, platformUtilsService, datePipe);
|
||||
}
|
||||
|
||||
// We reuse the same form on desktop and just swap content, so need to watch these to maintin proper values.
|
||||
ngOnChanges() {
|
||||
this.selectedExpirationDatePreset.setValue(0);
|
||||
this.selectedDeletionDatePreset.setValue(0);
|
||||
this.defaultDeletionDateTime.setValue(
|
||||
this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
|
||||
);
|
||||
if (this.initialExpirationDate) {
|
||||
this.defaultExpirationDateTime.setValue(
|
||||
this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm")
|
||||
);
|
||||
} else {
|
||||
this.defaultExpirationDateTime.setValue(null);
|
||||
}
|
||||
}
|
||||
}
|
@ -91,12 +91,10 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
|
||||
this.searchBarService.setEnabled(false);
|
||||
}
|
||||
|
||||
addSend() {
|
||||
async addSend() {
|
||||
this.action = Action.Add;
|
||||
if (this.addEditComponent != null) {
|
||||
this.addEditComponent.sendId = null;
|
||||
this.addEditComponent.send = null;
|
||||
this.addEditComponent.load();
|
||||
await this.addEditComponent.resetAndLoad();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,6 @@ import { GeneratorComponent } from "../tools/generator.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-history.component";
|
||||
import { AccessComponent } from "../tools/send/access.component";
|
||||
import { AddEditComponent as SendAddEditComponent } from "../tools/send/add-edit.component";
|
||||
import { EffluxDatesComponent as SendEffluxDatesComponent } from "../tools/send/efflux-dates.component";
|
||||
import { ToolsComponent } from "../tools/tools.component";
|
||||
import { PasswordRepromptComponent } from "../vault/components/password-reprompt.component";
|
||||
import { PremiumBadgeComponent } from "../vault/components/premium-badge.component";
|
||||
@ -198,7 +197,6 @@ import { SharedModule } from "./shared.module";
|
||||
SecurityKeysComponent,
|
||||
SelectableAvatarComponent,
|
||||
SendAddEditComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
@ -302,7 +300,6 @@ import { SharedModule } from "./shared.module";
|
||||
SecurityKeysComponent,
|
||||
SelectableAvatarComponent,
|
||||
SendAddEditComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
|
@ -1,303 +1,276 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="sendAddEditTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
||||
<form
|
||||
class="modal-content"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="sendAddEditTitle">{{ title }}</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<form
|
||||
[formGroup]="formGroup"
|
||||
[bitSubmit]="submitAndClose"
|
||||
[appApiAction]="formPromise"
|
||||
autocomplete="off"
|
||||
>
|
||||
<bit-dialog dialogSize="large">
|
||||
<span bitDialogTitle>
|
||||
{{ title }}
|
||||
</span>
|
||||
<span bitDialogContent *ngIf="send">
|
||||
<bit-callout *ngIf="disableSend">
|
||||
{{ "sendDisabledWarning" | i18n }}
|
||||
</bit-callout>
|
||||
<bit-callout *ngIf="!disableSend && disableHideEmail">
|
||||
{{ "sendOptionsPolicyInEffect" | i18n }}
|
||||
<ul class="tw-mb-0">
|
||||
<li>{{ "sendDisableHideEmailInEffect" | i18n }}</li>
|
||||
</ul>
|
||||
</bit-callout>
|
||||
<bit-form-field class="tw-w-1/2">
|
||||
<bit-label for="name">{{ "name" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="name" />
|
||||
<bit-hint>{{ "sendNameDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<div class="tw-flex" *ngIf="!editMode">
|
||||
<bit-radio-group formControlName="type">
|
||||
<bit-label>{{ "whatTypeOfSend" | i18n }}</bit-label>
|
||||
|
||||
<bit-radio-button
|
||||
*ngFor="let o of typeOptions"
|
||||
id="type_{{ o.value }}"
|
||||
class="tw-block"
|
||||
[value]="o.value"
|
||||
>
|
||||
<bit-label>{{ o.name }}</bit-label>
|
||||
</bit-radio-button>
|
||||
</bit-radio-group>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="send">
|
||||
<app-callout *ngIf="disableSend">
|
||||
<span>{{ "sendDisabledWarning" | i18n }}</span>
|
||||
</app-callout>
|
||||
<app-callout *ngIf="!disableSend && disableHideEmail">
|
||||
<span>{{ "sendOptionsPolicyInEffect" | i18n }}</span>
|
||||
<ul class="mb-0">
|
||||
<li>{{ "sendDisableHideEmailInEffect" | i18n }}</li>
|
||||
</ul>
|
||||
</app-callout>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="name">{{ "name" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="send.name"
|
||||
required
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "sendNameDesc" | i18n }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngIf="!editMode">
|
||||
<div class="col-6 form-group">
|
||||
<label>{{ "whatTypeOfSend" | i18n }}</label>
|
||||
<div class="form-check" *ngFor="let o of typeOptions">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
[(ngModel)]="send.type"
|
||||
name="Type_{{ o.value }}"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="typeChanged()"
|
||||
[checked]="send.type === o.value"
|
||||
/>
|
||||
<label class="form-check-label" for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Text -->
|
||||
<ng-container *ngIf="send.type === sendType.Text">
|
||||
<div class="form-group">
|
||||
<label for="text">{{ "sendTypeText" | i18n }}</label>
|
||||
<textarea
|
||||
id="text"
|
||||
name="Text.Text"
|
||||
rows="6"
|
||||
[(ngModel)]="send.text.text"
|
||||
class="form-control"
|
||||
[readOnly]="disableSend"
|
||||
></textarea>
|
||||
<small class="form-text text-muted">{{ "sendTextDesc" | i18n }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="send.text.hidden"
|
||||
id="text-hidden"
|
||||
name="Text.Hidden"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<label class="form-check-label" for="text-hidden">{{
|
||||
"textHiddenByDefault" | i18n
|
||||
}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<!-- File -->
|
||||
<ng-container *ngIf="send.type === sendType.File">
|
||||
<div class="form-group">
|
||||
<div *ngIf="editMode">
|
||||
<strong class="d-block">{{ "file" | i18n }}</strong>
|
||||
<!-- Text -->
|
||||
<ng-container *ngIf="type === sendType.Text">
|
||||
<bit-form-field>
|
||||
<bit-label for="text">{{ "sendTypeText" | i18n }}</bit-label>
|
||||
<textarea bitInput id="text" rows="6" formControlName="text"></textarea>
|
||||
<bit-hint>{{ "sendTextDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="textHidden" />
|
||||
<bit-label>{{ "textHiddenByDefault" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</ng-container>
|
||||
<!-- File -->
|
||||
<ng-container *ngIf="type === sendType.File">
|
||||
<div class="tw-flex">
|
||||
<div *ngIf="editMode">
|
||||
<bit-label>{{ "file" | i18n }}</bit-label>
|
||||
<p bitTypography="body1" class="tw-mb-0">
|
||||
{{ send.file.fileName }} ({{ send.file.sizeName }})
|
||||
</div>
|
||||
<div *ngIf="!editMode">
|
||||
<label for="file">{{ "file" | i18n }}</label>
|
||||
<input
|
||||
type="file"
|
||||
id="file"
|
||||
class="form-control-file"
|
||||
name="file"
|
||||
required
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<small class="form-text text-muted"
|
||||
>{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}</small
|
||||
>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h3 class="mt-5">{{ "share" | i18n }}</h3>
|
||||
<div class="form-group" *ngIf="link">
|
||||
<label for="link">{{ "sendLinkLabel" | i18n }}</label>
|
||||
<input type="text" readonly id="link" name="Link" [ngModel]="link" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<bit-form-field *ngIf="!editMode">
|
||||
<bit-label>{{ "file" | i18n }}</bit-label>
|
||||
<div>
|
||||
<button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
{{ selectedFile?.name ?? ("noFileChosen" | i18n) }}
|
||||
</div>
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="copyLink"
|
||||
id="copy-link"
|
||||
name="CopyLink"
|
||||
bitInput
|
||||
#fileSelector
|
||||
hidden
|
||||
type="file"
|
||||
id="file"
|
||||
name="file"
|
||||
formControlName="file"
|
||||
(change)="setSelectedFile($event)"
|
||||
/>
|
||||
<label class="form-check-label" for="copy-link">{{
|
||||
"copySendLinkOnSave" | i18n
|
||||
}}</label>
|
||||
</div>
|
||||
<bit-hint>{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div
|
||||
id="options-header"
|
||||
class="section-header d-flex flex-row align-items-center mt-5"
|
||||
(click)="toggleOptions()"
|
||||
>
|
||||
<h3 class="mb-0 mr-2">
|
||||
<button type="button" appStopClick class="header-expandable">
|
||||
<i
|
||||
class="bwi"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-angle-right': !showOptions, 'bwi-angle-down': showOptions }"
|
||||
></i>
|
||||
{{ "options" | i18n }}
|
||||
</button>
|
||||
</h3>
|
||||
</div>
|
||||
<div id="options" [hidden]="!showOptions">
|
||||
<app-send-efflux-dates
|
||||
[initialDeletionDate]="send.deletionDate"
|
||||
[initialExpirationDate]="send.expirationDate"
|
||||
[editMode]="editMode"
|
||||
[disabled]="disableSend"
|
||||
(datesChanged)="setDates($event)"
|
||||
>
|
||||
</app-send-efflux-dates>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</label>
|
||||
<input
|
||||
id="maxAccessCount"
|
||||
class="form-control"
|
||||
type="number"
|
||||
name="MaxAccessCount"
|
||||
[(ngModel)]="send.maxAccessCount"
|
||||
min="1"
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
<div class="form-text text-muted small">{{ "maxAccessCountDesc" | i18n }}</div>
|
||||
</div>
|
||||
<div class="col-6 form-group" *ngIf="editMode">
|
||||
<label for="accessCount">{{ "currentAccessCount" | i18n }}</label>
|
||||
<input
|
||||
id="accessCount"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="AccessCount"
|
||||
readonly
|
||||
[(ngModel)]="send.accessCount"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="password" *ngIf="!hasPassword">{{ "password" | i18n }}</label>
|
||||
<label for="password" *ngIf="hasPassword">{{ "newPassword" | i18n }}</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
id="password"
|
||||
class="form-control text-monospace"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="Password"
|
||||
[(ngModel)]="password"
|
||||
[readOnly]="disableSend"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePasswordVisible()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{ "sendPasswordDesc" | i18n }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notes">{{ "notes" | i18n }}</label>
|
||||
<textarea
|
||||
id="notes"
|
||||
name="Notes"
|
||||
rows="6"
|
||||
[(ngModel)]="send.notes"
|
||||
class="form-control"
|
||||
[readOnly]="disableSend"
|
||||
></textarea>
|
||||
<div class="form-text text-muted small">{{ "sendNotesDesc" | i18n }}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="send.hideEmail"
|
||||
id="hideEmail"
|
||||
name="HideEmail"
|
||||
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"
|
||||
/>
|
||||
<label class="form-check-label" for="hideEmail">
|
||||
{{ "hideEmail" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="send.disabled"
|
||||
id="disabled"
|
||||
name="Disabled"
|
||||
[disabled]="disableSend"
|
||||
/>
|
||||
<label class="form-check-label" for="disabled">{{ "disableThisSend" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit manual"
|
||||
[ngClass]="{ loading: form.loading }"
|
||||
[disabled]="form.loading || disableSend"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div class="ml-auto" *ngIf="send">
|
||||
<button
|
||||
#deleteBtn
|
||||
type="button"
|
||||
(click)="delete()"
|
||||
class="btn btn-outline-danger"
|
||||
appA11yTitle="{{ 'delete' | i18n }}"
|
||||
*ngIf="editMode"
|
||||
[disabled]="$any(deleteBtn).loading"
|
||||
[appApiAction]="deletePromise"
|
||||
>
|
||||
</ng-container>
|
||||
<h4 bitTypography="h4" class="tw-mt-5">{{ "share" | i18n }}</h4>
|
||||
|
||||
<bit-form-field *ngIf="link">
|
||||
<bit-label for="link">{{ "sendLinkLabel" | i18n }}</bit-label>
|
||||
<input bitInput type="text" readonly formControlName="link" />
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="copyLink" />
|
||||
<bit-label>{{ "copySendLinkOnSave" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<div class="tw-mt-5 tw-flex" (click)="toggleOptions()">
|
||||
<h4 bitTypography="h4" class="tw-mb-0 tw-mr-2">
|
||||
<button type="button" bitLink appStopClick>
|
||||
<i
|
||||
class="bwi bwi-trash bwi-lg bwi-fw"
|
||||
[hidden]="$any(deleteBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!$any(deleteBtn).loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
class="bwi"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-angle-right': !showOptions, 'bwi-angle-down': showOptions }"
|
||||
></i>
|
||||
{{ "options" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="options" [hidden]="!showOptions">
|
||||
<div class="tw-flex">
|
||||
<div *ngIf="!editMode" class="tw-w-1/2 tw-pr-3">
|
||||
<bit-form-field>
|
||||
<bit-label for="deletionDate">{{ "deletionDate" | i18n }}</bit-label>
|
||||
<bit-select
|
||||
id="deletionDate"
|
||||
name="SelectedDeletionDatePreset"
|
||||
formControlName="selectedDeletionDatePreset"
|
||||
>
|
||||
<bit-option
|
||||
*ngFor="let o of deletionDatePresets"
|
||||
[value]="o.value"
|
||||
[label]="o.name"
|
||||
></bit-option>
|
||||
</bit-select>
|
||||
<ng-container *ngIf="formGroup.controls['selectedDeletionDatePreset'].value === 0">
|
||||
<input
|
||||
bitInput
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
</ng-container>
|
||||
<bit-hint>{{ "deletionDateDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div *ngIf="editMode" class="tw-w-1/2 tw-pr-3">
|
||||
<bit-form-field>
|
||||
<bit-label for="deletionDate">{{ "deletionDate" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
<bit-hint>{{ "deletionDateDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div *ngIf="!editMode" class="tw-w-1/2 tw-pl-3">
|
||||
<bit-form-field>
|
||||
<bit-label for="expirationDate">
|
||||
{{ "expirationDate" | i18n }}
|
||||
</bit-label>
|
||||
<bit-select
|
||||
bitInput
|
||||
id="expirationDate"
|
||||
name="SelectedExpirationDatePreset"
|
||||
formControlName="selectedExpirationDatePreset"
|
||||
>
|
||||
<bit-option
|
||||
*ngFor="let e of expirationDatePresets"
|
||||
[value]="e.value"
|
||||
[label]="e.name"
|
||||
></bit-option>
|
||||
</bit-select>
|
||||
<ng-container *ngIf="formGroup.controls['selectedExpirationDatePreset'].value === 0">
|
||||
<input
|
||||
bitInput
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
</ng-container>
|
||||
<bit-hint>{{ "expirationDateDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div *ngIf="editMode" class="tw-w-1/2 tw-pl-3">
|
||||
<bit-form-field>
|
||||
<bit-label class="tw-flex" for="expirationDate">
|
||||
{{ "expirationDate" | i18n }}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
bitLink
|
||||
appStopClick
|
||||
(click)="clearExpiration()"
|
||||
*ngIf="!disableSend"
|
||||
class="tw-ml-auto"
|
||||
>
|
||||
{{ "clear" | i18n }}
|
||||
</button>
|
||||
</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
<bit-hint>{{ "expirationDateDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-flex">
|
||||
<bit-form-field class="tw-w-1/2 tw-pr-3">
|
||||
<bit-label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</bit-label>
|
||||
<input bitInput type="number" formControlName="maxAccessCount" min="1" />
|
||||
<bit-hint>{{ "maxAccessCountDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-w-1/2 tw-pl-3" *ngIf="editMode">
|
||||
<bit-label for="accessCount">{{ "currentAccessCount" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="accessCount" readonly />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div class="tw-flex">
|
||||
<bit-form-field class="tw-w-1/2 tw-pr-3">
|
||||
<bit-label for="password" *ngIf="!hasPassword">{{ "password" | i18n }}</bit-label>
|
||||
<bit-label for="password" *ngIf="hasPassword">{{ "newPassword" | i18n }}</bit-label>
|
||||
|
||||
<input bitInput type="password" formControlName="password" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<bit-hint>{{ "sendPasswordDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "notes" | i18n }}</bit-label>
|
||||
<textarea bitInput formControlName="notes" rows="6"></textarea>
|
||||
<bit-hint>{{ "sendNotesDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="hideEmail" />
|
||||
<bit-label>{{ "hideEmail" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="disabled" />
|
||||
<bit-label>{{ "disableThisSend" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</div>
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button
|
||||
type="submit"
|
||||
bitButton
|
||||
bitFormButton
|
||||
[appA11yTitle]="'save' | i18n"
|
||||
buttonType="primary"
|
||||
>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
[appA11yTitle]="'cancel' | i18n"
|
||||
bitDialogClose
|
||||
>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="editMode"
|
||||
type="button"
|
||||
class="tw-ml-auto"
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
[appA11yTitle]="'delete' | i18n"
|
||||
[bitAction]="deleteAndClose"
|
||||
></button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@ -19,6 +21,7 @@ import { DialogService } from "@bitwarden/components";
|
||||
})
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
override componentName = "app-send-add-edit";
|
||||
protected selectedFile: File;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
@ -31,7 +34,10 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
policyService: PolicyService,
|
||||
logService: LogService,
|
||||
sendApiService: SendApiService,
|
||||
dialogService: DialogService
|
||||
dialogService: DialogService,
|
||||
formBuilder: FormBuilder,
|
||||
protected dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) params: { sendId: string }
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
@ -44,8 +50,11 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
logService,
|
||||
stateService,
|
||||
sendApiService,
|
||||
dialogService
|
||||
dialogService,
|
||||
formBuilder
|
||||
);
|
||||
|
||||
this.sendId = params.sendId;
|
||||
}
|
||||
|
||||
async copyLinkToClipboard(link: string): Promise<void | boolean> {
|
||||
@ -55,4 +64,29 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
window.setTimeout(() => resolve(super.copyLinkToClipboard(link)), 500);
|
||||
});
|
||||
}
|
||||
|
||||
protected setSelectedFile(event: Event) {
|
||||
const fileInputEl = <HTMLInputElement>event.target;
|
||||
const file = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null;
|
||||
this.selectedFile = file;
|
||||
}
|
||||
|
||||
submitAndClose = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await this.submit();
|
||||
if (success) {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
};
|
||||
|
||||
deleteAndClose = async () => {
|
||||
const success = await this.delete();
|
||||
if (success) {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,188 +0,0 @@
|
||||
<div class="row" [formGroup]="datesForm">
|
||||
<div class="col-6 form-group">
|
||||
<label for="deletionDate">{{ "deletionDate" | i18n }}</label>
|
||||
<ng-template #deletionDateCustom>
|
||||
<ng-container [ngSwitch]="browserPath">
|
||||
<ng-container *ngSwitchCase="'firefox'">
|
||||
<div class="d-flex justify-content-around">
|
||||
<input
|
||||
id="deletionDateCustomFallback"
|
||||
class="form-control mt-1"
|
||||
type="date"
|
||||
name="DeletionDateFallback"
|
||||
formControlName="fallbackDeletionDate"
|
||||
required
|
||||
placeholder="MM/DD/YYYY"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<input
|
||||
id="deletionTimeCustomFallback"
|
||||
class="form-control mt-1 ml-1"
|
||||
type="time"
|
||||
name="DeletionTimeDate"
|
||||
formControlName="fallbackDeletionTime"
|
||||
required
|
||||
placeholder="HH:MM AM/PM"
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'safari'">
|
||||
<div class="d-flex justify-content-around">
|
||||
<input
|
||||
id="deletionDateCustomFallback"
|
||||
class="form-control mt-1"
|
||||
type="date"
|
||||
name="DeletionDateFallback"
|
||||
formControlName="fallbackDeletionDate"
|
||||
required
|
||||
placeholder="MM/DD/YYYY"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<select
|
||||
id="deletionTimeCustomFallback"
|
||||
class="form-control mt-1 ml-1"
|
||||
[required]="!editMode"
|
||||
formControlName="fallbackDeletionTime"
|
||||
name="SafariDeletionTime"
|
||||
>
|
||||
<option
|
||||
*ngFor="let o of safariDeletionTimePresetOptions"
|
||||
[ngValue]="o.twentyFourHour"
|
||||
>
|
||||
{{ o.twelveHour }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<input
|
||||
id="deletionDateCustom"
|
||||
class="form-control mt-1"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
required
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<div *ngIf="!editMode">
|
||||
<select
|
||||
id="deletionDate"
|
||||
name="SelectedDeletionDatePreset"
|
||||
formControlName="selectedDeletionDatePreset"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<ng-container *ngIf="selectedDeletionDatePreset.value === 0">
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom"> </ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="editMode">
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom"> </ng-container>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{ "deletionDateDesc" | i18n }}</div>
|
||||
</div>
|
||||
<div class="col-6 form-group">
|
||||
<div class="d-flex">
|
||||
<label for="expirationDate">{{ "expirationDate" | i18n }}</label>
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="clearExpiration()"
|
||||
class="ml-auto"
|
||||
*ngIf="editMode && !disabled"
|
||||
>
|
||||
{{ "clear" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<ng-template #expirationDateCustom>
|
||||
<ng-container [ngSwitch]="browserPath">
|
||||
<div *ngSwitchCase="'firefox'" class="d-flex justify-content-around">
|
||||
<input
|
||||
id="expirationDateCustomFallback"
|
||||
class="form-control mt-1"
|
||||
type="date"
|
||||
name="ExpirationDateFallback"
|
||||
formControlName="fallbackExpirationDate"
|
||||
[required]="!editMode"
|
||||
placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disabled"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<input
|
||||
id="expirationTimeCustomFallback"
|
||||
class="form-control mt-1 ml-1"
|
||||
type="time"
|
||||
name="ExpirationTimeFallback"
|
||||
formControlName="fallbackExpirationTime"
|
||||
[required]="!editMode"
|
||||
placeholder="HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</div>
|
||||
<!-- non-default cases are not showing up -->
|
||||
<div *ngSwitchCase="'safari'" class="d-flex justify-content-around">
|
||||
<input
|
||||
id="expirationDateCustomFallback"
|
||||
class="form-control mt-1"
|
||||
type="date"
|
||||
name="ExpirationDateFallback"
|
||||
formControlName="fallbackExpirationDate"
|
||||
[required]="!editMode"
|
||||
placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disabled"
|
||||
data-date-format="mm/dd/yyyy"
|
||||
/>
|
||||
<select
|
||||
id="expirationTimeCustomFallback"
|
||||
class="form-control mt-1 ml-1"
|
||||
[required]="!editMode"
|
||||
formControlName="fallbackExpirationTime"
|
||||
name="SafariExpirationTime"
|
||||
>
|
||||
<option
|
||||
*ngFor="let o of safariExpirationTimePresetOptions"
|
||||
[ngValue]="o.twentyFourHour"
|
||||
>
|
||||
{{ o.twelveHour }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<input
|
||||
id="expirationDateCustom"
|
||||
class="form-control mt-1"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
[readOnly]="disabled"
|
||||
/>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<div *ngIf="!editMode">
|
||||
<select
|
||||
id="expirationDate"
|
||||
name="SelectedExpirationDatePreset"
|
||||
formControlName="selectedExpirationDatePreset"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<ng-container *ngIf="selectedExpirationDatePreset.value === 0">
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom"> </ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="editMode">
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom"> </ng-container>
|
||||
</div>
|
||||
<div class="form-text text-muted small">{{ "expirationDateDesc" | i18n }}</div>
|
||||
</div>
|
||||
</div>
|
@ -1,22 +0,0 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { ControlContainer, NgForm } from "@angular/forms";
|
||||
|
||||
import { EffluxDatesComponent as BaseEffluxDatesComponent } from "@bitwarden/angular/tools/send/efflux-dates.component";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-send-efflux-dates",
|
||||
templateUrl: "efflux-dates.component.html",
|
||||
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
|
||||
})
|
||||
export class EffluxDatesComponent extends BaseEffluxDatesComponent {
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected datePipe: DatePipe
|
||||
) {
|
||||
super(i18nService, platformUtilsService, datePipe);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Component, NgZone, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component";
|
||||
@ -98,29 +99,17 @@ export class SendComponent extends BaseSendComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const component = await this.editSend(null);
|
||||
component.type = this.type;
|
||||
await this.editSend(null);
|
||||
}
|
||||
|
||||
async editSend(send: SendView) {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
AddEditComponent,
|
||||
this.sendAddEditModalRef,
|
||||
(comp) => {
|
||||
comp.sendId = send == null ? null : send.id;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onSavedSend.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onDeletedSend.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
);
|
||||
const dialog = this.dialogService.open(AddEditComponent, {
|
||||
data: {
|
||||
sendId: send == null ? null : send.id,
|
||||
},
|
||||
});
|
||||
|
||||
return childComponent;
|
||||
await lastValueFrom(dialog.closed);
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@ -20,6 +21,23 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
// Value = hours
|
||||
enum DatePreset {
|
||||
OneHour = 1,
|
||||
OneDay = 24,
|
||||
TwoDays = 48,
|
||||
ThreeDays = 72,
|
||||
SevenDays = 168,
|
||||
ThirtyDays = 720,
|
||||
Custom = 0,
|
||||
Never = null,
|
||||
}
|
||||
|
||||
interface DatePresetSelectOption {
|
||||
name: string;
|
||||
value: DatePreset;
|
||||
}
|
||||
|
||||
@Directive()
|
||||
export class AddEditComponent implements OnInit, OnDestroy {
|
||||
@Input() sendId: string;
|
||||
@ -29,12 +47,25 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
@Output() onDeletedSend = new EventEmitter<SendView>();
|
||||
@Output() onCancelled = new EventEmitter<SendView>();
|
||||
|
||||
deletionDatePresets: DatePresetSelectOption[] = [
|
||||
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
|
||||
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
|
||||
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
|
||||
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
|
||||
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
|
||||
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
|
||||
{ name: this.i18nService.t("custom"), value: DatePreset.Custom },
|
||||
];
|
||||
|
||||
expirationDatePresets: DatePresetSelectOption[] = [
|
||||
{ name: this.i18nService.t("never"), value: DatePreset.Never },
|
||||
...this.deletionDatePresets,
|
||||
];
|
||||
|
||||
copyLink = false;
|
||||
disableSend = false;
|
||||
disableHideEmail = false;
|
||||
send: SendView;
|
||||
deletionDate: string;
|
||||
expirationDate: string;
|
||||
hasPassword: boolean;
|
||||
password: string;
|
||||
showPassword = false;
|
||||
@ -51,6 +82,27 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
private sendLinkBaseUrl: string;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
protected formGroup = this.formBuilder.group({
|
||||
name: ["", Validators.required],
|
||||
text: [],
|
||||
textHidden: [false],
|
||||
fileContents: [],
|
||||
file: [null, Validators.required],
|
||||
link: [],
|
||||
copyLink: false,
|
||||
maxAccessCount: [],
|
||||
accessCount: [],
|
||||
password: [],
|
||||
notes: [],
|
||||
hideEmail: false,
|
||||
disabled: false,
|
||||
type: [],
|
||||
defaultExpirationDateTime: [],
|
||||
defaultDeletionDateTime: ["", Validators.required],
|
||||
selectedDeletionDatePreset: [DatePreset.SevenDays, Validators.required],
|
||||
selectedExpirationDatePreset: [],
|
||||
});
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
@ -59,10 +111,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
protected sendService: SendService,
|
||||
protected messagingService: MessagingService,
|
||||
protected policyService: PolicyService,
|
||||
private logService: LogService,
|
||||
protected logService: LogService,
|
||||
protected stateService: StateService,
|
||||
protected sendApiService: SendApiService,
|
||||
protected dialogService: DialogService
|
||||
protected dialogService: DialogService,
|
||||
protected formBuilder: FormBuilder
|
||||
) {
|
||||
this.typeOptions = [
|
||||
{ name: i18nService.t("sendTypeFile"), value: SendType.File },
|
||||
@ -72,7 +125,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get link(): string {
|
||||
if (this.send.id != null && this.send.accessId != null) {
|
||||
if (this.send != null && this.send.id != null && this.send.accessId != null) {
|
||||
return this.sendLinkBaseUrl + this.send.accessId + "/" + this.send.urlB64Key;
|
||||
}
|
||||
return null;
|
||||
@ -92,13 +145,39 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disableSend = policyAppliesToActiveUser;
|
||||
if (this.disableSend) {
|
||||
this.formGroup.disable();
|
||||
}
|
||||
});
|
||||
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.SendOptions, (p) => p.data.disableHideEmail)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((policyAppliesToActiveUser) => {
|
||||
this.disableHideEmail = policyAppliesToActiveUser;
|
||||
if ((this.disableHideEmail = policyAppliesToActiveUser)) {
|
||||
this.formGroup.controls.hideEmail.disable();
|
||||
}
|
||||
});
|
||||
|
||||
this.formGroup.controls.type.valueChanges.subscribe((val) => {
|
||||
this.type = val;
|
||||
this.typeChanged();
|
||||
});
|
||||
|
||||
this.formGroup.controls.selectedDeletionDatePreset.valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((datePreset) => {
|
||||
datePreset === DatePreset.Custom
|
||||
? this.formGroup.controls.defaultDeletionDateTime.enable()
|
||||
: this.formGroup.controls.defaultDeletionDateTime.disable();
|
||||
});
|
||||
|
||||
this.formGroup.controls.hideEmail.valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((val) => {
|
||||
if (!val && this.disableHideEmail) {
|
||||
this.formGroup.controls.hideEmail.disable();
|
||||
}
|
||||
});
|
||||
|
||||
await this.load();
|
||||
@ -117,29 +196,33 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
return this.i18nService.t(this.editMode ? "editSend" : "createSend");
|
||||
}
|
||||
|
||||
setDates(event: { deletionDate: string; expirationDate: string }) {
|
||||
this.deletionDate = event.deletionDate;
|
||||
this.expirationDate = event.expirationDate;
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
this.emailVerified = await this.stateService.getEmailVerified();
|
||||
if (!this.canAccessPremium || !this.emailVerified) {
|
||||
this.type = SendType.Text;
|
||||
}
|
||||
|
||||
this.type = !this.canAccessPremium || !this.emailVerified ? SendType.Text : SendType.File;
|
||||
if (this.send == null) {
|
||||
if (this.editMode) {
|
||||
const send = this.loadSend();
|
||||
this.send = await send.decrypt();
|
||||
this.type = this.send.type;
|
||||
this.updateFormValues();
|
||||
if (this.send.hideEmail) {
|
||||
this.formGroup.controls.hideEmail.enable();
|
||||
}
|
||||
} else {
|
||||
this.send = new SendView();
|
||||
this.send.type = this.type == null ? SendType.File : this.type;
|
||||
this.send.type = this.type;
|
||||
this.send.file = new SendFileView();
|
||||
this.send.text = new SendTextView();
|
||||
this.send.deletionDate = new Date();
|
||||
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
|
||||
this.formGroup.controls.type.patchValue(this.send.type);
|
||||
|
||||
this.formGroup.patchValue({
|
||||
selectedDeletionDatePreset: DatePreset.SevenDays,
|
||||
selectedExpirationDatePreset: DatePreset.Never,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,6 +230,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async submit(): Promise<boolean> {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.disableSend) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
@ -156,6 +241,17 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.send.name = this.formGroup.controls.name.value;
|
||||
this.send.text.text = this.formGroup.controls.text.value;
|
||||
this.send.text.hidden = this.formGroup.controls.textHidden.value;
|
||||
this.send.maxAccessCount = this.formGroup.controls.maxAccessCount.value;
|
||||
this.send.accessCount = this.formGroup.controls.accessCount.value;
|
||||
this.send.password = this.formGroup.controls.password.value;
|
||||
this.send.notes = this.formGroup.controls.notes.value;
|
||||
this.send.hideEmail = this.formGroup.controls.hideEmail.value;
|
||||
this.send.disabled = this.formGroup.controls.disabled.value;
|
||||
this.send.type = this.type;
|
||||
|
||||
if (this.send.name == null || this.send.name === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
@ -166,7 +262,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
let file: File = null;
|
||||
if (this.send.type === SendType.File && !this.editMode) {
|
||||
if (this.type === SendType.File && !this.editMode) {
|
||||
const fileEl = document.getElementById("file") as HTMLInputElement;
|
||||
const files = fileEl.files;
|
||||
if (files == null || files.length === 0) {
|
||||
@ -190,7 +286,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.password != null && this.password.trim() === "") {
|
||||
if (
|
||||
this.formGroup.controls.password.value != null &&
|
||||
this.formGroup.controls.password.value.trim() === ""
|
||||
) {
|
||||
this.password = null;
|
||||
}
|
||||
|
||||
@ -204,7 +303,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.send.accessId = encSend[0].accessId;
|
||||
}
|
||||
this.onSavedSend.emit(this.send);
|
||||
if (this.copyLink && this.link != null) {
|
||||
if (this.formGroup.controls.copyLink.value && this.link != null) {
|
||||
await this.handleCopyLinkToClipboard();
|
||||
return;
|
||||
}
|
||||
@ -227,7 +326,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
|
||||
}
|
||||
|
||||
async delete(): Promise<boolean> {
|
||||
protected async delete(): Promise<boolean> {
|
||||
if (this.deletePromise != null) {
|
||||
return false;
|
||||
}
|
||||
@ -257,7 +356,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
typeChanged() {
|
||||
if (this.send.type === SendType.File && !this.alertShown) {
|
||||
if (this.type === SendType.File && !this.alertShown) {
|
||||
if (!this.canAccessPremium) {
|
||||
this.alertShown = true;
|
||||
this.messagingService.send("premiumRequired");
|
||||
@ -266,6 +365,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.messagingService.send("emailVerificationRequired");
|
||||
}
|
||||
}
|
||||
this.type === SendType.Text || this.editMode
|
||||
? this.formGroup.controls.file.disable()
|
||||
: this.formGroup.controls.file.enable();
|
||||
}
|
||||
|
||||
toggleOptions() {
|
||||
@ -277,17 +379,23 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> {
|
||||
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
|
||||
const sendData = await this.sendService.encrypt(
|
||||
this.send,
|
||||
file,
|
||||
this.formGroup.controls.password.value,
|
||||
null
|
||||
);
|
||||
|
||||
// Parse dates
|
||||
try {
|
||||
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
|
||||
sendData[0].deletionDate =
|
||||
this.formattedDeletionDate == null ? null : new Date(this.formattedDeletionDate);
|
||||
} catch {
|
||||
sendData[0].deletionDate = null;
|
||||
}
|
||||
try {
|
||||
sendData[0].expirationDate =
|
||||
this.expirationDate == null ? null : new Date(this.expirationDate);
|
||||
this.formattedExpirationDate == null ? null : new Date(this.formattedExpirationDate);
|
||||
} catch {
|
||||
sendData[0].expirationDate = null;
|
||||
}
|
||||
@ -299,6 +407,34 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.showPassword = !this.showPassword;
|
||||
document.getElementById("password").focus();
|
||||
}
|
||||
|
||||
updateFormValues() {
|
||||
this.formGroup.patchValue({
|
||||
name: this.send?.name ?? "",
|
||||
text: this.send?.text?.text ?? "",
|
||||
textHidden: this.send?.text?.hidden ?? false,
|
||||
link: this.link ?? "",
|
||||
maxAccessCount: this.send?.maxAccessCount,
|
||||
accessCount: this.send?.accessCount ?? 0,
|
||||
notes: this.send?.notes ?? "",
|
||||
hideEmail: this.send?.hideEmail ?? false,
|
||||
disabled: this.send?.disabled ?? false,
|
||||
type: this.send.type ?? this.type,
|
||||
password: "",
|
||||
|
||||
selectedDeletionDatePreset: this.editMode ? DatePreset.Custom : DatePreset.SevenDays,
|
||||
selectedExpirationDatePreset: this.editMode ? DatePreset.Custom : DatePreset.Never,
|
||||
defaultExpirationDateTime:
|
||||
this.send.expirationDate != null
|
||||
? this.datePipe.transform(new Date(this.send.expirationDate), "yyyy-MM-ddTHH:mm")
|
||||
: null,
|
||||
defaultDeletionDateTime: this.datePipe.transform(
|
||||
new Date(this.send.deletionDate),
|
||||
"yyyy-MM-ddTHH:mm"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private async handleCopyLinkToClipboard() {
|
||||
const copySuccess = await this.copyLinkToClipboard(this.link);
|
||||
if (copySuccess ?? true) {
|
||||
@ -319,4 +455,46 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
await this.copyLinkToClipboard(this.link);
|
||||
}
|
||||
}
|
||||
|
||||
clearExpiration() {
|
||||
this.formGroup.controls.defaultExpirationDateTime.patchValue(null);
|
||||
}
|
||||
|
||||
get formattedExpirationDate(): string {
|
||||
switch (this.formGroup.controls.selectedExpirationDatePreset.value as DatePreset) {
|
||||
case DatePreset.Never:
|
||||
return null;
|
||||
case DatePreset.Custom:
|
||||
if (!this.formGroup.controls.defaultExpirationDateTime.value) {
|
||||
return null;
|
||||
}
|
||||
return this.formGroup.controls.defaultExpirationDateTime.value;
|
||||
default: {
|
||||
const now = new Date();
|
||||
const milliseconds = now.setTime(
|
||||
now.getTime() +
|
||||
(this.formGroup.controls.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000
|
||||
);
|
||||
return new Date(milliseconds).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get formattedDeletionDate(): string {
|
||||
switch (this.formGroup.controls.selectedDeletionDatePreset.value as DatePreset) {
|
||||
case DatePreset.Never:
|
||||
this.formGroup.controls.selectedDeletionDatePreset.patchValue(DatePreset.SevenDays);
|
||||
return this.formattedDeletionDate;
|
||||
case DatePreset.Custom:
|
||||
return this.formGroup.controls.defaultDeletionDateTime.value;
|
||||
default: {
|
||||
const now = new Date();
|
||||
const milliseconds = now.setTime(
|
||||
now.getTime() +
|
||||
(this.formGroup.controls.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000
|
||||
);
|
||||
return new Date(milliseconds).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,356 +0,0 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { UntypedFormControl, UntypedFormGroup } from "@angular/forms";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
// Different BrowserPath = different controls.
|
||||
enum BrowserPath {
|
||||
// Native datetime-locale.
|
||||
// We are happy.
|
||||
Default = "default",
|
||||
|
||||
// Native date and time inputs, but no datetime-locale.
|
||||
// We use individual date and time inputs and create a datetime programatically on submit.
|
||||
Firefox = "firefox",
|
||||
|
||||
// No native date, time, or datetime-locale inputs.
|
||||
// We use a polyfill for dates and a dropdown for times.
|
||||
Safari = "safari",
|
||||
}
|
||||
|
||||
enum DateField {
|
||||
DeletionDate = "deletion",
|
||||
ExpirationDate = "expiration",
|
||||
}
|
||||
|
||||
// Value = hours
|
||||
enum DatePreset {
|
||||
OneHour = 1,
|
||||
OneDay = 24,
|
||||
TwoDays = 48,
|
||||
ThreeDays = 72,
|
||||
SevenDays = 168,
|
||||
ThirtyDays = 720,
|
||||
Custom = 0,
|
||||
Never = null,
|
||||
}
|
||||
|
||||
// TimeOption is used for the dropdown implementation of custom times
|
||||
// twelveHour = displayed time; twentyFourHour = time used in logic
|
||||
interface TimeOption {
|
||||
twelveHour: string;
|
||||
twentyFourHour: string;
|
||||
}
|
||||
|
||||
@Directive()
|
||||
export class EffluxDatesComponent implements OnInit {
|
||||
@Input() readonly initialDeletionDate: Date;
|
||||
@Input() readonly initialExpirationDate: Date;
|
||||
@Input() readonly editMode: boolean;
|
||||
@Input() readonly disabled: boolean;
|
||||
|
||||
@Output() datesChanged = new EventEmitter<{ deletionDate: string; expirationDate: string }>();
|
||||
|
||||
get browserPath(): BrowserPath {
|
||||
if (this.platformUtilsService.isFirefox()) {
|
||||
return BrowserPath.Firefox;
|
||||
} else if (this.platformUtilsService.isSafari()) {
|
||||
return BrowserPath.Safari;
|
||||
}
|
||||
return BrowserPath.Default;
|
||||
}
|
||||
|
||||
datesForm = new UntypedFormGroup({
|
||||
selectedDeletionDatePreset: new UntypedFormControl(),
|
||||
selectedExpirationDatePreset: new UntypedFormControl(),
|
||||
defaultDeletionDateTime: new UntypedFormControl(),
|
||||
defaultExpirationDateTime: new UntypedFormControl(),
|
||||
fallbackDeletionDate: new UntypedFormControl(),
|
||||
fallbackDeletionTime: new UntypedFormControl(),
|
||||
fallbackExpirationDate: new UntypedFormControl(),
|
||||
fallbackExpirationTime: new UntypedFormControl(),
|
||||
});
|
||||
|
||||
deletionDatePresets: any[] = [
|
||||
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
|
||||
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
|
||||
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
|
||||
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
|
||||
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
|
||||
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
|
||||
{ name: this.i18nService.t("custom"), value: DatePreset.Custom },
|
||||
];
|
||||
|
||||
expirationDatePresets: any[] = [
|
||||
{ name: this.i18nService.t("never"), value: DatePreset.Never },
|
||||
].concat([...this.deletionDatePresets]);
|
||||
|
||||
get selectedDeletionDatePreset(): UntypedFormControl {
|
||||
return this.datesForm.get("selectedDeletionDatePreset") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get selectedExpirationDatePreset(): UntypedFormControl {
|
||||
return this.datesForm.get("selectedExpirationDatePreset") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get defaultDeletionDateTime(): UntypedFormControl {
|
||||
return this.datesForm.get("defaultDeletionDateTime") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get defaultExpirationDateTime(): UntypedFormControl {
|
||||
return this.datesForm.get("defaultExpirationDateTime") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get fallbackDeletionDate(): UntypedFormControl {
|
||||
return this.datesForm.get("fallbackDeletionDate") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get fallbackDeletionTime(): UntypedFormControl {
|
||||
return this.datesForm.get("fallbackDeletionTime") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get fallbackExpirationDate(): UntypedFormControl {
|
||||
return this.datesForm.get("fallbackExpirationDate") as UntypedFormControl;
|
||||
}
|
||||
|
||||
get fallbackExpirationTime(): UntypedFormControl {
|
||||
return this.datesForm.get("fallbackExpirationTime") as UntypedFormControl;
|
||||
}
|
||||
|
||||
// Should be able to call these at any time and compute a submitable value
|
||||
get formattedDeletionDate(): string {
|
||||
switch (this.selectedDeletionDatePreset.value as DatePreset) {
|
||||
case DatePreset.Never:
|
||||
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
|
||||
return this.formattedDeletionDate;
|
||||
case DatePreset.Custom:
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
case BrowserPath.Firefox:
|
||||
return this.fallbackDeletionDate.value + "T" + this.fallbackDeletionTime.value;
|
||||
default:
|
||||
return this.defaultDeletionDateTime.value;
|
||||
}
|
||||
default: {
|
||||
const now = new Date();
|
||||
const milliseconds = now.setTime(
|
||||
now.getTime() + (this.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000
|
||||
);
|
||||
return new Date(milliseconds).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get formattedExpirationDate(): string {
|
||||
switch (this.selectedExpirationDatePreset.value as DatePreset) {
|
||||
case DatePreset.Never:
|
||||
return null;
|
||||
case DatePreset.Custom:
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
case BrowserPath.Firefox:
|
||||
if (
|
||||
(!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) &&
|
||||
this.editMode
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return this.fallbackExpirationDate.value + "T" + this.fallbackExpirationTime.value;
|
||||
default:
|
||||
if (!this.defaultExpirationDateTime.value) {
|
||||
return null;
|
||||
}
|
||||
return this.defaultExpirationDateTime.value;
|
||||
}
|
||||
default: {
|
||||
const now = new Date();
|
||||
const milliseconds = now.setTime(
|
||||
now.getTime() + (this.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000
|
||||
);
|
||||
return new Date(milliseconds).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
get safariDeletionTimePresetOptions() {
|
||||
return this.safariTimePresetOptions(DateField.DeletionDate);
|
||||
}
|
||||
|
||||
get safariExpirationTimePresetOptions() {
|
||||
return this.safariTimePresetOptions(DateField.ExpirationDate);
|
||||
}
|
||||
|
||||
private get nextWeek(): Date {
|
||||
const nextWeek = new Date();
|
||||
nextWeek.setDate(nextWeek.getDate() + 7);
|
||||
return nextWeek;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected datePipe: DatePipe
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setInitialFormValues();
|
||||
this.emitDates();
|
||||
this.datesForm.valueChanges.subscribe(() => {
|
||||
this.emitDates();
|
||||
});
|
||||
}
|
||||
|
||||
onDeletionDatePresetSelect(value: DatePreset) {
|
||||
this.selectedDeletionDatePreset.setValue(value);
|
||||
}
|
||||
|
||||
clearExpiration() {
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
case BrowserPath.Firefox:
|
||||
this.fallbackExpirationDate.setValue(null);
|
||||
this.fallbackExpirationTime.setValue(null);
|
||||
break;
|
||||
case BrowserPath.Default:
|
||||
this.defaultExpirationDateTime.setValue(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected emitDates() {
|
||||
this.datesChanged.emit({
|
||||
deletionDate: this.formattedDeletionDate,
|
||||
expirationDate: this.formattedExpirationDate,
|
||||
});
|
||||
}
|
||||
|
||||
protected setInitialFormValues() {
|
||||
if (this.editMode) {
|
||||
this.selectedDeletionDatePreset.setValue(DatePreset.Custom);
|
||||
this.selectedExpirationDatePreset.setValue(DatePreset.Custom);
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
case BrowserPath.Firefox:
|
||||
this.fallbackDeletionDate.setValue(this.initialDeletionDate.toISOString().slice(0, 10));
|
||||
this.fallbackDeletionTime.setValue(this.initialDeletionDate.toTimeString().slice(0, 5));
|
||||
if (this.initialExpirationDate != null) {
|
||||
this.fallbackExpirationDate.setValue(
|
||||
this.initialExpirationDate.toISOString().slice(0, 10)
|
||||
);
|
||||
this.fallbackExpirationTime.setValue(
|
||||
this.initialExpirationDate.toTimeString().slice(0, 5)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case BrowserPath.Default:
|
||||
if (this.initialExpirationDate) {
|
||||
this.defaultExpirationDateTime.setValue(
|
||||
this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm")
|
||||
);
|
||||
}
|
||||
this.defaultDeletionDateTime.setValue(
|
||||
this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays);
|
||||
this.selectedExpirationDatePreset.setValue(DatePreset.Never);
|
||||
|
||||
switch (this.browserPath) {
|
||||
case BrowserPath.Safari:
|
||||
this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10));
|
||||
this.fallbackDeletionTime.setValue(
|
||||
this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected safariTimePresetOptions(field: DateField): TimeOption[] {
|
||||
// init individual arrays for major sort groups
|
||||
const noon: TimeOption[] = [];
|
||||
const midnight: TimeOption[] = [];
|
||||
const ams: TimeOption[] = [];
|
||||
const pms: TimeOption[] = [];
|
||||
|
||||
// determine minute skip (5 min, 10 min, 15 min, etc.)
|
||||
const minuteIncrementer = 15;
|
||||
|
||||
// loop through each hour on a 12 hour system
|
||||
for (let h = 1; h <= 12; h++) {
|
||||
// loop through each minute in the hour using the skip to increment
|
||||
for (let m = 0; m < 60; m += minuteIncrementer) {
|
||||
// init the final strings that will be added to the lists
|
||||
let hour = h.toString();
|
||||
let minutes = m.toString();
|
||||
|
||||
// add prepending 0s to single digit hours/minutes
|
||||
if (h < 10) {
|
||||
hour = "0" + hour;
|
||||
}
|
||||
if (m < 10) {
|
||||
minutes = "0" + minutes;
|
||||
}
|
||||
|
||||
// build time strings and push to relevant sort groups
|
||||
if (h === 12) {
|
||||
const midnightOption: TimeOption = {
|
||||
twelveHour: `${hour}:${minutes} AM`,
|
||||
twentyFourHour: `00:${minutes}`,
|
||||
};
|
||||
midnight.push(midnightOption);
|
||||
|
||||
const noonOption: TimeOption = {
|
||||
twelveHour: `${hour}:${minutes} PM`,
|
||||
twentyFourHour: `${hour}:${minutes}`,
|
||||
};
|
||||
noon.push(noonOption);
|
||||
} else {
|
||||
const amOption: TimeOption = {
|
||||
twelveHour: `${hour}:${minutes} AM`,
|
||||
twentyFourHour: `${hour}:${minutes}`,
|
||||
};
|
||||
ams.push(amOption);
|
||||
|
||||
const pmOption: TimeOption = {
|
||||
twelveHour: `${hour}:${minutes} PM`,
|
||||
twentyFourHour: `${h + 12}:${minutes}`,
|
||||
};
|
||||
pms.push(pmOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bring all the arrays together in the right order
|
||||
const validTimes = [...midnight, ...ams, ...noon, ...pms];
|
||||
|
||||
// determine if an unsupported value already exists on the send & add that to the top of the option list
|
||||
// example: if the Send was created with a different client
|
||||
if (field === DateField.ExpirationDate && this.initialExpirationDate != null && this.editMode) {
|
||||
const previousValue: TimeOption = {
|
||||
twelveHour: this.datePipe.transform(this.initialExpirationDate, "hh:mm a"),
|
||||
twentyFourHour: this.datePipe.transform(this.initialExpirationDate, "HH:mm"),
|
||||
};
|
||||
return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes];
|
||||
} else if (
|
||||
field === DateField.DeletionDate &&
|
||||
this.initialDeletionDate != null &&
|
||||
this.editMode
|
||||
) {
|
||||
const previousValue: TimeOption = {
|
||||
twelveHour: this.datePipe.transform(this.initialDeletionDate, "hh:mm a"),
|
||||
twentyFourHour: this.datePipe.transform(this.initialDeletionDate, "HH:mm"),
|
||||
};
|
||||
return [previousValue, ...validTimes];
|
||||
} else {
|
||||
return [{ twelveHour: null, twentyFourHour: null }, ...validTimes];
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,9 @@ export type InputTypes =
|
||||
| "email"
|
||||
| "checkbox"
|
||||
| "search"
|
||||
| "file";
|
||||
| "file"
|
||||
| "date"
|
||||
| "time";
|
||||
|
||||
export abstract class BitFormFieldControl {
|
||||
ariaDescribedBy: string;
|
||||
|
Loading…
Reference in New Issue
Block a user