Merge pull request #724 from bitwarden/Send
This commit is contained in:
commit
bddbe186fc
2
jslib
2
jslib
|
@ -1 +1 @@
|
||||||
Subproject commit ee164bebc65aa56e41a122eb4ece8971eb23119b
|
Subproject commit 7941664a59f90a1b510b13d37062b90210da3b3c
|
|
@ -2,11 +2,9 @@ import {
|
||||||
BodyOutputType,
|
BodyOutputType,
|
||||||
Toast,
|
Toast,
|
||||||
ToasterConfig,
|
ToasterConfig,
|
||||||
ToasterContainerComponent,
|
|
||||||
ToasterService,
|
ToasterService,
|
||||||
} from 'angular2-toaster';
|
} from 'angular2-toaster';
|
||||||
import { Angulartics2 } from 'angulartics2';
|
import { Angulartics2 } from 'angulartics2';
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
|
@ -52,10 +50,16 @@ import { UserService } from 'jslib/abstractions/user.service';
|
||||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||||
|
|
||||||
import { ConstantsService } from 'jslib/services/constants.service';
|
import { ConstantsService } from 'jslib/services/constants.service';
|
||||||
import { NativeMessagingService } from '../services/nativeMessaging.service';
|
|
||||||
|
import { CipherType } from 'jslib/enums/cipherType';
|
||||||
|
|
||||||
|
import { ExportComponent } from './vault/export.component';
|
||||||
|
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
|
||||||
|
import { PasswordGeneratorComponent } from './vault/password-generator.component';
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'AppComponent';
|
const BroadcasterSubscriptionId = 'AppComponent';
|
||||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||||
|
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
@ -65,12 +69,20 @@ const IdleTimeout = 60000 * 10; // 10 minutes
|
||||||
<ng-template #settings></ng-template>
|
<ng-template #settings></ng-template>
|
||||||
<ng-template #premium></ng-template>
|
<ng-template #premium></ng-template>
|
||||||
<ng-template #passwordHistory></ng-template>
|
<ng-template #passwordHistory></ng-template>
|
||||||
|
<ng-template #appFolderAddEdit></ng-template>
|
||||||
|
<ng-template #exportVault></ng-template>
|
||||||
|
<ng-template #appPasswordGenerator></ng-template>
|
||||||
<router-outlet></router-outlet>`,
|
<router-outlet></router-outlet>`,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
||||||
@ViewChild('premium', { read: ViewContainerRef, static: true }) premiumRef: ViewContainerRef;
|
@ViewChild('premium', { read: ViewContainerRef, static: true }) premiumRef: ViewContainerRef;
|
||||||
@ViewChild('passwordHistory', { read: ViewContainerRef, static: true }) passwordHistoryRef: ViewContainerRef;
|
@ViewChild('passwordHistory', { read: ViewContainerRef, static: true }) passwordHistoryRef: ViewContainerRef;
|
||||||
|
@ViewChild('exportVault', { read: ViewContainerRef, static: true }) exportVaultModalRef: ViewContainerRef;
|
||||||
|
@ViewChild('appFolderAddEdit', { read: ViewContainerRef, static: true })
|
||||||
|
folderAddEditModalRef: ViewContainerRef;
|
||||||
|
@ViewChild('appPasswordGenerator', { read: ViewContainerRef, static: true })
|
||||||
|
passwordGeneratorModalRef: ViewContainerRef;
|
||||||
|
|
||||||
toasterConfig: ToasterConfig = new ToasterConfig({
|
toasterConfig: ToasterConfig = new ToasterConfig({
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
|
@ -84,8 +96,7 @@ export class AppComponent implements OnInit {
|
||||||
private idleTimer: number = null;
|
private idleTimer: number = null;
|
||||||
private isIdle = false;
|
private isIdle = false;
|
||||||
|
|
||||||
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
constructor(private broadcasterService: BroadcasterService, private userService: UserService,
|
||||||
private broadcasterService: BroadcasterService, private userService: UserService,
|
|
||||||
private tokenService: TokenService, private folderService: FolderService,
|
private tokenService: TokenService, private folderService: FolderService,
|
||||||
private settingsService: SettingsService, private syncService: SyncService,
|
private settingsService: SettingsService, private syncService: SyncService,
|
||||||
private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService,
|
private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService,
|
||||||
|
@ -98,7 +109,7 @@ export class AppComponent implements OnInit {
|
||||||
private searchService: SearchService, private notificationsService: NotificationsService,
|
private searchService: SearchService, private notificationsService: NotificationsService,
|
||||||
private platformUtilsService: PlatformUtilsService, private systemService: SystemService,
|
private platformUtilsService: PlatformUtilsService, private systemService: SystemService,
|
||||||
private stateService: StateService, private eventService: EventService,
|
private stateService: StateService, private eventService: EventService,
|
||||||
private policyService: PolicyService, private nativeMessagingService: NativeMessagingService) { }
|
private policyService: PolicyService) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.ngZone.runOutsideAngular(() => {
|
this.ngZone.runOutsideAngular(() => {
|
||||||
|
@ -197,7 +208,63 @@ export class AppComponent implements OnInit {
|
||||||
break;
|
break;
|
||||||
case 'ssoCallback':
|
case 'ssoCallback':
|
||||||
this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } });
|
this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } });
|
||||||
|
case 'premiumRequired':
|
||||||
|
const premiumConfirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'),
|
||||||
|
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
|
||||||
|
if (premiumConfirmed) {
|
||||||
|
this.openModal<PremiumComponent>(PremiumComponent, this.premiumRef);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'syncVault':
|
||||||
|
try {
|
||||||
|
await this.syncService.fullSync(true, true);
|
||||||
|
this.toasterService.popAsync('success', null, this.i18nService.t('syncingComplete'));
|
||||||
|
this.analytics.eventTrack.next({ action: 'Synced Full' });
|
||||||
|
} catch {
|
||||||
|
this.toasterService.popAsync('error', null, this.i18nService.t('syncingFailed'));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'checkSyncVault':
|
||||||
|
try {
|
||||||
|
const lastSync = await this.syncService.getLastSync();
|
||||||
|
let lastSyncAgo = SyncInterval + 1;
|
||||||
|
if (lastSync != null) {
|
||||||
|
lastSyncAgo = new Date().getTime() - lastSync.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSyncAgo >= SyncInterval) {
|
||||||
|
await this.syncService.fullSync(false);
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
this.messagingService.send('scheduleNextSync');
|
||||||
|
break;
|
||||||
|
case 'exportVault':
|
||||||
|
await this.openExportVault();
|
||||||
|
break;
|
||||||
|
case 'newLogin':
|
||||||
|
this.routeToVault('add', CipherType.Login);
|
||||||
|
break;
|
||||||
|
case 'newCard':
|
||||||
|
this.routeToVault('add', CipherType.Card);
|
||||||
|
break;
|
||||||
|
case 'newIdentity':
|
||||||
|
this.routeToVault('add', CipherType.Identity);
|
||||||
|
break;
|
||||||
|
case 'newSecureNote':
|
||||||
|
this.routeToVault('add', CipherType.SecureNote);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
break;
|
||||||
|
case 'newFolder':
|
||||||
|
await this.addFolder();
|
||||||
|
break;
|
||||||
|
case 'openPasswordGenerator':
|
||||||
|
// openPasswordGenerator has extended functionality if called in the vault
|
||||||
|
if (!this.router.url.includes('vault')) {
|
||||||
|
await this.openPasswordGenerator();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -207,6 +274,59 @@ export class AppComponent implements OnInit {
|
||||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async openExportVault() {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = this.exportVaultModalRef.createComponent(factory).instance;
|
||||||
|
const childComponent = this.modal.show<ExportComponent>(ExportComponent, this.exportVaultModalRef);
|
||||||
|
|
||||||
|
childComponent.onSaved.subscribe(() => {
|
||||||
|
this.modal.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(() => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async addFolder() {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = this.folderAddEditModalRef.createComponent(factory).instance;
|
||||||
|
const childComponent = this.modal.show<FolderAddEditComponent>(
|
||||||
|
FolderAddEditComponent, this.folderAddEditModalRef, true, comp => comp.folderId = null);
|
||||||
|
|
||||||
|
childComponent.onSavedFolder.subscribe(async () => {
|
||||||
|
this.modal.close();
|
||||||
|
this.syncService.fullSync(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(() => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async openPasswordGenerator() {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = this.passwordGeneratorModalRef.createComponent(factory).instance;
|
||||||
|
this.modal.show<PasswordGeneratorComponent>(PasswordGeneratorComponent,
|
||||||
|
this.passwordGeneratorModalRef, true, comp => comp.showSelect = false);
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(() => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async updateAppMenu() {
|
private async updateAppMenu() {
|
||||||
this.messagingService.send('updateAppMenu', {
|
this.messagingService.send('updateAppMenu', {
|
||||||
isAuthenticated: await this.userService.isAuthenticated(),
|
isAuthenticated: await this.userService.isAuthenticated(),
|
||||||
|
@ -320,4 +440,16 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
this.toasterService.popAsync(toast);
|
this.toasterService.popAsync(toast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private routeToVault(action: string, cipherType: CipherType) {
|
||||||
|
if (!this.router.url.includes('vault')) {
|
||||||
|
this.router.navigate(['/vault'], {
|
||||||
|
queryParams: {
|
||||||
|
action: action,
|
||||||
|
addType: cipherType,
|
||||||
|
},
|
||||||
|
replaceUrl: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +1,110 @@
|
||||||
<ng-container *ngIf="send">
|
<form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<div class="content">
|
||||||
<div class="content" *ngIf="send">
|
<div class="inner-content" *ngIf="send">
|
||||||
<div class="inner-content">
|
<div class="box">
|
||||||
|
<app-callout *ngIf="disableSend">
|
||||||
|
<span>{{'sendDisabledWarning' | i18n}}</span>
|
||||||
|
</app-callout>
|
||||||
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="box-header">
|
<div class="box-header">
|
||||||
{{'editSend' | i18n}}
|
{{title}}
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<label for="name">{{'name' | i18n}}</label>
|
<label for="name">{{'name' | i18n}}</label>
|
||||||
<input id="name" type="text" name="Name" [(ngModel)]="send.name" appAutofocus>
|
<input id="name" type="text" name="Name" [(ngModel)]="send.name" appAutofocus [readOnly]="disableSend">
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.File">
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-content">
|
||||||
|
<ng-container *ngIf="!editMode">
|
||||||
|
<div class="box-content-row box-content-row-radio">
|
||||||
|
<label class="radio-header">{{'whatTypeOfSend' | i18n}}</label>
|
||||||
|
<div class="item" *ngFor="let o of typeOptions">
|
||||||
|
<input type="radio" class="radio" [(ngModel)]="send.type" name="Type_{{o.value}}"
|
||||||
|
id="type_{{o.value}}" [value]="o.value" (change)="typeChanged(o)"
|
||||||
|
[checked]="send.type === o.value">
|
||||||
|
<label class="unstyled" for="type_{{o.value}}">
|
||||||
|
{{o.name}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="send.type === sendType.File" class="box-content-row" appBowRow>
|
||||||
<label for="file">{{'file' | i18n}}</label>
|
<label for="file">{{'file' | i18n}}</label>
|
||||||
<input id="file" type="text" name="file" [(ngModel)]="send.file.fileName" readonly>
|
<input type="file" id="file" class="form-control-file" name="file" required>
|
||||||
|
<div class="subtext">{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="editMode && send.type === sendType.File">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="file">{{'file' | i18n}}</label>
|
||||||
|
<input id="file" type="text" name="file" [(ngModel)]="send.file.fileName" readonly [disabled]="disableSend">
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="send.type === sendType.Text">
|
||||||
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
|
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
|
||||||
<label for="text">{{'text' | i18n}}</label>
|
<label for="text">{{'text' | i18n}}</label>
|
||||||
<input id="text" type="text" name="text" [(ngModel)]="send.text.text">
|
<textarea id="text" name="text" [(ngModel)]="send.text.text" rows="6" [readOnly]="disableSend"></textarea>
|
||||||
|
<div class="subtext">{{'sendTextDesc' | i18n}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="send.type === sendType.Text">
|
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="send.type === sendType.Text">
|
||||||
<label for="hideText">{{'textHiddenByDefault' | i18n}}</label>
|
<label for="hideText">{{'textHiddenByDefault' | i18n}}</label>
|
||||||
<input id="hideText" name="hideText" type="checkbox" [(ngModel)]="send.text.hidden">
|
<input id="hideText" name="hideText" type="checkbox" [(ngModel)]="send.text.hidden" [disabled]="disableSend">
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="box-header">
|
<div class="box-header">
|
||||||
{{'options' | i18n}}
|
{{'options' | i18n}}
|
||||||
|
<a class="toggle" href="#" appStopClick appBlurClick role="button" (click)="toggleOptions()">
|
||||||
|
<i class="fa fa-lg" aria-hidden="true" [ngClass]="{'fa-chevron-down': !showOptions, 'fa-chevron-up': showOptions}"></i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div [hidden]="!showOptions">
|
||||||
|
<div class="box">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row" appBoxRow *ngIf="editMode">
|
<div class="box-content-row" appBoxRow *ngIf="!editMode">
|
||||||
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
|
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
|
||||||
<input id="deletionDate" type="datetime-local" name="deletionDate"
|
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect" required [readOnly]="disableSend">
|
||||||
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
|
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
<div class="subtext">{{'deletionDateDesc' | i18n}}</div>
|
<div class="subtext">{{'deletionDateDesc' | i18n}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" *ngIf="deletionDateSelect === 0 || editMode">
|
||||||
|
<label *ngIf="editMode" for="deletionDateCustom">{{'deletionDate' | i18n}}</label>
|
||||||
|
<input id="deletionDateCustom" type="datetime-local" name="deletionDate"
|
||||||
|
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
|
||||||
|
<div class="subtext">{{'deletionDateDesc' | i18n}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow *ngIf="!editMode">
|
||||||
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
|
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
|
||||||
<input id="expirationDate" type="datetime-local" name="expirationDate" [(ngModel)]="expirationDate">
|
<select id="expirationDate" name="expirationDateSelect" [(ngModel)]="expirationDateSelect" required>
|
||||||
|
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
<div class="subtext">{{'expirationDateDesc' | i18n}}</div>
|
<div class="subtext">{{'expirationDateDesc' | i18n}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="box-content-row" *ngIf="expirationDateSelect === 0 || editMode">
|
||||||
|
<label *ngIf="editMode" for="expirationDateCustom">{{'expirationDate' | i18n}}</label>
|
||||||
|
<input id="expirationDateCustom" type="datetime-local" name="expirationDate"
|
||||||
|
[(ngModel)]="expirationDate" required placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
|
||||||
|
<div *ngIf="editMode" class="subtext">{{'expirationDateDesc' | i18n}}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
|
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
|
||||||
<input id="maxAccessCount" type="number" name="maxAccessCount" [(ngModel)]="send.maxAccessCount">
|
<input id="maxAccessCount" type="number" name="maxAccessCount" [(ngModel)]="send.maxAccessCount" [readOnly]="disableSend">
|
||||||
<div class="subtext">{{'maxAccessCountDesc' | i18n}}</div>
|
<div class="subtext">{{'maxAccessCountDesc' | i18n}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content-row" appBoxRow>
|
<div *ngIf="editMode" class="box-content-row" appBoxRow>
|
||||||
<label for="accessCount">{{'currentAccessCount' | i18n}}</label>
|
<label for="accessCount">{{'currentAccessCount' | i18n}}</label>
|
||||||
<input id="accessCount" type="text" name="accessCount" [(ngModel)]="send.accessCount" readonly>
|
<input id="accessCount" type="text" name="accessCount" [(ngModel)]="send.accessCount" readonly>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,11 +112,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||||
|
<div class="row-main">
|
||||||
<label for="password">{{(hasPassword ? 'newPassword' : 'password') | i18n}}</label>
|
<label for="password">{{(hasPassword ? 'newPassword' : 'password') | i18n}}</label>
|
||||||
<input id="password" type="password" name="password" [(ngModel)]="password">
|
<input id="password" name="password" type="{{showPassword ? 'text' : 'password'}}" [(ngModel)]="password" [readOnly]="disableSend">
|
||||||
<div class="subtext">{{'sendPasswordDesc' | i18n}}</div>
|
<div class="subtext">{{'sendPasswordDesc' | i18n}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
|
||||||
|
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()" [disabled]="disableSend">
|
||||||
|
<i class="fa fa-lg" aria-hidden="true"
|
||||||
|
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
|
@ -71,7 +134,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<textarea id="notes" name="notes" [(ngModel)]="send.notes" rows="6"></textarea>
|
<textarea id="notes" name="notes" [(ngModel)]="send.notes" rows="6" [readOnly]="disableSend"></textarea>
|
||||||
<small class="subtext">{{'sendNotesDesc' | i18n}}</small>
|
<small class="subtext">{{'sendNotesDesc' | i18n}}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,7 +143,8 @@
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
<label for="disabled">{{'disableSend' | i18n}}</label>
|
<label for="disabled">{{'disableSend' | i18n}}</label>
|
||||||
<input id="disabled" type="checkbox" name="disabled" [(ngModel)]="send.disabled">
|
<input id="disabled" type="checkbox" name="disabled" [(ngModel)]="send.disabled" [disabled]="disableSend">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,18 +153,35 @@
|
||||||
{{'share' | i18n}}
|
{{'share' | i18n}}
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow *ngIf="editMode">
|
||||||
<label for="url">{{'sendLink' | i18n}}</label>
|
<label for="link">{{'sendLinkLabel' | i18n}}</label>
|
||||||
<input id="url" name="url" [ngModel]="link" readonly>
|
<input id="link" name="link" [ngModel]="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">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}">
|
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" *ngIf="!disableSend">
|
||||||
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
|
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button appBlurClick type="button" (click)="cancel()">
|
||||||
|
{{'cancel' | i18n}}
|
||||||
|
</button>
|
||||||
|
<div class="right">
|
||||||
|
<button appBlurClick type="button" (click)="copyLinkToClipboard(link)" appA11yTitle="{{'copySendLinkToClipboard' | i18n}}" *ngIf="editMode">
|
||||||
|
<i class="fa fa-copy fa-lg fa-fw" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
|
||||||
|
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||||
|
[appApiAction]="deletePromise">
|
||||||
|
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
|
||||||
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</ng-container>
|
</form>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DatePipe } from '@angular/common';
|
import { DatePipe } from '@angular/common';
|
||||||
|
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
@ -26,11 +26,21 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
async refresh() {
|
async refresh() {
|
||||||
|
this.password = null;
|
||||||
const send = await this.loadSend();
|
const send = await this.loadSend();
|
||||||
this.send = await send.decrypt();
|
this.send = await send.decrypt();
|
||||||
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
|
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
|
||||||
this.deletionDate = this.dateToString(this.send.deletionDate);
|
this.deletionDate = this.dateToString(this.send.deletionDate);
|
||||||
this.expirationDate = this.dateToString(this.send.expirationDate);
|
this.expirationDate = this.dateToString(this.send.expirationDate);
|
||||||
this.password = null;
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.onCancelled.emit(this.send);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyLinkToClipboard(link: string) {
|
||||||
|
super.copyLinkToClipboard(link);
|
||||||
|
this.platformUtilsService.showToast('success', null,
|
||||||
|
this.i18nService.t('valueCopied', this.i18nService.t('sendLink')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,8 @@
|
||||||
<div class="list" *ngIf="filteredSends.length" infiniteScroll [infiniteScrollDistance]="1"
|
<div class="list" *ngIf="filteredSends.length" infiniteScroll [infiniteScrollDistance]="1"
|
||||||
[infiniteScrollContainer]="'#items .content'" [fromRoot]="true" (scrolled)="loadMore()">
|
[infiniteScrollContainer]="'#items .content'" [fromRoot]="true" (scrolled)="loadMore()">
|
||||||
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)"
|
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)"
|
||||||
href="#" title="{{'viewItem' | i18n}}"
|
href="#" title="{{'viewItem' | i18n}}" (contextmenu)="viewSendMenu(s)"
|
||||||
[ngClass]="{'active': s.id === activeSendId}">
|
[ngClass]="{'active': s.id === sendId}">
|
||||||
<div class="icon" aria-hidden="true">
|
<div class="icon" aria-hidden="true">
|
||||||
<i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-o' : 'fa-file-text-o'"></i>
|
<i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-o' : 'fa-file-text-o'"></i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,7 +67,8 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-send-add-edit id="addEdit" class="details" *ngIf="action == 'add' || action == 'edit'" [sendId]="sendId" [type]="selectedSendType" (onSavedSend)="refresh()"></app-send-add-edit>
|
<app-send-add-edit id="addEdit" class="details" *ngIf="action == 'add' || action == 'edit'" [sendId]="sendId" [type]="selectedSendType"
|
||||||
|
(onSavedSend)="savedSend($event)" (onCancelled)="cancel($event)" (onDeletedSend)="deletedSend($event)"></app-send-add-edit>
|
||||||
<div class="logo" *ngIf="!action">
|
<div class="logo" *ngIf="!action">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="inner-content">
|
<div class="inner-content">
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import { remote } from 'electron';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
NgZone,
|
NgZone,
|
||||||
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
@ -27,11 +30,13 @@ enum Action {
|
||||||
Edit = 'edit',
|
Edit = 'edit',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BroadcasterSubscriptionId = 'SendComponent';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-send',
|
selector: 'app-send',
|
||||||
templateUrl: 'send.component.html',
|
templateUrl: 'send.component.html',
|
||||||
})
|
})
|
||||||
export class SendComponent extends BaseSendComponent implements OnInit {
|
export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
|
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
|
||||||
|
|
||||||
sendId: string;
|
sendId: string;
|
||||||
|
@ -39,34 +44,65 @@ export class SendComponent extends BaseSendComponent implements OnInit {
|
||||||
|
|
||||||
constructor(sendService: SendService, i18nService: I18nService,
|
constructor(sendService: SendService, i18nService: I18nService,
|
||||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||||
broadcasterService: BroadcasterService, ngZone: NgZone,
|
private broadcasterService: BroadcasterService, ngZone: NgZone,
|
||||||
searchService: SearchService, policyService: PolicyService,
|
searchService: SearchService, policyService: PolicyService,
|
||||||
userService: UserService) {
|
userService: UserService) {
|
||||||
super(sendService, i18nService, platformUtilsService,
|
super(sendService, i18nService, platformUtilsService,
|
||||||
environmentService, broadcasterService, ngZone, searchService,
|
environmentService, ngZone, searchService,
|
||||||
policyService, userService);
|
policyService, userService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
switch (message.command) {
|
||||||
|
case 'syncCompleted':
|
||||||
|
await this.load();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
await this.load();
|
await this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
addSend() {
|
ngOnDestroy() {
|
||||||
this.sendId = null;
|
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||||
this.action = Action.Add;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
editSend(send: SendView) {
|
addSend() {
|
||||||
return;
|
this.action = Action.Add;
|
||||||
|
if (this.addEditComponent != null) {
|
||||||
|
this.addEditComponent.sendId = null;
|
||||||
|
this.addEditComponent.send = null;
|
||||||
|
this.addEditComponent.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(s: SendView) {
|
||||||
|
this.action = Action.None;
|
||||||
|
this.sendId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletedSend(s: SendView) {
|
||||||
|
await this.refresh();
|
||||||
|
this.action = Action.None;
|
||||||
|
this.sendId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async savedSend(s: SendView) {
|
||||||
|
await this.refresh();
|
||||||
|
this.selectSend(s.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectSend(sendId: string) {
|
async selectSend(sendId: string) {
|
||||||
this.sendId = sendId;
|
if (sendId === this.sendId && this.action === Action.Edit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.action = Action.Edit;
|
this.action = Action.Edit;
|
||||||
|
this.sendId = sendId;
|
||||||
if (this.addEditComponent != null) {
|
if (this.addEditComponent != null) {
|
||||||
this.addEditComponent.sendId = this.sendId;
|
this.addEditComponent.sendId = sendId;
|
||||||
await this.addEditComponent.refresh();
|
await this.addEditComponent.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,4 +110,20 @@ export class SendComponent extends BaseSendComponent implements OnInit {
|
||||||
get selectedSendType() {
|
get selectedSendType() {
|
||||||
return this.sends.find(s => s.id === this.sendId)?.type;
|
return this.sends.find(s => s.id === this.sendId)?.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewSendMenu(send: SendView) {
|
||||||
|
const menu = new remote.Menu();
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('copyLink'),
|
||||||
|
click: () => this.copy(send),
|
||||||
|
}));
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('delete'),
|
||||||
|
click: async () => {
|
||||||
|
await this.delete(send);
|
||||||
|
await this.deletedSend(send);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
menu.popup({ window: remote.getCurrentWindow() });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,4 +38,3 @@
|
||||||
<ng-template #share></ng-template>
|
<ng-template #share></ng-template>
|
||||||
<ng-template #folderAddEdit></ng-template>
|
<ng-template #folderAddEdit></ng-template>
|
||||||
<ng-template #passwordHistory></ng-template>
|
<ng-template #passwordHistory></ng-template>
|
||||||
<ng-template #exportVault></ng-template>
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ import { SyncService } from 'jslib/abstractions/sync.service';
|
||||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
|
||||||
const BroadcasterSubscriptionId = 'VaultComponent';
|
const BroadcasterSubscriptionId = 'VaultComponent';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -63,11 +62,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
|
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
|
||||||
@ViewChild('passwordGenerator', { read: ViewContainerRef, static: true }) passwordGeneratorModalRef: ViewContainerRef;
|
@ViewChild('passwordGenerator', { read: ViewContainerRef, static: true }) passwordGeneratorModalRef: ViewContainerRef;
|
||||||
@ViewChild('attachments', { read: ViewContainerRef, static: true }) attachmentsModalRef: ViewContainerRef;
|
@ViewChild('attachments', { read: ViewContainerRef, static: true }) attachmentsModalRef: ViewContainerRef;
|
||||||
@ViewChild('folderAddEdit', { read: ViewContainerRef, static: true }) folderAddEditModalRef: ViewContainerRef;
|
|
||||||
@ViewChild('passwordHistory', { read: ViewContainerRef, static: true }) passwordHistoryModalRef: ViewContainerRef;
|
@ViewChild('passwordHistory', { read: ViewContainerRef, static: true }) passwordHistoryModalRef: ViewContainerRef;
|
||||||
@ViewChild('exportVault', { read: ViewContainerRef, static: true }) exportVaultModalRef: ViewContainerRef;
|
|
||||||
@ViewChild('share', { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef;
|
@ViewChild('share', { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef;
|
||||||
@ViewChild('collections', { read: ViewContainerRef, static: true }) collectionsModalRef: ViewContainerRef;
|
@ViewChild('collections', { read: ViewContainerRef, static: true }) collectionsModalRef: ViewContainerRef;
|
||||||
|
@ViewChild('folderAddEdit', { read: ViewContainerRef, static: true }) folderAddEditModalRef: ViewContainerRef;
|
||||||
|
|
||||||
action: string;
|
action: string;
|
||||||
cipherId: string = null;
|
cipherId: string = null;
|
||||||
|
@ -111,9 +109,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
case 'newSecureNote':
|
case 'newSecureNote':
|
||||||
await this.addCipher(CipherType.SecureNote);
|
await this.addCipher(CipherType.SecureNote);
|
||||||
break;
|
break;
|
||||||
case 'newFolder':
|
|
||||||
await this.addFolder();
|
|
||||||
break;
|
|
||||||
case 'focusSearch':
|
case 'focusSearch':
|
||||||
(document.querySelector('#search') as HTMLInputElement).select();
|
(document.querySelector('#search') as HTMLInputElement).select();
|
||||||
detectChanges = false;
|
detectChanges = false;
|
||||||
|
@ -121,33 +116,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
case 'openPasswordGenerator':
|
case 'openPasswordGenerator':
|
||||||
await this.openPasswordGenerator(false);
|
await this.openPasswordGenerator(false);
|
||||||
break;
|
break;
|
||||||
case 'exportVault':
|
|
||||||
await this.openExportVault();
|
|
||||||
break;
|
|
||||||
case 'syncVault':
|
|
||||||
try {
|
|
||||||
await this.syncService.fullSync(true, true);
|
|
||||||
this.toasterService.popAsync('success', null, this.i18nService.t('syncingComplete'));
|
|
||||||
this.analytics.eventTrack.next({ action: 'Synced Full' });
|
|
||||||
} catch {
|
|
||||||
this.toasterService.popAsync('error', null, this.i18nService.t('syncingFailed'));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'checkSyncVault':
|
|
||||||
try {
|
|
||||||
const lastSync = await this.syncService.getLastSync();
|
|
||||||
let lastSyncAgo = SyncInterval + 1;
|
|
||||||
if (lastSync != null) {
|
|
||||||
lastSyncAgo = new Date().getTime() - lastSync.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastSyncAgo >= SyncInterval) {
|
|
||||||
await this.syncService.fullSync(false);
|
|
||||||
}
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
this.messagingService.send('scheduleNextSync');
|
|
||||||
break;
|
|
||||||
case 'syncCompleted':
|
case 'syncCompleted':
|
||||||
await this.load();
|
await this.load();
|
||||||
break;
|
break;
|
||||||
|
@ -230,7 +198,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
await this.viewCipher(cipherView);
|
await this.viewCipher(cipherView);
|
||||||
}
|
}
|
||||||
} else if (params.action === 'add') {
|
} else if (params.action === 'add') {
|
||||||
await this.addCipher();
|
this.addType = Number(params.addType);
|
||||||
|
this.addCipher(this.addType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.deleted) {
|
if (params.deleted) {
|
||||||
|
@ -239,7 +208,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
} else if (params.favorites) {
|
} else if (params.favorites) {
|
||||||
this.groupingsComponent.selectedFavorites = true;
|
this.groupingsComponent.selectedFavorites = true;
|
||||||
await this.filterFavorites();
|
await this.filterFavorites();
|
||||||
} else if (params.type) {
|
} else if (params.type && params.action !== 'add') {
|
||||||
const t = parseInt(params.type, null);
|
const t = parseInt(params.type, null);
|
||||||
this.groupingsComponent.selectedType = t;
|
this.groupingsComponent.selectedType = t;
|
||||||
await this.filterCipherType(t);
|
await this.filterCipherType(t);
|
||||||
|
@ -593,42 +562,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async openExportVault() {
|
|
||||||
if (this.modal != null) {
|
|
||||||
this.modal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
|
||||||
this.modal = this.exportVaultModalRef.createComponent(factory).instance;
|
|
||||||
const childComponent = this.modal.show<ExportComponent>(ExportComponent, this.exportVaultModalRef);
|
|
||||||
|
|
||||||
childComponent.onSaved.subscribe(() => {
|
|
||||||
this.modal.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.modal.onClosed.subscribe(() => {
|
|
||||||
this.modal = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async addFolder() {
|
async addFolder() {
|
||||||
if (this.modal != null) {
|
this.messagingService.send('newFolder');
|
||||||
this.modal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
|
||||||
this.modal = this.folderAddEditModalRef.createComponent(factory).instance;
|
|
||||||
const childComponent = this.modal.show<FolderAddEditComponent>(
|
|
||||||
FolderAddEditComponent, this.folderAddEditModalRef, true, comp => comp.folderId = null);
|
|
||||||
|
|
||||||
childComponent.onSavedFolder.subscribe(async (folder: FolderView) => {
|
|
||||||
this.modal.close();
|
|
||||||
await this.groupingsComponent.loadFolders();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.modal.onClosed.subscribe(() => {
|
|
||||||
this.modal = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async editFolder(folderId: string) {
|
async editFolder(folderId: string) {
|
||||||
|
|
|
@ -1562,6 +1562,10 @@
|
||||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
},
|
},
|
||||||
"sendLink": {
|
"sendLink": {
|
||||||
|
"message": "Send link",
|
||||||
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
|
},
|
||||||
|
"sendLinkLabel": {
|
||||||
"message": "Send Link",
|
"message": "Send Link",
|
||||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
},
|
},
|
||||||
|
@ -1583,5 +1587,53 @@
|
||||||
},
|
},
|
||||||
"newPassword": {
|
"newPassword": {
|
||||||
"message": "New Password"
|
"message": "New Password"
|
||||||
|
},
|
||||||
|
"whatTypeOfSend": {
|
||||||
|
"message": "What type of Send is this?",
|
||||||
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
|
},
|
||||||
|
"createSend": {
|
||||||
|
"message": "Create Send",
|
||||||
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
|
},
|
||||||
|
"sendTextDesc": {
|
||||||
|
"message": "The text you want to send."
|
||||||
|
},
|
||||||
|
"sendFileDesc": {
|
||||||
|
"message": "The file you want to send."
|
||||||
|
},
|
||||||
|
"days": {
|
||||||
|
"message": "$DAYS$ days",
|
||||||
|
"placeholders": {
|
||||||
|
"days": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oneDay": {
|
||||||
|
"message": "1 day"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"message": "Custom"
|
||||||
|
},
|
||||||
|
"deleteSendConfirmation": {
|
||||||
|
"message": "Are you sure you want to delete this Send?",
|
||||||
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
|
},
|
||||||
|
"copySendLinkToClipboard": {
|
||||||
|
"message": "Copy Send link to clipboard",
|
||||||
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
|
},
|
||||||
|
"copySendLinkOnSave": {
|
||||||
|
"message": "Copy the link to share this Send to my clipboard upon save."
|
||||||
|
},
|
||||||
|
"sendDisabled": {
|
||||||
|
"message": "Send disabled",
|
||||||
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
|
},
|
||||||
|
"sendDisabledWarning": {
|
||||||
|
"message": "Due to an enterprise policy, you are only able to delete an existing Send.",
|
||||||
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
.box-header {
|
.box-header {
|
||||||
margin: 0 10px 5px 10px;
|
margin: 0 10px 5px 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('headingColor');
|
color: themed('headingColor');
|
||||||
|
@ -21,6 +22,15 @@
|
||||||
color: themed('headingColor');
|
color: themed('headingColor');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
background-color: transparent;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
@include themify($themes) {
|
||||||
|
color: themed('headingColor');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.box-content {
|
.box-content {
|
||||||
|
@ -118,7 +128,7 @@
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-label, label {
|
.row-label, label:not(.unstyled) {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -169,7 +179,7 @@
|
||||||
&.box-content-row-multi {
|
&.box-content-row-multi {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
input:not([type="checkbox"]) {
|
input:not([type="checkbox"]):not([type="radio"]) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +260,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input:not([type="checkbox"]), textarea {
|
&.box-content-row-radio {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> .radio {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input:not([type="checkbox"]):not([type="radio"]), textarea {
|
||||||
border: none;
|
border: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
Loading…
Reference in New Issue