1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

Apply Prettier (#1202)

This commit is contained in:
Oscar Hinton
2021-12-20 15:47:17 +01:00
committed by GitHub
parent b4df834b16
commit 521feae535
141 changed files with 12454 additions and 10311 deletions

View File

@@ -1,66 +1,97 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}">
<div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{'selfHostedEnvironment' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl"
placeholder="{{'ex' | i18n}} https://bitwarden.company.com" appInputVerbatim>
</div>
</div>
<div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}}
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleCustom()" appA11yTitle="{{'toggleVisibility' | i18n}}">
<i class="fa fa-plus-square-o" [hidden]="showCustom" aria-hidden="true"></i>
<i class="fa fa-minus-square-o" [hidden]="!showCustom" aria-hidden="true"></i>
{{'customEnvironment' | i18n}}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{'notificationsUrl' | i18n}}</label>
<input id="notificationsUrl" type="text" name="NotificationsUrl"
[(ngModel)]="notificationsUrl" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl" appInputVerbatim>
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}}
</div>
</div>
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{ "selfHostedEnvironment" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
placeholder="{{ 'ex' | i18n }} https://bitwarden.company.com"
appInputVerbatim
/>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="box-footer">
{{ "selfHostedEnvironmentFooter" | i18n }}
</div>
</div>
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleCustom()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
>
<i class="fa fa-plus-square-o" [hidden]="showCustom" aria-hidden="true"></i>
<i class="fa fa-minus-square-o" [hidden]="!showCustom" aria-hidden="true"></i>
{{ "customEnvironment" | i18n }}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input
id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
appInputVerbatim
/>
</div>
</form>
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{ "apiUrl" | i18n }}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" appInputVerbatim />
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input
id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{ "notificationsUrl" | i18n }}</label>
<input
id="notificationsUrl"
type="text"
name="NotificationsUrl"
[(ngModel)]="notificationsUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{ "iconsUrl" | i18n }}</label>
<input
id="iconsUrl"
type="text"
name="IconsUrl"
[(ngModel)]="iconsUrl"
appInputVerbatim
/>
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{ "customEnvironmentFooter" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{ 'save' | i18n }}">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@@ -1,18 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib-angular/components/environment.component';
import { EnvironmentComponent as BaseEnvironmentComponent } from "jslib-angular/components/environment.component";
@Component({
selector: 'app-environment',
templateUrl: 'environment.component.html',
selector: "app-environment",
templateUrl: "environment.component.html",
})
export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
i18nService: I18nService) {
super(platformUtilsService, environmentService, i18nService);
}
constructor(
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
i18nService: I18nService
) {
super(platformUtilsService, environmentService, i18nService);
}
}

View File

@@ -1,24 +1,31 @@
<form id="hint-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'passwordHint' | i18n}}</h1>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appAutofocus
appInputVerbatim>
</div>
</div>
<div class="box-footer">
{{'enterEmailToGetHint' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
<div class="content">
<h1>{{ "passwordHint" | i18n }}</h1>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
appInputVerbatim
/>
</div>
</div>
<div class="box-footer">
{{ "enterEmailToGetHint" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
</div>
</form>

View File

@@ -1,20 +1,25 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component';
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
@Component({
selector: 'app-hint',
templateUrl: 'hint.component.html',
selector: "app-hint",
templateUrl: "hint.component.html",
})
export class HintComponent extends BaseHintComponent {
constructor(router: Router, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, apiService: ApiService, logService: LogService) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
apiService: ApiService,
logService: LogService
) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
}

View File

@@ -1,48 +1,77 @@
<form id="lock-page" (ngSubmit)="submit()">
<div class="content">
<p aria-hidden="true"><i class="fa fa-lock fa-4x text-muted"></i></p>
<p>{{'yourVaultIsLocked' | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{'pin' | i18n}}</label>
<input id="pin" type="{{showPassword ? 'text' : 'password'}}" name="PIN" class="monospaced"
[(ngModel)]="pin" required appInputVerbatim>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'loggedInAsOn' | i18n : email : webVaultHostname}}
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock">
<button type="button" class="btn block" [ngClass]="{'primary font-weight-bold': hideInput}"
appBlurClick (click)="unlockBiometric()">
{{biometricText | i18n}}
</button>
</div>
<div class="buttons-row">
<button type="submit" class="btn primary block" appBlurClick *ngIf="!hideInput">
<i class="fa fa-unlock-alt" aria-hidden="true"></i> <b>{{'unlock' | i18n}}</b>
</button>
<button type="button" class="btn block" appBlurClick (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
<div class="content">
<p aria-hidden="true"><i class="fa fa-lock fa-4x text-muted"></i></p>
<p>{{ "yourVaultIsLocked" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPassword ? 'text' : 'password' }}"
name="PIN"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
/>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{ "loggedInAsOn" | i18n: email:webVaultHostname }}
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row" *ngIf="supportsBiometric && biometricLock">
<button
type="button"
class="btn block"
[ngClass]="{ 'primary font-weight-bold': hideInput }"
appBlurClick
(click)="unlockBiometric()"
>
{{ biometricText | i18n }}
</button>
</div>
<div class="buttons-row">
<button type="submit" class="btn primary block" appBlurClick *ngIf="!hideInput">
<i class="fa fa-unlock-alt" aria-hidden="true"></i> <b>{{ "unlock" | i18n }}</b>
</button>
<button type="button" class="btn block" appBlurClick (click)="logOut()">
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</form>

View File

@@ -1,95 +1,108 @@
import {
Component,
NgZone,
OnDestroy,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ipcRenderer } from 'electron';
import { Component, NgZone, OnDestroy } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { ipcRenderer } from "electron";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
const BroadcasterSubscriptionId = 'LockComponent';
const BroadcasterSubscriptionId = "LockComponent";
@Component({
selector: 'app-lock',
templateUrl: 'lock.component.html',
selector: "app-lock",
templateUrl: "lock.component.html",
})
export class LockComponent extends BaseLockComponent implements OnDestroy {
private deferFocus: boolean = null;
private deferFocus: boolean = null;
constructor(router: Router, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
cryptoService: CryptoService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, stateService: StateService,
apiService: ApiService, private route: ActivatedRoute,
private broadcasterService: BroadcasterService, ngZone: NgZone,
logService: LogService, keyConnectorService: KeyConnectorService) {
super(router, i18nService, platformUtilsService, messagingService, cryptoService,
vaultTimeoutService, environmentService, stateService, apiService, logService,
keyConnectorService, ngZone);
}
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
messagingService: MessagingService,
cryptoService: CryptoService,
vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService,
stateService: StateService,
apiService: ApiService,
private route: ActivatedRoute,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
logService: LogService,
keyConnectorService: KeyConnectorService
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
cryptoService,
vaultTimeoutService,
environmentService,
stateService,
apiService,
logService,
keyConnectorService,
ngZone
);
}
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !await this.stateService.getNoAutoPromptBiometrics();
async ngOnInit() {
await super.ngOnInit();
const autoPromptBiometric = !(await this.stateService.getNoAutoPromptBiometrics());
this.route.queryParams.subscribe(params => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
setTimeout(async() => {
if (await ipcRenderer.invoke('windowVisible')) {
this.unlockBiometric();
}
}, 1000);
this.route.queryParams.subscribe((params) => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
setTimeout(async () => {
if (await ipcRenderer.invoke("windowVisible")) {
this.unlockBiometric();
}
}, 1000);
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
case 'windowIsFocused':
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send('getWindowIsFocused');
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
private focusInput() {
document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus();
}
private focusInput() {
document.getElementById(this.pinLock ? "pin" : "masterPassword").focus();
}
}

View File

@@ -1,58 +1,96 @@
<div class="login-header">
<a href="#" appStopClick (click)="settings()" class="environment-urls-settings-icon" attr.aria-label="{{'settings' | i18n}}">
{{'settings' | i18n}}
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</a>
<a
href="#"
appStopClick
(click)="settings()"
class="environment-urls-settings-icon"
attr.aria-label="{{ 'settings' | i18n }}"
>
{{ "settings" | i18n }}
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
</a>
</div>
<form id="login-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div id="content" class="content">
<img class="logo-image" alt="Bitwarden">
<p class="lead">{{'loginOrCreateNewAccount' | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appInputVerbatim="false">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<form
id="login-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
attr.aria-hidden="{{ showingModal }}"
>
<div id="content" class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appInputVerbatim="false"
/>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"><i class="fa fa-sign-in" aria-hidden="true"></i> {{'logIn' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{'createAccount' | i18n}}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}}
</a>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="sub-options">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading"
><i class="fa fa-sign-in" aria-hidden="true"></i> {{ "logIn" | i18n }}</b
>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</a>
</div>
<div class="buttons-row">
<a (click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</a>
</div>
</div>
<div class="sub-options">
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</div>
</div>
</form>
<ng-template #environment></ng-template>

View File

@@ -1,112 +1,129 @@
import {
Component,
NgZone,
OnDestroy,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
import { Router } from '@angular/router';
import { Router } from "@angular/router";
import { EnvironmentComponent } from './environment.component';
import { EnvironmentComponent } from "./environment.component";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component';
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
const BroadcasterSubscriptionId = 'LoginComponent';
const BroadcasterSubscriptionId = "LoginComponent";
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
selector: "app-login",
templateUrl: "login.component.html",
})
export class LoginComponent extends BaseLoginComponent implements OnDestroy {
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
@ViewChild("environment", { read: ViewContainerRef, static: true })
environmentModal: ViewContainerRef;
showingModal = false;
showingModal = false;
private deferFocus: boolean = null;
private deferFocus: boolean = null;
constructor(authService: AuthService, router: Router, i18nService: I18nService,
syncService: SyncService, private modalService: ModalService,
platformUtilsService: PlatformUtilsService, stateService: StateService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService, private broadcasterService: BroadcasterService,
ngZone: NgZone, private messagingService: MessagingService,
logService: LogService) {
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
passwordGenerationService, cryptoFunctionService, logService, ngZone);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
syncService: SyncService,
private modalService: ModalService,
platformUtilsService: PlatformUtilsService,
stateService: StateService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
private messagingService: MessagingService,
logService: LogService
) {
super(
authService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
case 'windowIsFocused':
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send('getWindowIsFocused');
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(EnvironmentComponent, this.environmentModal);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
onWindowHidden() {
this.showPassword = false;
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById('content') as HTMLDivElement;
content.setAttribute('style', 'width:335px');
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(
EnvironmentComponent,
this.environmentModal
);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onSaved.subscribe(() => {
modal.close();
});
}
onWindowHidden() {
this.showPassword = false;
}
async submit() {
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
}

View File

@@ -1,70 +1,91 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="premiumTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="premiumTitle">
{{'premiumMembership' | i18n}}
</div>
<div class="box-content box-content-padded">
<div *ngIf="!isPremium">
<p class="text-center lead">{{'premiumNotCurrentMember' | i18n}}</p>
<p>{{'premiumSignUpAndGet' | i18n}}</p>
<ul class="fa-ul">
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpStorage' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTwoStep' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpReports' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTotp' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpSupport' | i18n}}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpFuture' | i18n}}
</li>
</ul>
<p class="text-center lead no-margin">
{{'premiumPrice' | i18n : (price | currency:'$')}}
</p>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{'premiumCurrentMember' | i18n}}</p>
<p class="text-center">{{'premiumCurrentMemberThanks' | i18n}}</p>
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="premiumTitle">
{{ "premiumMembership" | i18n }}
</div>
<div class="box-content box-content-padded">
<div *ngIf="!isPremium">
<p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="fa-ul">
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpStorage" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpTwoStep" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpReports" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpTotp" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpSupport" | i18n }}
</li>
<li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{ "premiumSignUpFuture" | i18n }}
</li>
</ul>
<p class="text-center lead no-margin">
{{ "premiumPrice" | i18n: (price | currency: "$") }}
</p>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{'premiumManage' | i18n}}</b>
</button>
<button #purchaseBtn type="button" class="primary" appBlurClick (click)="purchase()" *ngIf="!isPremium"
[disabled]="purchaseBtn.loading">
<b>{{'premiumPurchase' | i18n}}</b>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="right" *ngIf="!isPremium">
<button #refreshBtn type="button" appBlurClick (click)="refresh()" [disabled]="refreshBtn.loading"
appA11yTitle="{{'premiumRefresh' | i18n}}" [appApiAction]="refreshPromise">
<i class="fa fa-refresh fa-lg fa-fw" [hidden]="refreshBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!refreshBtn.loading"
aria-hidden="true"></i>
</button>
</div>
<div *ngIf="isPremium">
<p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{ "premiumManage" | i18n }}</b>
</button>
<button
#purchaseBtn
type="button"
class="primary"
appBlurClick
(click)="purchase()"
*ngIf="!isPremium"
[disabled]="purchaseBtn.loading"
>
<b>{{ "premiumPurchase" | i18n }}</b>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right" *ngIf="!isPremium">
<button
#refreshBtn
type="button"
appBlurClick
(click)="refresh()"
[disabled]="refreshBtn.loading"
appA11yTitle="{{ 'premiumRefresh' | i18n }}"
[appApiAction]="refreshPromise"
>
<i
class="fa fa-refresh fa-lg fa-fw"
[hidden]="refreshBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!refreshBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,21 +1,25 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/components/premium.component';
import { PremiumComponent as BasePremiumComponent } from "jslib-angular/components/premium.component";
@Component({
selector: 'app-premium',
templateUrl: 'premium.component.html',
selector: "app-premium",
templateUrl: "premium.component.html",
})
export class PremiumComponent extends BasePremiumComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, logService: LogService,
stateService: StateService) {
super(i18nService, platformUtilsService, apiService, logService, stateService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
stateService: StateService
) {
super(i18nService, platformUtilsService, apiService, logService, stateService);
}
}

View File

@@ -1,93 +1,150 @@
<form id="register-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'createAccount' | i18n}}</h1>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required
[appAutofocus]="email === ''" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
[appAutofocus]="email !== ''" (input)="updatePasswordStrength()" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100" [ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}"></div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
<div class="content">
<h1>{{ "createAccount" | i18n }}</h1>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
[appAutofocus]="email === ''"
appInputVerbatim
/>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword" required
appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="email !== ''"
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
<div class="box last" *ngIf="showTerms">
<div class="box-footer checkbox">
<input type="checkbox" id="acceptPolicies" [(ngModel)]="acceptPolicies" name="AcceptPolicies">
<label for="acceptPolicies">
{{'acceptPolicies' | i18n}}<br>
<a href="https://bitwarden.com/terms/" target="_blank"
rel="noopener">{{'termsOfService' | i18n}}</a>,
<a href="https://bitwarden.com/privacy/" target="_blank"
rel="noopener">{{'privacyPolicy' | i18n}}</a>
</label>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="confirmMasterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="box last" *ngIf="showTerms">
<div class="box-footer checkbox">
<input
type="checkbox"
id="acceptPolicies"
[(ngModel)]="acceptPolicies"
name="AcceptPolicies"
/>
<label for="acceptPolicies">
{{ "acceptPolicies" | i18n }}<br />
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
"termsOfService" | i18n
}}</a
>,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
"privacyPolicy" | i18n
}}</a>
</label>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
</div>
</form>

View File

@@ -1,61 +1,74 @@
import {
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component';
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
const BroadcasterSubscriptionId = 'RegisterComponent';
const BroadcasterSubscriptionId = "RegisterComponent";
@Component({
selector: 'app-register',
templateUrl: 'register.component.html',
selector: "app-register",
templateUrl: "register.component.html",
})
export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, cryptoService: CryptoService,
apiService: ApiService, stateService: StateService,
platformUtilsService: PlatformUtilsService, passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, logService: LogService) {
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
passwordGenerationService, environmentService, logService);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
cryptoService: CryptoService,
apiService: ApiService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
logService: LogService
) {
super(
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
});
});
super.ngOnInit();
}
super.ngOnInit();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
}

View File

@@ -1,18 +1,28 @@
<div id="remove-password-page" *ngIf="!loading">
<div class="content">
<h1>{{'removeMasterPassword' | i18n}}</h1>
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="actionPromise" appBlurClick
(click)="convert()">
<b [hidden]="continuing">{{'removeMasterPassword' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button>
<button type="button" class="btn secondary block" [disabled]="actionPromise" appBlurClick
(click)="leave()">
<b [hidden]="leaving">{{'leaveOrganization' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!leaving" aria-hidden="true"></i>
</button>
</div>
<div class="content">
<h1>{{ "removeMasterPassword" | i18n }}</h1>
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
<div class="buttons">
<button
type="submit"
class="btn primary block"
[disabled]="actionPromise"
appBlurClick
(click)="convert()"
>
<b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn secondary block"
[disabled]="actionPromise"
appBlurClick
(click)="leave()"
>
<b [hidden]="leaving">{{ "leaveOrganization" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!leaving" aria-hidden="true"></i>
</button>
</div>
</div>
</div>

View File

@@ -1,10 +1,9 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { RemovePasswordComponent as BaseRemovePasswordComponent } from 'jslib-angular/components/remove-password.component';
import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
@Component({
selector: 'app-remove-password',
templateUrl: 'remove-password.component.html',
selector: "app-remove-password",
templateUrl: "remove-password.component.html",
})
export class RemovePasswordComponent extends BaseRemovePasswordComponent {
}
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}

View File

@@ -1,101 +1,159 @@
<form id="set-password-page" #form>
<div class="content">
<img class="logo-image" alt="Bitwarden">
<p class="lead">{{'setMasterPassword' | i18n}}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{'ssoCompleteRegistration' | i18n}}</app-callout>
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
*ngIf="resetPasswordAutoEnroll">
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</app-callout>
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
(input)="updatePasswordStrength()" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}">
</div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="password" name="MasterPasswordRetype"
class="monospaced" [(ngModel)]="masterPasswordRetype" required appInputVerbatim
autocomplete="new-password">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i *ngIf="form.loading" class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"
aria-hidden="true"></i>
<span>{{'submit' | i18n}}</span>
</button>
<button class="btn block" (click)="logOut()">
<span>{{'logOut' | i18n}}</span>
</button>
</div>
</form>
</div>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "setMasterPassword" | i18n }}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword"
>{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i
*ngIf="form.loading"
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button class="btn block" (click)="logOut()">
<span>{{ "logOut" | i18n }}</span>
</button>
</div>
</form>
</div>
</div>
</form>

View File

@@ -1,97 +1,106 @@
import {
Component,
NgZone,
OnDestroy,
} from '@angular/core';
import { Component, NgZone, OnDestroy } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
const BroadcasterSubscriptionId = 'SetPasswordComponent';
const BroadcasterSubscriptionId = "SetPasswordComponent";
import {
SetPasswordComponent as BaseSetPasswordComponent,
} from 'jslib-angular/components/set-password.component';
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
@Component({
selector: 'app-set-password',
templateUrl: 'set-password.component.html',
selector: "app-set-password",
templateUrl: "set-password.component.html",
})
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnDestroy {
constructor(apiService: ApiService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
policyService: PolicyService, router: Router,
syncService: SyncService, route: ActivatedRoute,
private broadcasterService: BroadcasterService, private ngZone: NgZone,
stateService: StateService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route,
stateService);
}
constructor(
apiService: ApiService,
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route,
stateService
);
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return 'success';
case 3:
return 'primary';
case 2:
return 'warning';
default:
return 'danger';
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
}
});
});
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t('strong');
case 3:
return this.i18nService.t('good');
case 2:
return this.i18nService.t('weak');
default:
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
}
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
onWindowHidden() {
this.showPassword = false;
}
}

View File

@@ -1,195 +1,285 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'settings' | i18n}}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<div class="box-header">
{{'security' | i18n}}
</div>
<div class="box-content box-content-padded">
<app-vault-timeout-input [vaultTimeouts]="vaultTimeouts" [formControl]="vaultTimeout" ngDefaultControl></app-vault-timeout-input>
<div class="form-group">
<label>{{'vaultTimeoutAction' | i18n}}</label>
<div class="radio radio-mt-2">
<label for="vaultTimeoutActionLock">
<input type="radio" name="VaultTimeoutAction" id="vaultTimeoutActionLock"
value="lock" [(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()">
{{'lock' | i18n}}
</label>
</div>
<small class="help-block">{{'vaultTimeoutActionLockDesc' | i18n}}</small>
<div class="radio">
<label for="vaultTimeoutActionLogOut">
<input type="radio" name="VaultTimeoutAction" id="vaultTimeoutActionLogOut"
value="logOut" [(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()">
{{'logOut' | i18n}}
</label>
</div>
<small class="help-block">{{'vaultTimeoutActionLogOutDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="pin">
<input id="pin" type="checkbox" name="PIN" [(ngModel)]="pin" (change)="updatePin()">
{{'unlockWithPin' | i18n}}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="biometric">
<input id="biometric" type="checkbox" name="biometric" [checked]="biometric" (change)="updateBiometric()">
{{biometricText | i18n}}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="noAutoPromptBiometrics">
<input id="noAutoPromptBiometrics" type="checkbox" name="noAutoPromptBiometrics" [(ngModel)]="noAutoPromptBiometrics"
[disabled]="!biometric" (change)="updateNoAutoPromptBiometrics()">
{{noAutoPromptBiometricsText | i18n}}
</label>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'options' | i18n}}
</div>
<div class="box-content box-content-padded">
<div class="form-group">
<label for="clearClipboard">{{'clearClipboard' | i18n}}</label>
<select id="clearClipboard" name="ClearClipboard" [(ngModel)]="clearClipboard"
(change)="saveClearClipboard()">
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'clearClipboardDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="minimizeOnCopyToClipboard">
<input id="minimizeOnCopyToClipboard" type="checkbox"
name="MinimizeOnCopyToClipboard" [(ngModel)]="minimizeOnCopyToClipboard"
(change)="saveMinOnCopyToClipboard()">
{{'minimizeOnCopyToClipboard' | i18n}}
</label>
</div>
<small class="help-block">{{'minimizeOnCopyToClipboardDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="disableFavicons">
<input id="disableFavicons" type="checkbox" name="DisableFavicons"
[(ngModel)]="disableFavicons" (change)="saveFavicons()">
{{'disableFavicon' | i18n}}
</label>
</div>
<small class="help-block">{{'disableFaviconDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegration">
<input id="enableBrowserIntegration" type="checkbox" name="EnableBrowserIntegration"
[(ngModel)]="enableBrowserIntegration" (change)="saveBrowserIntegration()">
{{'enableBrowserIntegration' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input id="enableBrowserIntegrationFingerprint" type="checkbox" name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint" (change)="saveBrowserIntegrationFingerprint()" [disabled]="!enableBrowserIntegration">
{{'enableBrowserIntegrationFingerprint' | i18n}}
</label>
</div>
<small class="help-block">{{'enableBrowserIntegrationFingerprintDesc' | i18n}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableTray">
<input id="enableTray" type="checkbox" name="EnableTray" [(ngModel)]="enableTray"
(change)="saveTray()">
{{enableTrayText}}
</label>
</div>
<small class="help-block">{{enableTrayDescText}}</small>
</div>
<div class="form-group" *ngIf="showMinToTray">
<div class="checkbox">
<label for="enableMinToTray">
<input id="enableMinToTray" type="checkbox" name="EnableMinToTray"
[(ngModel)]="enableMinToTray" (change)="saveMinToTray()">
{{enableMinToTrayText}}
</label>
</div>
<small class="help-block">{{enableMinToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableCloseToTray">
<input id="enableCloseToTray" type="checkbox" name="EnableCloseToTray"
[(ngModel)]="enableCloseToTray" (change)="saveCloseToTray()">
{{enableCloseToTrayText}}
</label>
</div>
<small class="help-block">{{enableCloseToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="startToTray">
<input id="startToTray" type="checkbox" name="StartToTray" [(ngModel)]="startToTray"
(change)="saveStartToTray()">
{{startToTrayText}}
</label>
</div>
<small class="help-block">{{startToTrayDescText}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="openAtLogin">
<input id="openAtLogin" type="checkbox" name="OpenAtLogin" [(ngModel)]="openAtLogin"
(change)="saveOpenAtLogin()">
{{'openAtLogin' | i18n}}
</label>
</div>
<small class="help-block">{{'openAtLoginDesc' | i18n}}</small>
</div>
<div class="form-group" *ngIf="showAlwaysShowDock">
<div class="checkbox">
<label for="alwaysShowDock">
<input id="alwaysShowDock" type="checkbox" name="AlwaysShowDock" [(ngModel)]="alwaysShowDock"
(change)="saveAlwaysShowDock()">
{{'alwaysShowDock' | i18n}}
</label>
</div>
<small class="help-block">{{'alwaysShowDockDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="theme">{{'theme' | i18n}}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'themeDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="locale">{{'language' | i18n}}</label>
<select id="locale" name="Locale" [(ngModel)]="locale" (change)="saveLocale()">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="help-block">{{'languageDesc' | i18n}}</small>
</div>
</div>
</div>
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<div class="box-header">
{{ "security" | i18n }}
</div>
<div class="box-content box-content-padded">
<app-vault-timeout-input
[vaultTimeouts]="vaultTimeouts"
[formControl]="vaultTimeout"
ngDefaultControl
></app-vault-timeout-input>
<div class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div class="radio radio-mt-2">
<label for="vaultTimeoutActionLock">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLock"
value="lock"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "lock" | i18n }}
</label>
</div>
<small class="help-block">{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
<div class="radio">
<label for="vaultTimeoutActionLogOut">
<input
type="radio"
name="VaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="logOut"
[(ngModel)]="vaultTimeoutAction"
(change)="saveVaultTimeoutOptions()"
/>
{{ "logOut" | i18n }}
</label>
</div>
<small class="help-block">{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="form-group">
<div class="checkbox">
<label for="pin">
<input
id="pin"
type="checkbox"
name="PIN"
[(ngModel)]="pin"
(change)="updatePin()"
/>
{{ "unlockWithPin" | i18n }}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="biometric">
<input
id="biometric"
type="checkbox"
name="biometric"
[checked]="biometric"
(change)="updateBiometric()"
/>
{{ biometricText | i18n }}
</label>
</div>
</div>
<div class="form-group" *ngIf="supportsBiometric">
<div class="checkbox">
<label for="noAutoPromptBiometrics">
<input
id="noAutoPromptBiometrics"
type="checkbox"
name="noAutoPromptBiometrics"
[(ngModel)]="noAutoPromptBiometrics"
[disabled]="!biometric"
(change)="updateNoAutoPromptBiometrics()"
/>
{{ noAutoPromptBiometricsText | i18n }}
</label>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "options" | i18n }}
</div>
<div class="box-content box-content-padded">
<div class="form-group">
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
<select
id="clearClipboard"
name="ClearClipboard"
[(ngModel)]="clearClipboard"
(change)="saveClearClipboard()"
>
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
<small class="help-block">{{ "clearClipboardDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="minimizeOnCopyToClipboard">
<input
id="minimizeOnCopyToClipboard"
type="checkbox"
name="MinimizeOnCopyToClipboard"
[(ngModel)]="minimizeOnCopyToClipboard"
(change)="saveMinOnCopyToClipboard()"
/>
{{ "minimizeOnCopyToClipboard" | i18n }}
</label>
</div>
<small class="help-block">{{ "minimizeOnCopyToClipboardDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="disableFavicons">
<input
id="disableFavicons"
type="checkbox"
name="DisableFavicons"
[(ngModel)]="disableFavicons"
(change)="saveFavicons()"
/>
{{ "disableFavicon" | i18n }}
</label>
</div>
<small class="help-block">{{ "disableFaviconDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegration">
<input
id="enableBrowserIntegration"
type="checkbox"
name="EnableBrowserIntegration"
[(ngModel)]="enableBrowserIntegration"
(change)="saveBrowserIntegration()"
/>
{{ "enableBrowserIntegration" | i18n }}
</label>
</div>
<small class="help-block">{{ "enableBrowserIntegrationDesc" | i18n }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableBrowserIntegrationFingerprint">
<input
id="enableBrowserIntegrationFingerprint"
type="checkbox"
name="EnableBrowserIntegrationFingerprint"
[(ngModel)]="enableBrowserIntegrationFingerprint"
(change)="saveBrowserIntegrationFingerprint()"
[disabled]="!enableBrowserIntegration"
/>
{{ "enableBrowserIntegrationFingerprint" | i18n }}
</label>
</div>
<small class="help-block">{{
"enableBrowserIntegrationFingerprintDesc" | i18n
}}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableTray">
<input
id="enableTray"
type="checkbox"
name="EnableTray"
[(ngModel)]="enableTray"
(change)="saveTray()"
/>
{{ enableTrayText }}
</label>
</div>
<small class="help-block">{{ enableTrayDescText }}</small>
</div>
<div class="form-group" *ngIf="showMinToTray">
<div class="checkbox">
<label for="enableMinToTray">
<input
id="enableMinToTray"
type="checkbox"
name="EnableMinToTray"
[(ngModel)]="enableMinToTray"
(change)="saveMinToTray()"
/>
{{ enableMinToTrayText }}
</label>
</div>
<small class="help-block">{{ enableMinToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="enableCloseToTray">
<input
id="enableCloseToTray"
type="checkbox"
name="EnableCloseToTray"
[(ngModel)]="enableCloseToTray"
(change)="saveCloseToTray()"
/>
{{ enableCloseToTrayText }}
</label>
</div>
<small class="help-block">{{ enableCloseToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="startToTray">
<input
id="startToTray"
type="checkbox"
name="StartToTray"
[(ngModel)]="startToTray"
(change)="saveStartToTray()"
/>
{{ startToTrayText }}
</label>
</div>
<small class="help-block">{{ startToTrayDescText }}</small>
</div>
<div class="form-group">
<div class="checkbox">
<label for="openAtLogin">
<input
id="openAtLogin"
type="checkbox"
name="OpenAtLogin"
[(ngModel)]="openAtLogin"
(change)="saveOpenAtLogin()"
/>
{{ "openAtLogin" | i18n }}
</label>
</div>
<small class="help-block">{{ "openAtLoginDesc" | i18n }}</small>
</div>
<div class="form-group" *ngIf="showAlwaysShowDock">
<div class="checkbox">
<label for="alwaysShowDock">
<input
id="alwaysShowDock"
type="checkbox"
name="AlwaysShowDock"
[(ngModel)]="alwaysShowDock"
(change)="saveAlwaysShowDock()"
/>
{{ "alwaysShowDock" | i18n }}
</label>
</div>
<small class="help-block">{{ "alwaysShowDockDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="theme">{{ "theme" | i18n }}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "themeDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="locale">{{ "language" | i18n }}</label>
<select id="locale" name="Locale" [(ngModel)]="locale" (change)="saveLocale()">
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="help-block">{{ "languageDesc" | i18n }}</small>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@@ -1,360 +1,393 @@
import {
Component,
OnInit,
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { Component, OnInit } from "@angular/core";
import { FormControl } from "@angular/forms";
import { debounceTime } from "rxjs/operators";
import { DeviceType } from 'jslib-common/enums/deviceType';
import { ThemeType } from 'jslib-common/enums/themeType';
import { DeviceType } from "jslib-common/enums/deviceType";
import { ThemeType } from "jslib-common/enums/themeType";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { SetPinComponent } from '../components/set-pin.component';
import { SetPinComponent } from "../components/set-pin.component";
import { Utils } from 'jslib-common/misc/utils';
import { isWindowsStore } from 'jslib-electron/utils';
import { Utils } from "jslib-common/misc/utils";
import { isWindowsStore } from "jslib-electron/utils";
import { StorageLocation } from 'jslib-common/enums/storageLocation';
import { StorageLocation } from "jslib-common/enums/storageLocation";
@Component({
selector: 'app-settings',
templateUrl: 'settings.component.html',
selector: "app-settings",
templateUrl: "settings.component.html",
})
export class SettingsComponent implements OnInit {
vaultTimeoutAction: string;
pin: boolean = null;
disableFavicons: boolean = false;
enableBrowserIntegration: boolean = false;
enableBrowserIntegrationFingerprint: boolean = false;
enableMinToTray: boolean = false;
enableCloseToTray: boolean = false;
enableTray: boolean = false;
showMinToTray: boolean = false;
startToTray: boolean = false;
minimizeOnCopyToClipboard: boolean = false;
locale: string;
vaultTimeouts: any[];
localeOptions: any[];
theme: string;
themeOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometric: boolean;
biometricText: string;
noAutoPromptBiometrics: boolean;
noAutoPromptBiometricsText: string;
alwaysShowDock: boolean;
showAlwaysShowDock: boolean = false;
openAtLogin: boolean;
requireEnableTray: boolean = false;
vaultTimeoutAction: string;
pin: boolean = null;
disableFavicons: boolean = false;
enableBrowserIntegration: boolean = false;
enableBrowserIntegrationFingerprint: boolean = false;
enableMinToTray: boolean = false;
enableCloseToTray: boolean = false;
enableTray: boolean = false;
showMinToTray: boolean = false;
startToTray: boolean = false;
minimizeOnCopyToClipboard: boolean = false;
locale: string;
vaultTimeouts: any[];
localeOptions: any[];
theme: string;
themeOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
supportsBiometric: boolean;
biometric: boolean;
biometricText: string;
noAutoPromptBiometrics: boolean;
noAutoPromptBiometricsText: string;
alwaysShowDock: boolean;
showAlwaysShowDock: boolean = false;
openAtLogin: boolean;
requireEnableTray: boolean = false;
enableTrayText: string;
enableTrayDescText: string;
enableMinToTrayText: string;
enableMinToTrayDescText: string;
enableCloseToTrayText: string;
enableCloseToTrayDescText: string;
startToTrayText: string;
startToTrayDescText: string;
enableTrayText: string;
enableTrayDescText: string;
enableMinToTrayText: string;
enableMinToTrayDescText: string;
enableCloseToTrayText: string;
enableCloseToTrayDescText: string;
startToTrayText: string;
startToTrayDescText: string;
vaultTimeout: FormControl = new FormControl(null);
vaultTimeout: FormControl = new FormControl(null);
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService, private stateService: StateService,
private messagingService: MessagingService, private cryptoService: CryptoService,
private modalService: ModalService) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService,
private messagingService: MessagingService,
private cryptoService: CryptoService,
private modalService: ModalService
) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
const trayKey = isMac ? 'enableMenuBar' : 'enableTray';
this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + 'Desc');
const trayKey = isMac ? "enableMenuBar" : "enableTray";
this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + "Desc");
const minToTrayKey = isMac ? 'enableMinToMenuBar' : 'enableMinToTray';
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + 'Desc');
const minToTrayKey = isMac ? "enableMinToMenuBar" : "enableMinToTray";
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + "Desc");
const closeToTrayKey = isMac ? 'enableCloseToMenuBar' : 'enableCloseToTray';
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + 'Desc');
const closeToTrayKey = isMac ? "enableCloseToMenuBar" : "enableCloseToTray";
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + "Desc");
const startToTrayKey = isMac ? 'startToMenuBar' : 'startToTray';
this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + 'Desc');
const startToTrayKey = isMac ? "startToMenuBar" : "startToTray";
this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc");
this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t('oneMinute'), value: 1 },
{ name: i18nService.t('fiveMinutes'), value: 5 },
{ name: i18nService.t('fifteenMinutes'), value: 15 },
{ name: i18nService.t('thirtyMinutes'), value: 30 },
{ name: i18nService.t('oneHour'), value: 60 },
{ name: i18nService.t('fourHours'), value: 240 },
{ name: i18nService.t('onIdle'), value: -4 },
{ name: i18nService.t('onSleep'), value: -3 },
];
this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t("oneMinute"), value: 1 },
{ name: i18nService.t("fiveMinutes"), value: 5 },
{ name: i18nService.t("fifteenMinutes"), value: 15 },
{ name: i18nService.t("thirtyMinutes"), value: 30 },
{ name: i18nService.t("oneHour"), value: 60 },
{ name: i18nService.t("fourHours"), value: 240 },
{ name: i18nService.t("onIdle"), value: -4 },
{ name: i18nService.t("onSleep"), value: -3 },
];
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t('onLocked'), value: -2 });
}
this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t('onRestart'), value: -1 },
{ name: i18nService.t('never'), value: null },
]);
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
this.saveVaultTimeoutOptions();
});
const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach(locale => {
let name = locale;
if (i18nService.localeNames.has(locale)) {
name += (' - ' + i18nService.localeNames.get(locale));
}
localeOptions.push({ name: name, value: locale });
});
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
this.localeOptions = localeOptions;
this.themeOptions = [
{ name: i18nService.t('default'), value: null },
{ name: i18nService.t('light'), value: ThemeType.Light },
{ name: i18nService.t('dark'), value: ThemeType.Dark },
{ name: 'Nord', value: ThemeType.Nord },
];
this.clearClipboardOptions = [
{ name: i18nService.t('never'), value: null },
{ name: i18nService.t('tenSeconds'), value: 10 },
{ name: i18nService.t('twentySeconds'), value: 20 },
{ name: i18nService.t('thirtySeconds'), value: 30 },
{ name: i18nService.t('oneMinute'), value: 60 },
{ name: i18nService.t('twoMinutes'), value: 120 },
{ name: i18nService.t('fiveMinutes'), value: 300 },
];
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t("onLocked"), value: -2 });
}
async ngOnInit() {
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.stateService.getDisableFavicon();
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
this.enableBrowserIntegrationFingerprint = await this.stateService.getEnableBrowserIntegrationFingerprint();
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
this.enableTray = await this.stateService.getEnableTray();
this.startToTray = await this.stateService.getEnableStartToTray();
this.locale = await this.stateService.getLocale();
this.theme = await this.stateService.getTheme();
this.clearClipboard = await this.stateService.getClearClipboard();
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.stateService.getBiometricText();
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics();
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.stateService.getOpenAtLogin();
this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t("onRestart"), value: -1 },
{ name: i18nService.t("never"), value: null },
]);
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
this.saveVaultTimeoutOptions();
});
const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach((locale) => {
let name = locale;
if (i18nService.localeNames.has(locale)) {
name += " - " + i18nService.localeNames.get(locale);
}
localeOptions.push({ name: name, value: locale });
});
localeOptions.sort(Utils.getSortFunction(i18nService, "name"));
localeOptions.splice(0, 0, { name: i18nService.t("default"), value: null });
this.localeOptions = localeOptions;
this.themeOptions = [
{ name: i18nService.t("default"), value: null },
{ name: i18nService.t("light"), value: ThemeType.Light },
{ name: i18nService.t("dark"), value: ThemeType.Dark },
{ name: "Nord", value: ThemeType.Nord },
];
this.clearClipboardOptions = [
{ name: i18nService.t("never"), value: null },
{ name: i18nService.t("tenSeconds"), value: 10 },
{ name: i18nService.t("twentySeconds"), value: 20 },
{ name: i18nService.t("thirtySeconds"), value: 30 },
{ name: i18nService.t("oneMinute"), value: 60 },
{ name: i18nService.t("twoMinutes"), value: 120 },
{ name: i18nService.t("fiveMinutes"), value: 300 },
];
}
async ngOnInit() {
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
const pinSet = await this.vaultTimeoutService.isPinLockSet();
this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.stateService.getDisableFavicon();
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
this.enableBrowserIntegrationFingerprint =
await this.stateService.getEnableBrowserIntegrationFingerprint();
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
this.enableTray = await this.stateService.getEnableTray();
this.startToTray = await this.stateService.getEnableStartToTray();
this.locale = await this.stateService.getLocale();
this.theme = await this.stateService.getTheme();
this.clearClipboard = await this.stateService.getClearClipboard();
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
this.biometricText = await this.stateService.getBiometricText();
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics();
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
this.openAtLogin = await this.stateService.getOpenAtLogin();
}
async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === "logOut") {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
this.i18nService.t("yes"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) {
this.vaultTimeoutAction = "lock";
return;
}
}
async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === 'logOut') {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('vaultTimeoutLogOutConfirmation'),
this.i18nService.t('vaultTimeoutLogOutConfirmationTitle'),
this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning');
if (!confirmed) {
this.vaultTimeoutAction = 'lock';
return;
}
}
// Avoid saving 0 since it's useless as a timeout value.
if (this.vaultTimeout.value === 0) {
return;
}
if (!this.vaultTimeout.valid) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('vaultTimeoutTooLarge'));
return;
}
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction);
// Avoid saving 0 since it's useless as a timeout value.
if (this.vaultTimeout.value === 0) {
return;
}
async updatePin() {
if (this.pin) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
if (ref == null) {
this.pin = false;
return;
}
this.pin = await ref.onClosedPromise();
}
if (!this.pin) {
await this.cryptoService.clearPinProtectedKey();
await this.vaultTimeoutService.clear();
}
if (!this.vaultTimeout.valid) {
this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("vaultTimeoutTooLarge")
);
return;
}
async updateBiometric() {
const current = this.biometric;
if (this.biometric) {
this.biometric = false;
} else if (this.supportsBiometric) {
this.biometric = await this.platformUtilsService.authenticateBiometric();
}
if (this.biometric === current) {
return;
}
if (this.biometric) {
await this.stateService.setBiometricUnlock(true);
} else {
await this.stateService.setBiometricUnlock(null);
await this.stateService.setNoAutoPromptBiometrics(null);
this.noAutoPromptBiometrics = false;
}
await this.stateService.setBiometricLocked(false);
await this.cryptoService.toggleKey();
await this.vaultTimeoutService.setVaultTimeoutOptions(
this.vaultTimeout.value,
this.vaultTimeoutAction
);
}
async updatePin() {
if (this.pin) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
if (ref == null) {
this.pin = false;
return;
}
this.pin = await ref.onClosedPromise();
}
if (!this.pin) {
await this.cryptoService.clearPinProtectedKey();
await this.vaultTimeoutService.clear();
}
}
async updateBiometric() {
const current = this.biometric;
if (this.biometric) {
this.biometric = false;
} else if (this.supportsBiometric) {
this.biometric = await this.platformUtilsService.authenticateBiometric();
}
if (this.biometric === current) {
return;
}
if (this.biometric) {
await this.stateService.setBiometricUnlock(true);
} else {
await this.stateService.setBiometricUnlock(null);
await this.stateService.setNoAutoPromptBiometrics(null);
this.noAutoPromptBiometrics = false;
}
await this.stateService.setBiometricLocked(false);
await this.cryptoService.toggleKey();
}
async updateNoAutoPromptBiometrics() {
if (!this.biometric) {
this.noAutoPromptBiometrics = false;
}
async updateNoAutoPromptBiometrics() {
if (!this.biometric) {
this.noAutoPromptBiometrics = false;
}
if (this.noAutoPromptBiometrics) {
await this.stateService.setNoAutoPromptBiometrics(true);
} else {
await this.stateService.setNoAutoPromptBiometrics(null);
}
}
if (this.noAutoPromptBiometrics) {
await this.stateService.setNoAutoPromptBiometrics(true);
} else {
await this.stateService.setNoAutoPromptBiometrics(null);
}
async saveFavicons() {
await this.stateService.setDisableFavicon(this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons, {
storageLocation: StorageLocation.Disk,
});
this.messagingService.send("refreshCiphers");
}
async saveMinToTray() {
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveFavicons() {
await this.stateService.setDisableFavicon(this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons, { storageLocation: StorageLocation.Disk });
this.messagingService.send('refreshCiphers');
}
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
}
async saveMinToTray() {
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
}
async saveCloseToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
}
async saveTray() {
if (this.requireEnableTray && !this.enableTray && (this.startToTray || this.enableCloseToTray)) {
const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t('confirmTrayDesc'), this.i18nService.t('confirmTrayTitle'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (confirm) {
this.startToTray = false;
await this.stateService.setEnableStartToTray(this.startToTray);
this.enableCloseToTray = false;
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
} else {
this.enableTray = true;
}
return;
}
await this.stateService.setEnableTray(this.enableTray);
this.messagingService.send(this.enableTray ? 'showTray' : 'removeTray');
}
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveTray() {
if (
this.requireEnableTray &&
!this.enableTray &&
(this.startToTray || this.enableCloseToTray)
) {
const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t("confirmTrayDesc"),
this.i18nService.t("confirmTrayTitle"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (confirm) {
this.startToTray = false;
await this.stateService.setEnableStartToTray(this.startToTray);
this.enableCloseToTray = false;
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
} else {
this.enableTray = true;
}
return;
}
async saveLocale() {
await this.stateService.setLocale(this.locale);
await this.stateService.setEnableTray(this.enableTray);
this.messagingService.send(this.enableTray ? "showTray" : "removeTray");
}
async saveStartToTray() {
if (this.requireEnableTray) {
this.enableTray = true;
await this.stateService.setEnableTray(this.enableTray);
}
async saveTheme() {
await this.stateService.setTheme(this.theme);
window.setTimeout(() => window.location.reload(), 200);
await this.stateService.setEnableStartToTray(this.startToTray);
}
async saveLocale() {
await this.stateService.setLocale(this.locale);
}
async saveTheme() {
await this.stateService.setTheme(this.theme);
window.setTimeout(() => window.location.reload(), 200);
}
async saveMinOnCopyToClipboard() {
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
}
async saveClearClipboard() {
await this.stateService.setClearClipboard(this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? "addOpenAtLogin" : "removeOpenAtLogin");
}
async saveBrowserIntegration() {
if (process.platform === "darwin" && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t("browserIntegrationMasOnlyDesc"),
this.i18nService.t("browserIntegrationMasOnlyTitle"),
this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false;
return;
} else if (isWindowsStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t("browserIntegrationWindowsStoreDesc"),
this.i18nService.t("browserIntegrationWindowsStoreTitle"),
this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false;
return;
}
async saveMinOnCopyToClipboard() {
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(
this.enableBrowserIntegration ? "enableBrowserIntegration" : "disableBrowserIntegration"
);
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveClearClipboard() {
await this.stateService.setClearClipboard(this.clearClipboard);
}
async saveAlwaysShowDock() {
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
}
async saveOpenAtLogin() {
this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? 'addOpenAtLogin' : 'removeOpenAtLogin');
}
async saveBrowserIntegration() {
if (process.platform === 'darwin' && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationMasOnlyDesc'),
this.i18nService.t('browserIntegrationMasOnlyTitle'),
this.i18nService.t('ok'), null, 'warning');
this.enableBrowserIntegration = false;
return;
} else if (isWindowsStore()) {
await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationWindowsStoreDesc'),
this.i18nService.t('browserIntegrationWindowsStoreTitle'),
this.i18nService.t('ok'), null, 'warning');
this.enableBrowserIntegration = false;
return;
}
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration');
if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false;
this.saveBrowserIntegrationFingerprint();
}
}
async saveBrowserIntegrationFingerprint() {
await this.stateService.setEnableBrowserIntegrationFingerprint(this.enableBrowserIntegrationFingerprint);
}
async saveBrowserIntegrationFingerprint() {
await this.stateService.setEnableBrowserIntegrationFingerprint(
this.enableBrowserIntegrationFingerprint
);
}
}

View File

@@ -1,9 +1,9 @@
<form id="sso-page" (ngSubmit)="submit()">
<div class="content">
<img class="logo-image" alt="Bitwarden">
<div class="box">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<div class="box">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
</div>
</form>

View File

@@ -1,40 +1,56 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
@Component({
selector: 'app-sso',
templateUrl: 'sso.component.html',
selector: "app-sso",
templateUrl: "sso.component.html",
})
export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, syncService: SyncService, route: ActivatedRoute,
stateService: StateService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
logService: LogService) {
super(authService, router, i18nService, route, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
this.redirectUri = 'bitwarden://sso-callback';
this.clientId = 'desktop';
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
syncService: SyncService,
route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
logService: LogService
) {
super(
authService,
router,
i18nService,
route,
stateService,
platformUtilsService,
apiService,
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
this.redirectUri = "bitwarden://sso-callback";
this.clientId = "desktop";
}
}

View File

@@ -1,28 +1,33 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="twoStepTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="twoStepTitle">
{{'twoStepOptions' | i18n}}
</div>
<div class="box-content">
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row"
(click)="choose(p)">
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right">
<span class="text">{{p.name}}</span>
<span class="detail">{{p.description}}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="twoStepTitle">
{{ "twoStepOptions" | i18n }}
</div>
<div class="box-content">
<a
href="#"
appStopClick
*ngFor="let p of providers"
class="box-content-row"
(click)="choose(p)"
>
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right" />
<span class="text">{{ p.name }}</span>
<span class="detail">{{ p.description }}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@@ -1,21 +1,23 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib-angular/components/two-factor-options.component';
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
@Component({
selector: 'app-two-factor-options',
templateUrl: 'two-factor-options.component.html',
selector: "app-two-factor-options",
templateUrl: "two-factor-options.component.html",
})
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
super(authService, router, i18nService, platformUtilsService, window);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(authService, router, i18nService, platformUtilsService, window);
}
}

View File

@@ -1,89 +1,139 @@
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
<div class="content">
<h1>{{title}}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">{{'enterVerificationCodeApp' | i18n}}</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
</p>
<div class="box last"
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{'verificationCode' | i18n}}</label>
<input id="code" type="text" name="Code" [(ngModel)]="token" required appAutofocus appInputVerbatim>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
<form
id="two-factor-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
attr.aria-hidden="{{ showingModal }}"
>
<div class="content">
<h1>{{ title }}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">
{{ "enterVerificationCodeApp" | i18n }}
</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</p>
<div
class="box last"
*ngIf="
selectedProviderType === providerType.Email ||
selectedProviderType === providerType.Authenticator
"
>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="text"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p>{{'insertYubiKey' | i18n}}</p>
<img src="../../images/yubikey.jpg" alt="">
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus
appInputVerbatim>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<div class="box-content">
<div class="box-content-row">
<p>{{'noTwoStepProviders' | i18n}}</p>
<p>{{'noTwoStepProviders2' | i18n}}</p>
</div>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick *ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo">
<span [hidden]="form.loading"><i class="fa fa-sign-in" aria-hidden="true"></i>
{{'continue' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
<div class="sub-options">
<a href="#" appStopClick (click)="anotherMethod()" role="button">{{'useAnotherTwoStepMethod' | i18n}}</a>
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise" role="button"
*ngIf="selectedProviderType === providerType.Email">
{{'sendVerificationCodeEmailAgain' | i18n}}
</a>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p>{{ "insertYubiKey" | i18n }}</p>
<img src="../../images/yubikey.jpg" alt="" />
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="password"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<ng-container
*ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<div class="box-content">
<div class="box-content-row">
<p>{{ "noTwoStepProviders" | i18n }}</p>
<p>{{ "noTwoStepProviders2" | i18n }}</p>
</div>
</div>
</div>
<div class="buttons">
<button
type="submit"
class="btn primary block"
[disabled]="form.loading"
appBlurClick
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo
"
>
<span [hidden]="form.loading"
><i class="fa fa-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}</span
>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a routerLink="/login" class="btn block">{{ "cancel" | i18n }}</a>
</div>
<div class="sub-options">
<a href="#" appStopClick (click)="anotherMethod()" role="button">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
<a
href="#"
appStopClick
(click)="sendEmail(true)"
[appApiAction]="emailPromise"
role="button"
*ngIf="selectedProviderType === providerType.Email"
>
{{ "sendVerificationCodeEmailAgain" | i18n }}
</a>
</div>
</div>
</form>
<ng-template #twoFactorOptions></ng-template>

View File

@@ -1,70 +1,84 @@
import {
Component,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { TwoFactorOptionsComponent } from "./two-factor-options.component";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component';
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
selector: "app-two-factor",
templateUrl: "two-factor.component.html",
})
export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild('twoFactorOptions', { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef;
@ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
twoFactorOptionsModal: ViewContainerRef;
showingModal = false;
showingModal = false;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, syncService: SyncService,
environmentService: EnvironmentService, private modalService: ModalService,
stateService: StateService, route: ActivatedRoute,
logService: LogService) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, route, logService);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
apiService: ApiService,
platformUtilsService: PlatformUtilsService,
syncService: SyncService,
environmentService: EnvironmentService,
private modalService: ModalService,
stateService: StateService,
route: ActivatedRoute,
logService: LogService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
route,
logService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async anotherMethod() {
const [modal, childComponent] = await this.modalService.openViewRef(TwoFactorOptionsComponent, this.twoFactorOptionsModal);
async anotherMethod() {
const [modal, childComponent] = await this.modalService.openViewRef(
TwoFactorOptionsComponent,
this.twoFactorOptionsModal
);
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
modal.onShown.subscribe(() => {
this.showingModal = true;
});
modal.onClosed.subscribe(() => {
this.showingModal = false;
});
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
}
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
childComponent.onRecoverSelected.subscribe(() => {
modal.close();
});
}
}

View File

@@ -1,80 +1,124 @@
<form id="update-temp-password-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<app-callout type="warning" title="{{'updateMasterPassword' | i18n}}">
{{'updateMasterPasswordWarning' | i18n}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreStyle.Color}}"
*ngIf="masterPasswordScoreStyle.Text">
{{masterPasswordScoreStyle.Text}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
[appAutofocus]="masterPassword === ''" (input)="updatePasswordStrength()"
appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreStyle.Color}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreStyle.Width + '%')}"
attr.aria-valuenow="{{masterPasswordScoreStyle.Width}}"></div>
</div>
</div>
<div class="content">
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ "updateMasterPasswordWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreStyle.Color }}"
*ngIf="masterPasswordScoreStyle.Text"
>
{{ masterPasswordScoreStyle.Text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="masterPassword === ''"
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="masterPasswordRetype" required
appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreStyle.Color }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreStyle.Width + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreStyle.Width }}"
></div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a (click)="logOut()" class="btn block">{{'logOut' | i18n}}</a>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<a (click)="logOut()" class="btn block">{{ "logOut" | i18n }}</a>
</div>
</div>
</form>

View File

@@ -1,65 +1,81 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
interface MasterPasswordScore {
Color: string;
Text: string;
Width: number;
Color: string;
Text: string;
Width: number;
}
@Component({
selector: 'app-update-temp-password',
templateUrl: 'update-temp-password.component.html',
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: 'bg-success',
Text: 'strong',
Width: scoreWidth,
};
case 3:
return {
Color: 'bg-primary',
Text: 'good',
Width: scoreWidth,
};
case 2:
return {
Color: 'bg-warning',
Text: 'weak',
Width: scoreWidth,
};
default:
return {
Color: 'bg-danger',
Text: 'weak',
Width: scoreWidth,
};
}
}
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, messagingService: MessagingService,
apiService: ApiService, syncService: SyncService,
logService: LogService, stateService: StateService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
messagingService, apiService, stateService, syncService, logService);
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: "bg-success",
Text: "strong",
Width: scoreWidth,
};
case 3:
return {
Color: "bg-primary",
Text: "good",
Width: scoreWidth,
};
case 2:
return {
Color: "bg-warning",
Text: "weak",
Width: scoreWidth,
};
default:
return {
Color: "bg-danger",
Text: "weak",
Width: scoreWidth,
};
}
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
apiService: ApiService,
syncService: SyncService,
logService: LogService,
stateService: StateService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
syncService,
logService
);
}
}

View File

@@ -1,24 +1,46 @@
<app-callout type="info" *ngIf="vaultTimeoutPolicy">
{{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}}
{{ "vaultTimeoutPolicyInEffect" | i18n: vaultTimeoutPolicyHours:vaultTimeoutPolicyMinutes }}
</app-callout>
<div [formGroup]="form">
<div class="form-group">
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
<select id="vaultTimeout" name="VaultTimeout" formControlName="vaultTimeout" class="form-control" appAutofocus>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
<div class="form-group">
<label for="vaultTimeout">{{ "vaultTimeout" | i18n }}</label>
<select
id="vaultTimeout"
name="VaultTimeout"
formControlName="vaultTimeout"
class="form-control"
appAutofocus
>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "vaultTimeoutDesc" | i18n }}</small>
</div>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom">
<div class="col">
<label for="hours">{{ "hours" | i18n }}</label>
<input
id="hours"
class="form-control"
type="number"
min="0"
name="hours"
formControlName="hours"
/>
</div>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom">
<div class="col">
<label for="hours">{{'hours' | i18n}}</label>
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours">
</div>
<div class="col">
<label for="minutes">{{'minutes' | i18n}}</label>
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes" formControlName="minutes">
</div>
<div class="col">
<label for="minutes">{{ "minutes" | i18n }}</label>
<input
id="minutes"
class="form-control"
type="number"
min="0"
max="59"
name="minutes"
formControlName="minutes"
/>
</div>
<div class="form-group"></div> <!-- Styling fix -->
</div>
<div class="form-group"></div>
<!-- Styling fix -->
</div>

View File

@@ -1,28 +1,22 @@
import { Component } from '@angular/core';
import {
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import { Component } from "@angular/core";
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from "@angular/forms";
import {
VaultTimeoutInputComponent as VaultTimeoutInputComponentBase
} from 'jslib-angular/components/settings/vault-timeout-input.component';
import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "jslib-angular/components/settings/vault-timeout-input.component";
@Component({
selector: 'app-vault-timeout-input',
templateUrl: 'vault-timeout-input.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
selector: "app-vault-timeout-input",
templateUrl: "vault-timeout-input.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
})
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {
}
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {}

View File

@@ -1,72 +1,71 @@
import { NgModule } from '@angular/core';
import {
RouterModule,
Routes,
} from '@angular/router';
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
import { LockGuardService } from 'jslib-angular/services/lock-guard.service';
import { LoginGuardService } from '../services/loginGuard.service';
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { LoginGuardService } from "../services/loginGuard.service";
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component';
import { RemovePasswordComponent } from './accounts/remove-password.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { SendComponent } from './send/send.component';
import { SendComponent } from "./send/send.component";
import { VaultComponent } from './vault/vault.component';
import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [
{ path: '', redirectTo: '/vault', pathMatch: 'full' },
{
path: 'lock',
component: LockComponent,
canActivate: [LockGuardService],
},
{
path: 'login',
component: LoginComponent,
canActivate: [LoginGuardService],
},
{ path: '2fa', component: TwoFactorComponent },
{ path: 'register', component: RegisterComponent },
{
path: 'vault',
component: VaultComponent,
canActivate: [AuthGuardService],
},
{ path: 'hint', component: HintComponent },
{ path: 'set-password', component: SetPasswordComponent },
{ path: 'sso', component: SsoComponent },
{
path: 'send',
component: SendComponent,
canActivate: [AuthGuardService],
},
{
path: 'update-temp-password',
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
},
{
path: 'remove-password',
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
data: { titleId: 'removeMasterPassword' },
},
{ path: "", redirectTo: "/vault", pathMatch: "full" },
{
path: "lock",
component: LockComponent,
canActivate: [LockGuardService],
},
{
path: "login",
component: LoginComponent,
canActivate: [LoginGuardService],
},
{ path: "2fa", component: TwoFactorComponent },
{ path: "register", component: RegisterComponent },
{
path: "vault",
component: VaultComponent,
canActivate: [AuthGuardService],
},
{ path: "hint", component: HintComponent },
{ path: "set-password", component: SetPasswordComponent },
{ path: "sso", component: SsoComponent },
{
path: "send",
component: SendComponent,
canActivate: [AuthGuardService],
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
data: { titleId: "removeMasterPassword" },
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
})],
exports: [RouterModule],
imports: [
RouterModule.forRoot(routes, {
useHash: true,
/*enableTracing: true,*/
}),
],
exports: [RouterModule],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

File diff suppressed because it is too large Load Diff

View File

@@ -1,257 +1,257 @@
import 'zone.js/dist/zone';
import "zone.js/dist/zone";
import { A11yModule } from '@angular/cdk/a11y';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { OverlayModule } from '@angular/cdk/overlay';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { DatePipe } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import 'zone.js/dist/zone';
import { A11yModule } from "@angular/cdk/a11y";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { OverlayModule } from "@angular/cdk/overlay";
import { ScrollingModule } from "@angular/cdk/scrolling";
import { DatePipe } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import "zone.js/dist/zone";
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services.module';
import { AppRoutingModule } from "./app-routing.module";
import { ServicesModule } from "./services.module";
import { AppComponent } from './app.component';
import { AppComponent } from "./app.component";
import { EnvironmentComponent } from './accounts/environment.component';
import { HintComponent } from './accounts/hint.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { PremiumComponent } from './accounts/premium.component';
import { RegisterComponent } from './accounts/register.component';
import { RemovePasswordComponent } from './accounts/remove-password.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SettingsComponent } from './accounts/settings.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { VaultTimeoutInputComponent } from './accounts/vault-timeout-input.component';
import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { PremiumComponent } from "./accounts/premium.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SettingsComponent } from "./accounts/settings.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { VaultTimeoutInputComponent } from "./accounts/vault-timeout-input.component";
import { AvatarComponent } from 'jslib-angular/components/avatar.component';
import { CalloutComponent } from 'jslib-angular/components/callout.component';
import { IconComponent } from 'jslib-angular/components/icon.component';
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component';
import { AvatarComponent } from "jslib-angular/components/avatar.component";
import { CalloutComponent } from "jslib-angular/components/callout.component";
import { IconComponent } from "jslib-angular/components/icon.component";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
import { CipherListVirtualScroll } from 'jslib-angular/directives/cipherListVirtualScroll.directive';
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
import { InputVerbatimDirective } from 'jslib-angular/directives/input-verbatim.directive';
import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive';
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive';
import { TrueFalseValueDirective } from 'jslib-angular/directives/true-false-value.directive';
import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
import { CipherListVirtualScroll } from "jslib-angular/directives/cipherListVirtualScroll.directive";
import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive";
import { ColorPasswordPipe } from 'jslib-angular/pipes/color-password.pipe';
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe';
import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe";
import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
import { AddEditCustomFieldsComponent } from './vault/add-edit-custom-fields.component';
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { ExportComponent } from './vault/export.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
import { GroupingsComponent } from './vault/groupings.component';
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component';
import { PasswordGeneratorComponent } from './vault/password-generator.component';
import { PasswordHistoryComponent } from './vault/password-history.component';
import { ShareComponent } from './vault/share.component';
import { VaultComponent } from './vault/vault.component';
import { ViewCustomFieldsComponent } from './vault/view-custom-fields.component';
import { ViewComponent } from './vault/view.component';
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { ExportComponent } from "./vault/export.component";
import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordGeneratorHistoryComponent } from "./vault/password-generator-history.component";
import { PasswordGeneratorComponent } from "./vault/password-generator.component";
import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from "./vault/share.component";
import { VaultComponent } from "./vault/vault.component";
import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component";
import { ViewComponent } from "./vault/view.component";
import { AddEditComponent as SendAddEditComponent } from './send/add-edit.component';
import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component';
import { SendComponent } from './send/send.component';
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendComponent } from "./send/send.component";
import { AccountSwitcherComponent } from './layout/account-switcher.component';
import { HeaderComponent } from './layout/header.component';
import { NavComponent } from './layout/nav.component';
import { SearchComponent } from './layout/search/search.component';
import { AccountSwitcherComponent } from "./layout/account-switcher.component";
import { HeaderComponent } from "./layout/header.component";
import { NavComponent } from "./layout/nav.component";
import { SearchComponent } from "./layout/search/search.component";
import { PasswordRepromptComponent } from './components/password-reprompt.component';
import { SetPinComponent } from './components/set-pin.component';
import { VerifyMasterPasswordComponent } from './components/verify-master-password.component';
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { SetPinComponent } from "./components/set-pin.component";
import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
import { registerLocaleData } from '@angular/common';
import localeAf from '@angular/common/locales/af';
import localeAz from '@angular/common/locales/az';
import localeBe from '@angular/common/locales/be';
import localeBg from '@angular/common/locales/bg';
import localeBn from '@angular/common/locales/bn';
import localeCa from '@angular/common/locales/ca';
import localeCs from '@angular/common/locales/cs';
import localeDa from '@angular/common/locales/da';
import localeDe from '@angular/common/locales/de';
import localeEl from '@angular/common/locales/el';
import localeEnGb from '@angular/common/locales/en-GB';
import localeEnIn from '@angular/common/locales/en-IN';
import localeEs from '@angular/common/locales/es';
import localeEt from '@angular/common/locales/et';
import localeFa from '@angular/common/locales/fa';
import localeFi from '@angular/common/locales/fi';
import localeFr from '@angular/common/locales/fr';
import localeHe from '@angular/common/locales/he';
import localeHr from '@angular/common/locales/hr';
import localeHu from '@angular/common/locales/hu';
import localeId from '@angular/common/locales/id';
import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja';
import localeKn from '@angular/common/locales/kn';
import localeKo from '@angular/common/locales/ko';
import localeLv from '@angular/common/locales/lv';
import localeMl from '@angular/common/locales/ml';
import localeNb from '@angular/common/locales/nb';
import localeNl from '@angular/common/locales/nl';
import localePl from '@angular/common/locales/pl';
import localePtBr from '@angular/common/locales/pt';
import localePtPt from '@angular/common/locales/pt-PT';
import localeRo from '@angular/common/locales/ro';
import localeRu from '@angular/common/locales/ru';
import localeSk from '@angular/common/locales/sk';
import localeSr from '@angular/common/locales/sr';
import localeMe from '@angular/common/locales/sr-Latn-ME';
import localeSv from '@angular/common/locales/sv';
import localeTh from '@angular/common/locales/th';
import localeTr from '@angular/common/locales/tr';
import localeUk from '@angular/common/locales/uk';
import localeVi from '@angular/common/locales/vi';
import localeZhCn from '@angular/common/locales/zh-Hans';
import localeZhTw from '@angular/common/locales/zh-Hant';
import { registerLocaleData } from "@angular/common";
import localeAf from "@angular/common/locales/af";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFa from "@angular/common/locales/fa";
import localeFi from "@angular/common/locales/fi";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSk from "@angular/common/locales/sk";
import localeSr from "@angular/common/locales/sr";
import localeMe from "@angular/common/locales/sr-Latn-ME";
import localeSv from "@angular/common/locales/sv";
import localeTh from "@angular/common/locales/th";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
registerLocaleData(localeAf, 'af');
registerLocaleData(localeAz, 'az');
registerLocaleData(localeBe, 'be');
registerLocaleData(localeBg, 'bg');
registerLocaleData(localeBn, 'bn');
registerLocaleData(localeCa, 'ca');
registerLocaleData(localeCs, 'cs');
registerLocaleData(localeDa, 'da');
registerLocaleData(localeDe, 'de');
registerLocaleData(localeEl, 'el');
registerLocaleData(localeEnGb, 'en-GB');
registerLocaleData(localeEnIn, 'en-IN');
registerLocaleData(localeEs, 'es');
registerLocaleData(localeEt, 'et');
registerLocaleData(localeFa, 'fa');
registerLocaleData(localeFi, 'fi');
registerLocaleData(localeFr, 'fr');
registerLocaleData(localeHe, 'he');
registerLocaleData(localeHr, 'hr');
registerLocaleData(localeHu, 'hu');
registerLocaleData(localeId, 'id');
registerLocaleData(localeIt, 'it');
registerLocaleData(localeJa, 'ja');
registerLocaleData(localeKn, 'kn');
registerLocaleData(localeKo, 'ko');
registerLocaleData(localeLv, 'lv');
registerLocaleData(localeMe, 'me');
registerLocaleData(localeMl, 'ml');
registerLocaleData(localeNb, 'nb');
registerLocaleData(localeNl, 'nl');
registerLocaleData(localePl, 'pl');
registerLocaleData(localePtBr, 'pt-BR');
registerLocaleData(localePtPt, 'pt-PT');
registerLocaleData(localeRo, 'ro');
registerLocaleData(localeRu, 'ru');
registerLocaleData(localeSk, 'sk');
registerLocaleData(localeSr, 'sr');
registerLocaleData(localeSv, 'sv');
registerLocaleData(localeTh, 'th');
registerLocaleData(localeTr, 'tr');
registerLocaleData(localeUk, 'uk');
registerLocaleData(localeVi, 'vi');
registerLocaleData(localeZhCn, 'zh-CN');
registerLocaleData(localeZhTw, 'zh-TW');
registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMe, "me");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTh, "th");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
DragDropModule,
FormsModule,
ReactiveFormsModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
ScrollingModule,
A11yModule,
OverlayModule,
],
declarations: [
A11yTitleDirective,
AddEditComponent,
AddEditCustomFieldsComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
CipherListVirtualScroll,
CiphersComponent,
CollectionsComponent,
ColorPasswordPipe,
EnvironmentComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
GroupingsComponent,
HintComponent,
I18nPipe,
IconComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,
NavComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PasswordRepromptComponent,
PremiumComponent,
RegisterComponent,
RemovePasswordComponent,
SearchCiphersPipe,
SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SetPasswordComponent,
SetPinComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
HeaderComponent,
AccountSwitcherComponent,
AvatarComponent,
SearchComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],
imports: [
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
DragDropModule,
FormsModule,
ReactiveFormsModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
ScrollingModule,
A11yModule,
OverlayModule,
],
declarations: [
A11yTitleDirective,
AddEditComponent,
AddEditCustomFieldsComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
CipherListVirtualScroll,
CiphersComponent,
CollectionsComponent,
ColorPasswordPipe,
EnvironmentComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
GroupingsComponent,
HintComponent,
I18nPipe,
IconComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,
NavComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PasswordRepromptComponent,
PremiumComponent,
RegisterComponent,
RemovePasswordComponent,
SearchCiphersPipe,
SelectCopyDirective,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SetPasswordComponent,
SetPinComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
TrueFalseValueDirective,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
VaultComponent,
VaultTimeoutInputComponent,
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
HeaderComponent,
AccountSwitcherComponent,
AvatarComponent,
SearchComponent,
],
providers: [DatePipe],
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

@@ -1,38 +1,55 @@
<div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">{{'passwordConfirmation' | i18n}}</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appAutofocus>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'passwordConfirmationDesc' | i18n}}
</div>
</div>
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">{{ "passwordConfirmation" | i18n }}</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appAutofocus
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
</button>
</div>
</form>
</div>
</div>
<div class="box-footer">
{{ "passwordConfirmationDesc" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from 'jslib-angular/components/password-reprompt.component';
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component";
@Component({
templateUrl: 'password-reprompt.component.html',
templateUrl: "password-reprompt.component.html",
})
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@@ -1,44 +1,65 @@
<div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable set-pin-modal" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div>
{{'setYourPinCode' | i18n}}
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="pin">{{'pin' | i18n}}</label>
<input id="pin" type="{{showPin ? 'text' : 'password'}}" name="Pin" class="monospaced"
[(ngModel)]="pin" required appInputVerbatim appAutofocus>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleVisibility()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPin, 'fa-eye-slash': showPin}"></i>
</a>
</div>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input type="checkbox" id="masterPasswordOnRestart" name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart">
<span>{{'lockWithMasterPassOnRestart' | i18n}}</span>
</label>
</div>
<div class="modal-dialog modal-dialog-scrollable set-pin-modal" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div>
{{ "setYourPinCode" | i18n }}
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPin ? 'text' : 'password' }}"
name="Pin"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
appAutofocus
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleVisibility()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPin, 'fa-eye-slash': showPin }"
></i>
</a>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
</button>
</div>
</form>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input
type="checkbox"
id="masterPasswordOnRestart"
name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart"
/>
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { SetPinComponent as BaseSetPinComponent } from 'jslib-angular/components/set-pin.component';
import { SetPinComponent as BaseSetPinComponent } from "jslib-angular/components/set-pin.component";
@Component({
templateUrl: 'set-pin.component.html',
templateUrl: "set-pin.component.html",
})
export class SetPinComponent extends BaseSetPinComponent { }
export class SetPinComponent extends BaseSetPinComponent {}

View File

@@ -1,25 +1,46 @@
<ng-container *ngIf="!usesKeyConnector">
<div class="box-content-row" appBoxRow>
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<div class="box-content-row" appBoxRow>
<label class="d-block">{{'sendVerificationCode' | i18n}}</label>
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP">
{{'sendCode' | i18n}}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{'codeSent' | i18n}}
</span>
</div>
<div class="box-content-row" appBoxRow>
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button
type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{ "codeSent" | i18n }}
</span>
</div>
<div class="box-content-row" appBoxRow>
<label for="verificationCode">{{'verificationCode' | i18n}}</label>
<input id="verificationCode" type="input" name="verificationCode" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input
id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container>

View File

@@ -1,31 +1,23 @@
import {
animate,
style,
transition,
trigger,
} from '@angular/animations';
import { Component } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { animate, style, transition, trigger } from "@angular/animations";
import { Component } from "@angular/core";
import { NG_VALUE_ACCESSOR } from "@angular/forms";
import { VerifyMasterPasswordComponent as BaseComponent } from 'jslib-angular/components/verify-master-password.component';
import { VerifyMasterPasswordComponent as BaseComponent } from "jslib-angular/components/verify-master-password.component";
@Component({
selector: 'app-verify-master-password',
templateUrl: 'verify-master-password.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger('sent', [
transition(':enter', [
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
]),
],
selector: "app-verify-master-password",
templateUrl: "verify-master-password.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger("sent", [
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
})
export class VerifyMasterPasswordComponent extends BaseComponent { }
export class VerifyMasterPasswordComponent extends BaseComponent {}

View File

@@ -1,22 +1,50 @@
<a class="account-switcher" (click)="toggle()" cdkOverlayOrigin #trigger="cdkOverlayOrigin" [hidden]="!showSwitcher">
<app-avatar [data]="activeAccountEmail" size="25" [circle]="true" [fontSize]="14" [dynamic]="true" *ngIf="activeAccountEmail != null"></app-avatar>
<span>{{activeAccountEmail}}</span>
<i class="fa" aria-hidden="true" [ngClass]="{'fa-chevron-down': !isOpen, 'fa-chevron-up': isOpen}"></i>
<a
class="account-switcher"
(click)="toggle()"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
[hidden]="!showSwitcher"
>
<app-avatar
[data]="activeAccountEmail"
size="25"
[circle]="true"
[fontSize]="14"
[dynamic]="true"
*ngIf="activeAccountEmail != null"
></app-avatar>
<span>{{ activeAccountEmail }}</span>
<i
class="fa"
aria-hidden="true"
[ngClass]="{ 'fa-chevron-down': !isOpen, 'fa-chevron-up': isOpen }"
></i>
</a>
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="isOpen" cdkConnectedOverlayMinWidth="250px">
<div class="account-switcher-dropdown" [@transformPanel]="'open'">
<div class="accounts">
<a *ngFor="let a of accounts | keyvalue" class="account" [ngClass]="{'active': a.value.profile.authenticationStatus == 'active'}"
href="#" appStopClick (click)="switch(a.key)">
<span class="email">{{a.value.profile.email}}</span>
<span class="server">{{a.value.settings.environmentUrls.server}}</span>
<span class="status">{{a.value.profile.authenticationStatus}}</span>
</a>
</div>
<div class="border"></div>
<a class="add" routerLink="/login" (click)="toggle()">
<i class="fa fa-plus" aria-hidden="true"></i> {{'addAccount' | i18n}}
</a>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
cdkConnectedOverlayMinWidth="250px"
>
<div class="account-switcher-dropdown" [@transformPanel]="'open'">
<div class="accounts">
<a
*ngFor="let a of accounts | keyvalue"
class="account"
[ngClass]="{ active: a.value.profile.authenticationStatus == 'active' }"
href="#"
appStopClick
(click)="switch(a.key)"
>
<span class="email">{{ a.value.profile.email }}</span>
<span class="server">{{ a.value.settings.environmentUrls.server }}</span>
<span class="status">{{ a.value.profile.authenticationStatus }}</span>
</a>
</div>
<div class="border"></div>
<a class="add" routerLink="/login" (click)="toggle()">
<i class="fa fa-plus" aria-hidden="true"></i> {{ "addAccount" | i18n }}
</a>
</div>
</ng-template>

View File

@@ -1,78 +1,87 @@
import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { animate, state, style, transition, trigger } from "@angular/animations";
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { AuthenticationStatus } from 'jslib-common/enums/authenticationStatus';
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
import { Account } from 'jslib-common/models/domain/account';
import { Account } from "jslib-common/models/domain/account";
@Component({
selector: 'app-account-switcher',
templateUrl: 'account-switcher.component.html',
animations: [
trigger('transformPanel', [
state('void', style({
opacity: 0,
})),
transition('void => open', animate('100ms linear', style({
opacity: 1,
}))),
transition('* => void', animate('100ms linear', style({opacity: 0}))),
]),
],
selector: "app-account-switcher",
templateUrl: "account-switcher.component.html",
animations: [
trigger("transformPanel", [
state(
"void",
style({
opacity: 0,
})
),
transition(
"void => open",
animate(
"100ms linear",
style({
opacity: 1,
})
)
),
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
]),
],
})
export class AccountSwitcherComponent implements OnInit {
isOpen: boolean = false;
accounts: { [userId: string]: Account } = {};
activeAccountEmail: string;
isOpen: boolean = false;
accounts: { [userId: string]: Account } = {};
activeAccountEmail: string;
get showSwitcher() {
return this.accounts != null && Object.keys(this.accounts).length > 0;
}
get showSwitcher() {
return this.accounts != null && Object.keys(this.accounts).length > 0;
}
constructor(private stateService: StateService, private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService, private router: Router) {}
constructor(
private stateService: StateService,
private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService,
private router: Router
) {}
async ngOnInit(): Promise<void> {
this.stateService.accounts.subscribe(async accounts => {
for (const userId in accounts) {
if (userId === await this.stateService.getUserId()) {
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
} else {
accounts[userId].profile.authenticationStatus = await this.vaultTimeoutService.isLocked(userId) ?
AuthenticationStatus.Locked :
AuthenticationStatus.Unlocked;
}
}
this.accounts = accounts;
this.activeAccountEmail = await this.stateService.getEmail();
});
}
toggle() {
this.isOpen = !this.isOpen;
}
async switch(userId: string) {
await this.stateService.setActiveUser(userId);
const locked = await this.vaultTimeoutService.isLocked(userId);
if (locked) {
this.messagingService.send('locked', { userId: userId });
async ngOnInit(): Promise<void> {
this.stateService.accounts.subscribe(async (accounts) => {
for (const userId in accounts) {
if (userId === (await this.stateService.getUserId())) {
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
} else {
this.messagingService.send('unlocked');
this.messagingService.send('syncVault');
this.router.navigate(['vault']);
accounts[userId].profile.authenticationStatus = (await this.vaultTimeoutService.isLocked(
userId
))
? AuthenticationStatus.Locked
: AuthenticationStatus.Unlocked;
}
}
this.accounts = accounts;
this.activeAccountEmail = await this.stateService.getEmail();
});
}
toggle() {
this.isOpen = !this.isOpen;
}
async switch(userId: string) {
await this.stateService.setActiveUser(userId);
const locked = await this.vaultTimeoutService.isLocked(userId);
if (locked) {
this.messagingService.send("locked", { userId: userId });
} else {
this.messagingService.send("unlocked");
this.messagingService.send("syncVault");
this.router.navigate(["vault"]);
}
}
}

View File

@@ -1,4 +1,4 @@
<div class="header">
<app-search></app-search>
<app-account-switcher></app-account-switcher>
<app-search></app-search>
<app-account-switcher></app-account-switcher>
</div>

View File

@@ -1,8 +1,7 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
@Component({
selector: 'app-header',
templateUrl: 'header.component.html',
selector: "app-header",
templateUrl: "header.component.html",
})
export class HeaderComponent {
}
export class HeaderComponent {}

View File

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

View File

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

View File

@@ -1,39 +1,38 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
export type SearchBarState = {
enabled: boolean;
placeholderText: string;
enabled: boolean;
placeholderText: string;
};
@Injectable()
export class SearchBarService {
searchText = new BehaviorSubject<string>(null);
searchText = new BehaviorSubject<string>(null);
private _state = {
enabled: false,
placeholderText: "",
};
private _state = {
enabled: false,
placeholderText: '',
};
// tslint:disable-next-line:member-ordering
state = new BehaviorSubject<SearchBarState>(this._state);
// tslint:disable-next-line:member-ordering
state = new BehaviorSubject<SearchBarState>(this._state);
setEnabled(enabled: boolean) {
this._state.enabled = enabled;
this.updateState();
}
setEnabled(enabled: boolean) {
this._state.enabled = enabled;
this.updateState();
}
setPlaceholderText(placeholderText: string) {
this._state.placeholderText = placeholderText;
this.updateState();
}
setPlaceholderText(placeholderText: string) {
this._state.placeholderText = placeholderText;
this.updateState();
}
setSearchText(value: string) {
this.searchText.next(value);
}
setSearchText(value: string) {
this.searchText.next(value);
}
private updateState() {
this.state.next(this._state);
}
private updateState() {
this.state.next(this._state);
}
}

View File

@@ -1,4 +1,11 @@
<div class="search" *ngIf="state.enabled">
<input type="search" [placeholder]="state.placeholderText" id="search" autocomplete="off" [formControl]="searchText" appAutofocus>
<i class="fa fa-search" aria-hidden="true"></i>
<input
type="search"
[placeholder]="state.placeholderText"
id="search"
autocomplete="off"
[formControl]="searchText"
appAutofocus
/>
<i class="fa fa-search" aria-hidden="true"></i>
</div>

View File

@@ -1,24 +1,23 @@
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Component } from "@angular/core";
import { FormControl } from "@angular/forms";
import { SearchBarService, SearchBarState } from './search-bar.service';
import { SearchBarService, SearchBarState } from "./search-bar.service";
@Component({
selector: 'app-search',
templateUrl: 'search.component.html',
selector: "app-search",
templateUrl: "search.component.html",
})
export class SearchComponent {
state: SearchBarState;
searchText: FormControl = new FormControl(null);
state: SearchBarState;
searchText: FormControl = new FormControl(null);
constructor(private searchBarService: SearchBarService) {
this.searchBarService.state.subscribe((state) => {
this.state = state;
});
constructor(private searchBarService: SearchBarService) {
this.searchBarService.state.subscribe(state => {
this.state = state;
});
this.searchText.valueChanges.subscribe(value => {
this.searchBarService.setSearchText(value);
});
}
this.searchText.valueChanges.subscribe((value) => {
this.searchBarService.setSearchText(value);
});
}
}

View File

@@ -1,19 +1,19 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { isDev } from 'jslib-electron/utils';
import { isDev } from "jslib-electron/utils";
// tslint:disable-next-line
require('../scss/styles.scss');
require("../scss/styles.scss");
import { AppModule } from './app.module';
import { AppModule } from "./app.module";
if (!isDev()) {
enableProdMode();
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
// Disable drag and drop to prevent malicious links from executing in the context of the app
document.addEventListener('dragover', event => event.preventDefault());
document.addEventListener('drop', event => event.preventDefault());
document.addEventListener("dragover", (event) => event.preventDefault());
document.addEventListener("drop", (event) => event.preventDefault());

View File

@@ -1,176 +1,297 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<div class="inner-content" *ngIf="send">
<div class="box">
<app-callout *ngIf="disableSend">
<span>{{'sendDisabledWarning' | i18n}}</span>
</app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{'sendOptionsPolicyInEffect' | i18n}}
</app-callout>
</div>
<div class="box">
<div class="box-header">
{{title}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="send.name" appAutofocus [readOnly]="disableSend">
</div>
<div class="box-content-row box-content-row-radio" *ngIf="!editMode">
<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" [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">
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" class="form-control-file" name="file" required [disabled]="disableSend">
</div>
<div class="box-content-row" appBowRow *ngIf="editMode && send.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">
<label for="text">{{'text' | i18n}}</label>
<textarea id="text" name="text" [(ngModel)]="send.text.text" rows="6" [readOnly]="disableSend"></textarea>
</div>
</div>
<div class="box-footer" *ngIf="!editMode && send.type === sendType.File">
{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}}
</div>
<div class="box-footer" *ngIf="send.type === sendType.Text">
{{'sendTextDesc' | i18n}}
</div>
</div>
<div class="box" *ngIf="send.type === sendType.Text">
<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">
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'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 [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>
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label>
<input id="maxAccessCount" type="number" name="maxAccessCount" [(ngModel)]="send.maxAccessCount" [readOnly]="disableSend">
</div>
</div>
<div class="box-footer" *ngIf="!editMode">
{{'maxAccessCountDesc' | i18n}}
</div>
<div class="box-footer" *ngIf="editMode">
<p>{{'maxAccessCountDesc' | i18n}}</p>
{{'currentAccessCount' | i18n}}: <strong>{{send.accessCount}}</strong>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="password">{{(hasPassword ? 'newPassword' : 'password') | i18n}}</label>
<input id="password" name="password" type="{{showPassword ? 'text' : 'password'}}"
[(ngModel)]="password" [readOnly]="disableSend" appInputVerbatim>
</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 class="box-footer">
{{'sendPasswordDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-header">
{{'notes' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea id="notes" name="notes" [(ngModel)]="send.notes" rows="6" [readOnly]="disableSend"></textarea>
</div>
</div>
<div class="box-footer">
{{'sendNotesDesc' | i18n}}
</div>
</div>
<div class="box">
<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">
</div>
</div>
</div>
<div class="box">
<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">
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'share' | i18n}}
</div>
<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>
</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">
</div>
</div>
</div>
<div class="content">
<div class="inner-content" *ngIf="send">
<div class="box">
<app-callout *ngIf="disableSend">
<span>{{ "sendDisabledWarning" | i18n }}</span>
</app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{ "sendOptionsPolicyInEffect" | i18n }}
</app-callout>
</div>
<div class="box">
<div class="box-header">
{{ title }}
</div>
</div>
<div class="footer">
<button appBlurClick type="submit" class="primary btn-submit" appA11yTitle="{{'save' | i18n}}" [disabled]="form.loading" *ngIf="!disableSend">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span><i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i></span>
</button>
<button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading">
{{'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">
<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 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"
/>
</div>
<div class="box-content-row box-content-row-radio" *ngIf="!editMode">
<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"
[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">
<label for="file">{{ "file" | i18n }}</label>
<input
type="file"
id="file"
class="form-control-file"
name="file"
required
[disabled]="disableSend"
/>
</div>
<div class="box-content-row" appBowRow *ngIf="editMode && send.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">
<label for="text">{{ "text" | i18n }}</label>
<textarea
id="text"
name="text"
[(ngModel)]="send.text.text"
rows="6"
[readOnly]="disableSend"
></textarea>
</div>
</div>
<div class="box-footer" *ngIf="!editMode && send.type === sendType.File">
{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}
</div>
<div class="box-footer" *ngIf="send.type === sendType.Text">
{{ "sendTextDesc" | i18n }}
</div>
</div>
<div class="box" *ngIf="send.type === sendType.Text">
<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"
/>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "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 [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>
<label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</label>
<input
id="maxAccessCount"
type="number"
name="maxAccessCount"
[(ngModel)]="send.maxAccessCount"
[readOnly]="disableSend"
/>
</div>
</div>
<div class="box-footer" *ngIf="!editMode">
{{ "maxAccessCountDesc" | i18n }}
</div>
<div class="box-footer" *ngIf="editMode">
<p>{{ "maxAccessCountDesc" | i18n }}</p>
{{ "currentAccessCount" | i18n }}: <strong>{{ send.accessCount }}</strong>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="password">{{
(hasPassword ? "newPassword" : "password") | i18n
}}</label>
<input
id="password"
name="password"
type="{{ showPassword ? 'text' : 'password' }}"
[(ngModel)]="password"
[readOnly]="disableSend"
appInputVerbatim
/>
</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 class="box-footer">
{{ "sendPasswordDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-header">
{{ "notes" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea
id="notes"
name="notes"
[(ngModel)]="send.notes"
rows="6"
[readOnly]="disableSend"
></textarea>
</div>
</div>
<div class="box-footer">
{{ "sendNotesDesc" | i18n }}
</div>
</div>
<div class="box">
<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"
/>
</div>
</div>
</div>
<div class="box">
<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"
/>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "share" | i18n }}
</div>
<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 />
</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"
/>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
<button
appBlurClick
type="submit"
class="primary btn-submit"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
*ngIf="!disableSend"
>
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span><i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i></span>
</button>
<button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading">
{{ "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"
>
<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>

View File

@@ -1,47 +1,64 @@
import { DatePipe } from '@angular/common';
import { DatePipe } from "@angular/common";
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/send/add-edit.component';
import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/send/add-edit.component";
@Component({
selector: 'app-send-add-edit',
templateUrl: 'add-edit.component.html',
selector: "app-send-add-edit",
templateUrl: "add-edit.component.html",
})
export class AddEditComponent extends BaseAddEditComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, datePipe: DatePipe,
sendService: SendService, stateService: StateService,
messagingService: MessagingService, policyService: PolicyService,
logService: LogService) {
super(i18nService, platformUtilsService, environmentService,
datePipe, sendService, messagingService, policyService,
logService, stateService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
datePipe: DatePipe,
sendService: SendService,
stateService: StateService,
messagingService: MessagingService,
policyService: PolicyService,
logService: LogService
) {
super(
i18nService,
platformUtilsService,
environmentService,
datePipe,
sendService,
messagingService,
policyService,
logService,
stateService
);
}
async refresh() {
this.password = null;
const send = await this.loadSend();
this.send = await send.decrypt();
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
}
async refresh() {
this.password = null;
const send = await this.loadSend();
this.send = await send.decrypt();
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
}
cancel() {
this.onCancelled.emit(this.send);
}
cancel() {
this.onCancelled.emit(this.send);
}
async copyLinkToClipboard(link: string) {
super.copyLinkToClipboard(link);
this.platformUtilsService.showToast('success', null,
this.i18nService.t('valueCopied', this.i18nService.t('sendLink')));
}
async copyLinkToClipboard(link: string) {
super.copyLinkToClipboard(link);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("valueCopied", this.i18nService.t("sendLink"))
);
}
}

View File

@@ -1,35 +1,55 @@
<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" formControlName="selectedDeletionDatePreset" required>
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{o.name}}
</option>
</select>
<small 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"
formControlName="defaultDeletionDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM">
<small 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" formControlName="selectedExpirationDatePreset" required>
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{o.name}}
</option>
</select>
<small 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"
formControlName="defaultExpirationDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend">
<small *ngIf="editMode" class="help-block">{{'expirationDateDesc' | i18n}}</small>
</div>
</div>
<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"
formControlName="selectedDeletionDatePreset"
required
>
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small 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"
formControlName="defaultDeletionDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
/>
<small 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"
formControlName="selectedExpirationDatePreset"
required
>
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small 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"
formControlName="defaultExpirationDateTime"
required
placeholder="MM/DD/YYYY HH:MM AM/PM"
[readOnly]="disableSend"
/>
<small *ngIf="editMode" class="help-block">{{ "expirationDateDesc" | i18n }}</small>
</div>
</div>
</div>
</ng-container>

View File

@@ -1,38 +1,41 @@
import { DatePipe } from '@angular/common';
import { DatePipe } from "@angular/common";
import {
Component,
OnChanges,
} from '@angular/core';
import { Component, OnChanges } from "@angular/core";
import { ControlContainer, NgForm } from '@angular/forms';
import { ControlContainer, NgForm } from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EffluxDatesComponent as BaseEffluxDatesComponent } from 'jslib-angular/components/send/efflux-dates.component';
import { EffluxDatesComponent as BaseEffluxDatesComponent } from "jslib-angular/components/send/efflux-dates.component";
@Component({
selector: 'app-send-efflux-dates',
templateUrl: 'efflux-dates.component.html',
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
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);
}
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);
}
// 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);
}
}
}

View File

@@ -1,97 +1,141 @@
<div id="sends" class="vault">
<div class="groupings">
<div class="mac-bar"></div>
<div class="content">
<div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedAll}">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allSends' | i18n}}
</a>
</li>
</ul>
<h2>{{'types' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedType === sendType.Text}">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.Text)">
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>&nbsp;{{'sendTypeText' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === sendType.File}">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.File)">
<i class="fa fa-fw fa-file-o" aria-hidden="true"></i>&nbsp;{{'sendTypeFile' | i18n}}
</a>
</li>
</ul>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</div>
<div class="groupings">
<div class="mac-bar"></div>
<div class="content">
<div class="inner-content">
<h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{ "allSends" | i18n }}
</a>
</li>
</ul>
<h2>{{ "types" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedType === sendType.Text }">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.Text)">
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>&nbsp;{{
"sendTypeText" | i18n
}}
</a>
</li>
<li [ngClass]="{ active: selectedType === sendType.File }">
<a href="#" appStopClick appBlurClick (click)="selectType(sendType.File)">
<i class="fa fa-fw fa-file-o" aria-hidden="true"></i>&nbsp;{{ "sendTypeFile" | i18n }}
</a>
</li>
</ul>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</div>
<div id="items" class="items">
<div class="content">
<div class="list" *ngIf="filteredSends.length">
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)"
title="{{'viewItem' | i18n}}" (contextmenu)="viewSendMenu(s)"
[ngClass]="{'active': s.id === sendId}" class="flex-list-item">
<div class="item-icon" aria-hidden="true">
<i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-text-o' : 'fa-file-o'"></i>
</div>
<div class="item-content">
<div class="item-title">
{{s.name}}
<span class="title-badges">
<ng-container *ngIf="s.disabled">
<i class="fa fa-warning" appStopProp title="{{'disabled' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'disabled' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i class="fa fa-key" appStopProp title="{{'password' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'password' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.maxAccessCountReached">
<i class="fa fa-ban" appStopProp title="{{'maxAccessCountReached' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i class="fa fa-clock-o" appStopProp title="{{'expired' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'expired' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i class="fa fa-trash" appStopProp title="{{'pendingDeletion' | i18n}}"
aria-hidden="true"></i>
<span class="sr-only">{{'pendingDeletion' | i18n}}</span>
</ng-container>
</span>
</div>
<span class="item-details">{{s.deletionDate | date}}</span>
</div>
</a>
</div>
<div class="no-items" *ngIf="!filteredSends.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{'noItemsInList' | i18n}}</p>
</div>
<div id="items" class="items">
<div class="content">
<div class="list" *ngIf="filteredSends.length">
<a
*ngFor="let s of filteredSends"
appStopClick
(click)="selectSend(s.id)"
title="{{ 'viewItem' | i18n }}"
(contextmenu)="viewSendMenu(s)"
[ngClass]="{ active: s.id === sendId }"
class="flex-list-item"
>
<div class="item-icon" aria-hidden="true">
<i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-text-o' : 'fa-file-o'"></i>
</div>
<div class="item-content">
<div class="item-title">
{{ s.name }}
<span class="title-badges">
<ng-container *ngIf="s.disabled">
<i
class="fa fa-warning"
appStopProp
title="{{ 'disabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "disabled" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i
class="fa fa-key"
appStopProp
title="{{ 'password' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "password" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.maxAccessCountReached">
<i
class="fa fa-ban"
appStopProp
title="{{ 'maxAccessCountReached' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i
class="fa fa-clock-o"
appStopProp
title="{{ 'expired' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "expired" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i
class="fa fa-trash"
appStopProp
title="{{ 'pendingDeletion' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container>
</span>
</div>
</div>
<div class="footer">
<button appBlurClick (click)="addSend()" class="block primary" appA11yTitle="{{'addItem' | i18n}}">
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div>
<span class="item-details">{{ s.deletionDate | date }}</span>
</div>
</a>
</div>
<div class="no-items" *ngIf="!filteredSends.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{ "noItemsInList" | i18n }}</p>
</ng-container>
</div>
</div>
<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="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
<div class="footer">
<button
appBlurClick
(click)="addSend()"
class="block primary"
appA11yTitle="{{ 'addItem' | i18n }}"
>
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div>
</div>
<app-send-add-edit
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="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
</div>
</div>

View File

@@ -1,138 +1,146 @@
import {
Component,
NgZone,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { SendComponent as BaseSendComponent } from 'jslib-angular/components/send/send.component';
import { SendComponent as BaseSendComponent } from "jslib-angular/components/send/send.component";
import { invokeMenu, RendererMenuItem } from 'jslib-electron/utils';
import { invokeMenu, RendererMenuItem } from "jslib-electron/utils";
import { SendView } from 'jslib-common/models/view/sendView';
import { SendView } from "jslib-common/models/view/sendView";
import { SearchBarService } from '../layout/search/search-bar.service';
import { AddEditComponent } from './add-edit.component';
import { SearchBarService } from "../layout/search/search-bar.service";
import { AddEditComponent } from "./add-edit.component";
enum Action {
None = '',
Add = 'add',
Edit = 'edit',
None = "",
Add = "add",
Edit = "edit",
}
const BroadcasterSubscriptionId = 'SendComponent';
const BroadcasterSubscriptionId = "SendComponent";
@Component({
selector: 'app-send',
templateUrl: 'send.component.html',
selector: "app-send",
templateUrl: "send.component.html",
})
export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy {
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
sendId: string;
action: Action = Action.None;
sendId: string;
action: Action = Action.None;
constructor(sendService: SendService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
private broadcasterService: BroadcasterService, ngZone: NgZone,
searchService: SearchService, policyService: PolicyService,
private searchBarService: SearchBarService, logService: LogService) {
super(sendService, i18nService, platformUtilsService,
environmentService, ngZone, searchService,
policyService, logService);
this.searchBarService.searchText.subscribe(searchText => {
this.searchText = searchText;
this.searchTextChanged();
});
}
constructor(
sendService: SendService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
searchService: SearchService,
policyService: PolicyService,
private searchBarService: SearchBarService,
logService: LogService
) {
super(
sendService,
i18nService,
platformUtilsService,
environmentService,
ngZone,
searchService,
policyService,
logService
);
this.searchBarService.searchText.subscribe((searchText) => {
this.searchText = searchText;
this.searchTextChanged();
});
}
async ngOnInit() {
this.searchBarService.setEnabled(true);
this.searchBarService.setPlaceholderText(this.i18nService.t('searchSends'));
async ngOnInit() {
this.searchBarService.setEnabled(true);
this.searchBarService.setPlaceholderText(this.i18nService.t("searchSends"));
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
await this.load();
break;
}
});
});
await this.load();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.searchBarService.setEnabled(false);
}
addSend() {
this.action = Action.Add;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = null;
this.addEditComponent.send = null;
this.addEditComponent.load();
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "syncCompleted":
await this.load();
break;
}
}
});
});
await this.load();
}
cancel(s: SendView) {
this.action = Action.None;
this.sendId = null;
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.searchBarService.setEnabled(false);
}
async deletedSend(s: SendView) {
await this.refresh();
this.action = Action.None;
this.sendId = null;
addSend() {
this.action = Action.Add;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = null;
this.addEditComponent.send = null;
this.addEditComponent.load();
}
}
async savedSend(s: SendView) {
await this.refresh();
this.selectSend(s.id);
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) {
if (sendId === this.sendId && this.action === Action.Edit) {
return;
}
async selectSend(sendId: string) {
if (sendId === this.sendId && this.action === Action.Edit) {
return;
}
this.action = Action.Edit;
this.sendId = sendId;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = sendId;
await this.addEditComponent.refresh();
}
this.action = Action.Edit;
this.sendId = sendId;
if (this.addEditComponent != null) {
this.addEditComponent.sendId = sendId;
await this.addEditComponent.refresh();
}
}
get selectedSendType() {
return this.sends.find(s => s.id === this.sendId)?.type;
}
get selectedSendType() {
return this.sends.find((s) => s.id === this.sendId)?.type;
}
viewSendMenu(send: SendView) {
const menu: RendererMenuItem[] = [];
menu.push({
label: this.i18nService.t('copyLink'),
click: () => this.copy(send),
});
menu.push({
label: this.i18nService.t('delete'),
click: async () => {
await this.delete(send);
await this.deletedSend(send);
},
});
viewSendMenu(send: SendView) {
const menu: RendererMenuItem[] = [];
menu.push({
label: this.i18nService.t("copyLink"),
click: () => this.copy(send),
});
menu.push({
label: this.i18nService.t("delete"),
click: async () => {
await this.delete(send);
await this.deletedSend(send);
},
});
invokeMenu(menu);
}
invokeMenu(menu);
}
}

View File

@@ -1,179 +1,173 @@
import {
APP_INITIALIZER,
NgModule,
} from '@angular/core';
import { APP_INITIALIZER, NgModule } from "@angular/core";
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
import { ElectronPlatformUtilsService } from 'jslib-electron/services/electronPlatformUtils.service';
import { ElectronRendererMessagingService } from 'jslib-electron/services/electronRendererMessaging.service';
import { ElectronRendererSecureStorageService } from 'jslib-electron/services/electronRendererSecureStorage.service';
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service';
import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronPlatformUtilsService } from "jslib-electron/services/electronPlatformUtils.service";
import { ElectronRendererMessagingService } from "jslib-electron/services/electronRendererMessaging.service";
import { ElectronRendererSecureStorageService } from "jslib-electron/services/electronRendererSecureStorage.service";
import { ElectronRendererStorageService } from "jslib-electron/services/electronRendererStorage.service";
import { I18nService } from '../services/i18n.service';
import { LoginGuardService } from '../services/loginGuard.service';
import { NativeMessagingService } from '../services/nativeMessaging.service';
import { PasswordRepromptService } from '../services/passwordReprompt.service';
import { SearchBarService } from './layout/search/search-bar.service';
import { I18nService } from "../services/i18n.service";
import { LoginGuardService } from "../services/loginGuard.service";
import { NativeMessagingService } from "../services/nativeMessaging.service";
import { PasswordRepromptService } from "../services/passwordReprompt.service";
import { SearchBarService } from "./layout/search/search-bar.service";
import { JslibServicesModule } from 'jslib-angular/services/jslib-services.module';
import { JslibServicesModule } from "jslib-angular/services/jslib-services.module";
import { AuthService } from 'jslib-common/services/auth.service';
import { ContainerService } from 'jslib-common/services/container.service';
import { EventService } from 'jslib-common/services/event.service';
import { SystemService } from 'jslib-common/services/system.service';
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service';
import { AuthService } from "jslib-common/services/auth.service";
import { ContainerService } from "jslib-common/services/container.service";
import { EventService } from "jslib-common/services/event.service";
import { SystemService } from "jslib-common/services/system.service";
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { ElectronCryptoService } from 'jslib-electron/services/electronCrypto.service';
import { ElectronCryptoService } from "jslib-electron/services/electronCrypto.service";
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service';
import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service';
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service';
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service';
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service';
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service';
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service';
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service';
import { SystemService as SystemServiceAbstraction } from 'jslib-common/abstractions/system.service';
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { SystemService as SystemServiceAbstraction } from "jslib-common/abstractions/system.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
import { ThemeType } from 'jslib-common/enums/themeType';
import { ThemeType } from "jslib-common/enums/themeType";
export function initFactory(window: Window, environmentService: EnvironmentServiceAbstraction,
syncService: SyncServiceAbstraction, vaultTimeoutService: VaultTimeoutService,
i18nService: I18nService, eventService: EventService,
authService: AuthService, notificationsService: NotificationsServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction, stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction): Function {
export function initFactory(
window: Window,
environmentService: EnvironmentServiceAbstraction,
syncService: SyncServiceAbstraction,
vaultTimeoutService: VaultTimeoutService,
i18nService: I18nService,
eventService: EventService,
authService: AuthService,
notificationsService: NotificationsServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction
): Function {
return async () => {
await stateService.init();
await environmentService.setUrlsFromStorage();
syncService.fullSync(true);
await vaultTimeoutService.init(true);
const locale = await stateService.getLocale();
await i18nService.init(locale);
eventService.init(true);
authService.init();
setTimeout(() => notificationsService.init(), 3000);
const htmlEl = window.document.documentElement;
htmlEl.classList.add("os_" + platformUtilsService.getDeviceString());
htmlEl.classList.add("locale_" + i18nService.translationLocale);
const theme = await platformUtilsService.getEffectiveTheme();
htmlEl.classList.add("theme_" + theme);
platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => {
const bwTheme = await stateService.getTheme();
if (bwTheme == null || bwTheme === ThemeType.System) {
htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
htmlEl.classList.add("theme_" + sysTheme);
}
});
return async () => {
await stateService.init();
await environmentService.setUrlsFromStorage();
syncService.fullSync(true);
await vaultTimeoutService.init(true);
const locale = await stateService.getLocale();
await i18nService.init(locale);
eventService.init(true);
authService.init();
setTimeout(() => notificationsService.init(), 3000);
const htmlEl = window.document.documentElement;
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString());
htmlEl.classList.add('locale_' + i18nService.translationLocale);
const theme = await platformUtilsService.getEffectiveTheme();
htmlEl.classList.add('theme_' + theme);
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => {
const bwTheme = await stateService.getTheme();
if (bwTheme == null || bwTheme === ThemeType.System) {
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
htmlEl.classList.add('theme_' + sysTheme);
}
});
let installAction = null;
const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = "install";
} else if (installedVersion !== currentVersion) {
installAction = "update";
}
let installAction = null;
const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) {
installAction = 'install';
} else if (installedVersion !== currentVersion) {
installAction = 'update';
}
if (installAction != null) {
await stateService.setInstalledVersion(currentVersion);
}
if (installAction != null) {
await stateService.setInstalledVersion(currentVersion);
}
const containerService = new ContainerService(cryptoService);
containerService.attachToGlobal(window);
};
const containerService = new ContainerService(cryptoService);
containerService.attachToGlobal(window);
};
}
@NgModule({
imports: [
JslibServicesModule,
],
declarations: [],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initFactory,
deps: [
'WINDOW',
EnvironmentServiceAbstraction,
SyncServiceAbstraction,
VaultTimeoutServiceAbstraction,
I18nServiceAbstraction,
EventServiceAbstraction,
AuthServiceAbstraction,
NotificationsServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
CryptoServiceAbstraction,
],
multi: true,
},
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{
provide: PlatformUtilsServiceAbstraction,
useFactory: (i18nService: I18nServiceAbstraction, messagingService: MessagingServiceAbstraction,
stateService: StateServiceAbstraction) => new ElectronPlatformUtilsService(i18nService,
messagingService, true, stateService),
deps: [
I18nServiceAbstraction,
MessagingServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: I18nServiceAbstraction,
useFactory: (window: Window) => new I18nService(window.navigator.language, './locales'),
deps: [ 'WINDOW' ],
},
{
provide: MessagingServiceAbstraction,
useClass: ElectronRendererMessagingService,
deps: [ BroadcasterServiceAbstraction ],
},
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: 'SECURE_STORAGE', useClass: ElectronRendererSecureStorageService },
{
provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: SystemServiceAbstraction,
useClass: SystemService,
deps: [
VaultTimeoutServiceAbstraction,
MessagingServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
NativeMessagingService,
SearchBarService,
{
provide: LoginGuardService,
useClass: LoginGuardService,
deps: [
StateServiceAbstraction,
PlatformUtilsServiceAbstraction,
I18nServiceAbstraction,
],
},
],
imports: [JslibServicesModule],
declarations: [],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initFactory,
deps: [
"WINDOW",
EnvironmentServiceAbstraction,
SyncServiceAbstraction,
VaultTimeoutServiceAbstraction,
I18nServiceAbstraction,
EventServiceAbstraction,
AuthServiceAbstraction,
NotificationsServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
CryptoServiceAbstraction,
],
multi: true,
},
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{
provide: PlatformUtilsServiceAbstraction,
useFactory: (
i18nService: I18nServiceAbstraction,
messagingService: MessagingServiceAbstraction,
stateService: StateServiceAbstraction
) => new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService),
deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StateServiceAbstraction],
},
{
provide: I18nServiceAbstraction,
useFactory: (window: Window) => new I18nService(window.navigator.language, "./locales"),
deps: ["WINDOW"],
},
{
provide: MessagingServiceAbstraction,
useClass: ElectronRendererMessagingService,
deps: [BroadcasterServiceAbstraction],
},
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: "SECURE_STORAGE", useClass: ElectronRendererSecureStorageService },
{
provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService,
deps: [
CryptoFunctionServiceAbstraction,
PlatformUtilsServiceAbstraction,
LogServiceAbstraction,
StateServiceAbstraction,
],
},
{
provide: SystemServiceAbstraction,
useClass: SystemService,
deps: [
VaultTimeoutServiceAbstraction,
MessagingServiceAbstraction,
PlatformUtilsServiceAbstraction,
StateServiceAbstraction,
],
},
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
NativeMessagingService,
SearchBarService,
{
provide: LoginGuardService,
useClass: LoginGuardService,
deps: [StateServiceAbstraction, PlatformUtilsServiceAbstraction, I18nServiceAbstraction],
},
],
})
export class ServicesModule {
}
export class ServicesModule {}

View File

@@ -1,64 +1,120 @@
<div class="box">
<div class="box-header">
{{'customFields' | i18n}}
</div>
<div class="box-content">
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
<div class="box-content-row box-content-row-multi box-draggable-row" cdkDrag
*ngFor="let f of cipher.fields; let i = index; trackBy:trackByFunction"
[ngClass]="{'box-content-row-checkbox': f.type === fieldType.Boolean}">
<a href="#" appStopClick (click)="removeField(f)" appA11yTitle="{{'remove' | i18n}}"
role="button">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
</a>
<label for="fieldName{{i}}" class="sr-only">{{'name' | i18n}}</label>
<label for="fieldValue{{i}}" class="sr-only">{{'value' | i18n}}</label>
<div class="row-main">
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name"
class="row-label" placeholder="{{'name' | i18n}}" appInputVerbatim>
<!-- Text -->
<input id="fieldValue{{i}}" type="text" name="Field.Value{{i}}" [(ngModel)]="f.value"
*ngIf="f.type === fieldType.Text" placeholder="{{'value' | i18n}}" appInputVerbatim>
<!-- Password -->
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}"
name="Field.Value{{i}}" [(ngModel)]="f.value" class="monospaced"
*ngIf="f.type === fieldType.Hidden" placeholder="{{'value' | i18n}}"
[disabled]="!cipher.viewPassword && !f.newField" appInputVerbatim>
<!-- Linked -->
<select id="fieldValue{{i}}" name="Field.Value{{i}}" [(ngModel)]="f.linkedId"
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null">
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<!-- Boolean -->
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox" [(ngModel)]="f.value"
*ngIf="f.type === fieldType.Boolean" appTrueFalseValue trueValue="true"
falseValue="false">
<div class="action-buttons"
*ngIf="f.type === fieldType.Hidden && (cipher.viewPassword || f.newField)">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}"></i>
</a>
</div>
<div class="drag-handle" appA11yTitle="{{'dragToSort' | i18n}}" cdkDragHandle>
<i class="fa fa-bars" aria-hidden="true"></i>
</div>
</div>
<div class="box-header">
{{ "customFields" | i18n }}
</div>
<div class="box-content">
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
<div
class="box-content-row box-content-row-multi box-draggable-row"
cdkDrag
*ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction"
[ngClass]="{ 'box-content-row-checkbox': f.type === fieldType.Boolean }"
>
<a
href="#"
appStopClick
(click)="removeField(f)"
appA11yTitle="{{ 'remove' | i18n }}"
role="button"
>
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
</a>
<label for="fieldName{{ i }}" class="sr-only">{{ "name" | i18n }}</label>
<label for="fieldValue{{ i }}" class="sr-only">{{ "value" | i18n }}</label>
<div class="row-main">
<input
id="fieldName{{ i }}"
type="text"
name="Field.Name{{ i }}"
[(ngModel)]="f.name"
class="row-label"
placeholder="{{ 'name' | i18n }}"
appInputVerbatim
/>
<!-- Text -->
<input
id="fieldValue{{ i }}"
type="text"
name="Field.Value{{ i }}"
[(ngModel)]="f.value"
*ngIf="f.type === fieldType.Text"
placeholder="{{ 'value' | i18n }}"
appInputVerbatim
/>
<!-- Password -->
<input
id="fieldValue{{ i }}"
type="{{ f.showValue ? 'text' : 'password' }}"
name="Field.Value{{ i }}"
[(ngModel)]="f.value"
class="monospaced"
*ngIf="f.type === fieldType.Hidden"
placeholder="{{ 'value' | i18n }}"
[disabled]="!cipher.viewPassword && !f.newField"
appInputVerbatim
/>
<!-- Linked -->
<select
id="fieldValue{{ i }}"
name="Field.Value{{ i }}"
[(ngModel)]="f.linkedId"
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null"
>
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<!-- Add new custom field -->
<div class="box-content-row" appBoxRow>
<a href="#" appStopClick (click)="addField()" role="button">
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i> {{'newCustomField' | i18n}}
</a>
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
<select id="addFieldType" name="AddFieldType" [(ngModel)]="addFieldType" class="field-type">
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
<option *ngIf="cipher.linkedFieldOptions != null" [ngValue]="addFieldLinkedTypeOption.value">
{{addFieldLinkedTypeOption.name}}
</option>
</select>
<!-- Boolean -->
<input
id="fieldValue{{ i }}"
name="Field.Value{{ i }}"
type="checkbox"
[(ngModel)]="f.value"
*ngIf="f.type === fieldType.Boolean"
appTrueFalseValue
trueValue="true"
falseValue="false"
/>
<div
class="action-buttons"
*ngIf="f.type === fieldType.Hidden && (cipher.viewPassword || f.newField)"
>
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleFieldValue(f)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue }"
></i>
</a>
</div>
<div class="drag-handle" appA11yTitle="{{ 'dragToSort' | i18n }}" cdkDragHandle>
<i class="fa fa-bars" aria-hidden="true"></i>
</div>
</div>
</div>
<!-- Add new custom field -->
<div class="box-content-row" appBoxRow>
<a href="#" appStopClick (click)="addField()" role="button">
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i>
{{ "newCustomField" | i18n }}
</a>
<label for="addFieldType" class="sr-only">{{ "type" | i18n }}</label>
<select id="addFieldType" name="AddFieldType" [(ngModel)]="addFieldType" class="field-type">
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{ o.name }}</option>
<option
*ngIf="cipher.linkedFieldOptions != null"
[ngValue]="addFieldLinkedTypeOption.value"
>
{{ addFieldLinkedTypeOption.name }}
</option>
</select>
</div>
</div>
</div>

View File

@@ -1,18 +1,16 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import {
AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent
} from 'jslib-angular/components/add-edit-custom-fields.component';
import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "jslib-angular/components/add-edit-custom-fields.component";
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Component({
selector: 'app-vault-add-edit-custom-fields',
templateUrl: 'add-edit-custom-fields.component.html',
selector: "app-vault-add-edit-custom-fields",
templateUrl: "add-edit-custom-fields.component.html",
})
export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent {
constructor(i18nService: I18nService, eventService: EventService) {
super(i18nService, eventService);
}
constructor(i18nService: I18nService, eventService: EventService) {
super(i18nService, eventService);
}
}

View File

@@ -1,343 +1,603 @@
<form #form="ngForm" (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<div class="inner-content" *ngIf="cipher">
<div class="box">
<app-callout type="info" *ngIf="allowOwnershipOptions() && !allowPersonal">
{{'personalOwnershipPolicyInEffect' | i18n}}
</app-callout>
<div class="box-header">
{{title}}
</div>
<div class="box-content">
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
<label for="type">{{'type' | i18n}}</label>
<select id="type" name="Type" [(ngModel)]="cipher.type">
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="cipher.name" [appAutofocus]="!editMode">
</div>
<!-- Login -->
<div *ngIf="cipher.type === cipherType.Login">
<div class="box-content-row" appBoxRow>
<label for="loginUsername">{{'username' | i18n}}</label>
<input id="loginUsername" type="text" name="Login.Username"
[(ngModel)]="cipher.login.username" appInputVerbatim>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="loginPassword">{{'password' | i18n}}</label>
<input id="loginPassword" class="monospaced"
type="{{showPassword ? 'text' : 'password'}}" name="Login.Password"
[(ngModel)]="cipher.login.password" [disabled]="!cipher.viewPassword"
appInputVerbatim>
</div>
<div class="action-buttons" *ngIf=cipher.viewPassword>
<button type="button" #checkPasswordBtn class="row-btn btn" appBlurClick
appA11yTitle="{{'checkPassword' | i18n}}" (click)="checkPassword()"
[appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading">
<i class="fa fa-lg fa-check-circle" [hidden]="checkPasswordBtn.loading"
aria-hidden="true"></i>
<i class="fa fa-lg fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"
aria-hidden="true"></i>
</button>
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'generatePassword' | i18n}}" (click)="generatePassword()">
<i class="fa fa-lg fa-refresh" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="loginTotp">{{'authenticatorKeyTotp' | i18n}}</label>
<input id="loginTotp" type="{{cipher.viewPassword ? 'text' : 'password'}}" name="Login.Totp"
class="monospaced" [(ngModel)]="cipher.login.totp" [disabled]="!cipher.viewPassword"
appInputVerbatim>
</div>
</div>
<!-- Card -->
<div *ngIf="cipher.type === cipherType.Card">
<div class="box-content-row" appBoxRow>
<label for="cardCardholderName">{{'cardholderName' | i18n}}</label>
<input id="cardCardholderName" type="text" name="Card.CardCardholderName"
[(ngModel)]="cipher.card.cardholderName">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardNumber">{{'number' | i18n}}</label>
<input id="cardNumber" class="monospaced" type="{{showCardNumber ? 'text' : 'password'}}"
name="Card.Number" [(ngModel)]="cipher.card.number" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleCardNumber()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber}"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardBrand">{{'brand' | i18n}}</label>
<select id="cardBrand" name="Card.Brand" [(ngModel)]="cipher.card.brand">
<option *ngFor="let o of cardBrandOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpMonth">{{'expirationMonth' | i18n}}</label>
<select id="cardExpMonth" name="Card.ExpMonth" [(ngModel)]="cipher.card.expMonth">
<option *ngFor="let o of cardExpMonthOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpYear">{{'expirationYear' | i18n}}</label>
<input id="cardExpYear" type="text" name="Card.ExpYear" [(ngModel)]="cipher.card.expYear"
placeholder="{{'ex' | i18n}} {{currentDate | date: 'yyyy'}}">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardCode">{{'securityCode' | i18n}}</label>
<input id="cardCode" class="monospaced" type="{{showCardCode ? 'text' : 'password'}}"
name="Card.Code" [(ngModel)]="cipher.card.code" appInputVerbatim>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleCardCode()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode}"></i>
</a>
</div>
</div>
</div>
<!-- Identity -->
<div *ngIf="cipher.type === cipherType.Identity">
<div class="box-content-row" appBoxRow>
<label for="idTitle">{{'title' | i18n}}</label>
<select id="idTitle" name="Identity.Title" [(ngModel)]="cipher.identity.title">
<option *ngFor="let o of identityTitleOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="idFirstName">{{'firstName' | i18n}}</label>
<input id="idFirstName" type="text" name="Identity.FirstName"
[(ngModel)]="cipher.identity.firstName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idMiddleName">{{'middleName' | i18n}}</label>
<input id="idMiddleName" type="text" name="Identity.MiddleName"
[(ngModel)]="cipher.identity.middleName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idLastName">{{'lastName' | i18n}}</label>
<input id="idLastName" type="text" name="Identity.LastName"
[(ngModel)]="cipher.identity.lastName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idUsername">{{'username' | i18n}}</label>
<input id="idUsername" type="text" name="Identity.Username"
[(ngModel)]="cipher.identity.username" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idCompany">{{'company' | i18n}}</label>
<input id="idCompany" type="text" name="Identity.Company"
[(ngModel)]="cipher.identity.company">
</div>
<div class="box-content-row" appBoxRow>
<label for="idSsn">{{'ssn' | i18n}}</label>
<input id="idSsn" type="text" name="Identity.SSN" [(ngModel)]="cipher.identity.ssn"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPassportNumber">{{'passportNumber' | i18n}}</label>
<input id="idPassportNumber" type="text" name="Identity.PassportNumber"
[(ngModel)]="cipher.identity.passportNumber" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idLicenseNumber">{{'licenseNumber' | i18n}}</label>
<input id="idLicenseNumber" type="text" name="Identity.LicenseNumber"
[(ngModel)]="cipher.identity.licenseNumber" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idEmail">{{'email' | i18n}}</label>
<input id="idEmail" type="text" name="Identity.Email" [(ngModel)]="cipher.identity.email"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPhone">{{'phone' | i18n}}</label>
<input id="idPhone" type="text" name="Identity.Phone" [(ngModel)]="cipher.identity.phone">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress1">{{'address1' | i18n}}</label>
<input id="idAddress1" type="text" name="Identity.Address1"
[(ngModel)]="cipher.identity.address1">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress2">{{'address2' | i18n}}</label>
<input id="idAddress2" type="text" name="Identity.Address2"
[(ngModel)]="cipher.identity.address2">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress3">{{'address3' | i18n}}</label>
<input id="idAddress3" type="text" name="Identity.Address3"
[(ngModel)]="cipher.identity.address3">
</div>
<div class="box-content-row" appBoxRow>
<label for="idCity">{{'cityTown' | i18n}}</label>
<input id="idCity" type="text" name="Identity.City" [(ngModel)]="cipher.identity.city">
</div>
<div class="box-content-row" appBoxRow>
<label for="idState">{{'stateProvince' | i18n}}</label>
<input id="idState" type="text" name="Identity.State" [(ngModel)]="cipher.identity.state">
</div>
<div class="box-content-row" appBoxRow>
<label for="idPostalCode">{{'zipPostalCode' | i18n}}</label>
<input id="idPostalCode" type="text" name="Identity.PostalCode"
[(ngModel)]="cipher.identity.postalCode">
</div>
<div class="box-content-row" appBoxRow>
<label for="idCountry">{{'country' | i18n}}</label>
<input id="idCountry" type="text" name="Identity.Country"
[(ngModel)]="cipher.identity.country">
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.type === cipherType.Login">
<div class="box-content">
<ng-container *ngIf="cipher.login.hasUris">
<div class="box-content-row box-content-row-multi" appBoxRow
*ngFor="let u of cipher.login.uris; let i = index; trackBy:trackByFunction">
<a href="#" appStopClick (click)="removeUri(u)" appA11yTitle="{{'remove' | i18n}}">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true" role="button"></i>
</a>
<div class="row-main">
<label for="loginUri{{i}}">{{'uriPosition' | i18n : (i + 1)}}</label>
<input id="loginUri{{i}}" type="text" name="Login.Uris[{{i}}].Uri" [(ngModel)]="u.uri"
placeholder="{{'ex' | i18n}} https://google.com" appInputVerbatim>
<label for="loginUriMatch{{i}}" class="sr-only">
{{'matchDetection' | i18n}} {{(i + 1)}}
</label>
<select id="loginUriMatch{{i}}" name="Login.Uris[{{i}}].Match" [(ngModel)]="u.match"
[hidden]="u.showOptions === false || (u.showOptions == null && u.match == null)"
(change)="loginUriMatchChanged(u)">
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleOptions' | i18n}}" (click)="toggleUriOptions(u)">
<i class="fa fa-lg fa-cog" aria-hidden="true"></i>
</a>
</div>
</div>
</ng-container>
<a href="#" appStopClick appBlurClick (click)="addUri()" class="box-content-row" role="button">
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i> {{'newUri' | i18n}}
</a>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="folder">{{'folder' | i18n}}</label>
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
<option *ngFor="let f of folders" [ngValue]="f.id">{{f.name}}</option>
</select>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="favorite">{{'favorite' | i18n}}</label>
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="canUseReprompt">
<label for="passwordPrompt">{{'passwordPrompt' | i18n}}
<a href="#" appA11yTitle="{{'learnMore' | i18n}}" (click)="openHelpReprompt()">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</label>
<input id="passwordPrompt" type="checkbox" name="PasswordPrompt" [ngModel]="reprompt"
(change)="repromptChanged()">
</div>
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
(click)="attachments()" *ngIf="editMode && !cloneMode" role="button">
<div class="row-main">{{'attachments' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon" aria-hidden="true"></i>
</a>
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
(click)="editCollections()" *ngIf="editMode && !cloneMode && cipher.organizationId"
role="button">
<div class="row-main">{{'collections' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<label for="notes">{{'notes' | i18n}}</label>
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"></textarea>
</div>
</div>
</div>
<app-vault-add-edit-custom-fields [cipher]="cipher" [thisCipherType]="cipher.type" [editMode]="editMode">
</app-vault-add-edit-custom-fields>
<div class="box" *ngIf="allowOwnershipOptions()">
<div class="box-header">
{{'ownership' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="organizationId">{{'whoOwnsThisItem' | i18n}}</label>
<select id="organizationId" class="form-control" name="OrganizationId"
[(ngModel)]="cipher.organizationId" (change)="organizationChanged()">
<option *ngFor="let o of ownershipOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
</div>
<div class="box" *ngIf="(!editMode || cloneMode) && cipher.organizationId">
<div class="box-header">
{{'collections' | i18n}}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox" *ngFor="let c of collections; let i = index"
appBoxRow>
<label for="collection_{{i}}">{{c.name}}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
name="Collection[{{i}}].Checked">
</div>
</div>
</div>
<div class="content">
<div class="inner-content" *ngIf="cipher">
<div class="box">
<app-callout type="info" *ngIf="allowOwnershipOptions() && !allowPersonal">
{{ "personalOwnershipPolicyInEffect" | i18n }}
</app-callout>
<div class="box-header">
{{ title }}
</div>
</div>
<div class="footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" [disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button appBlurClick type="button" (click)="cancel()">
{{'cancel' | i18n}}
</button>
<div class="right">
<button appBlurClick type="button" (click)="share()" appA11yTitle="{{'moveToOrganization' | i18n}}"
*ngIf="editMode && cipher && !cipher.organizationId && !cloneMode">
<i class="fa fa-arrow-circle-o-right fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode && !cloneMode" [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 class="box-content">
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
<label for="type">{{ "type" | i18n }}</label>
<select id="type" name="Type" [(ngModel)]="cipher.type">
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
name="Name"
[(ngModel)]="cipher.name"
[appAutofocus]="!editMode"
/>
</div>
<!-- Login -->
<div *ngIf="cipher.type === cipherType.Login">
<div class="box-content-row" appBoxRow>
<label for="loginUsername">{{ "username" | i18n }}</label>
<input
id="loginUsername"
type="text"
name="Login.Username"
[(ngModel)]="cipher.login.username"
appInputVerbatim
/>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="loginPassword">{{ "password" | i18n }}</label>
<input
id="loginPassword"
class="monospaced"
type="{{ showPassword ? 'text' : 'password' }}"
name="Login.Password"
[(ngModel)]="cipher.login.password"
[disabled]="!cipher.viewPassword"
appInputVerbatim
/>
</div>
<div class="action-buttons" *ngIf="cipher.viewPassword">
<button
type="button"
#checkPasswordBtn
class="row-btn btn"
appBlurClick
appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()"
[appApiAction]="checkPasswordPromise"
[disabled]="checkPasswordBtn.loading"
>
<i
class="fa fa-lg fa-check-circle"
[hidden]="checkPasswordBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-lg fa-spinner fa-spin"
[hidden]="!checkPasswordBtn.loading"
aria-hidden="true"
></i>
</button>
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'generatePassword' | i18n }}"
(click)="generatePassword()"
>
<i class="fa fa-lg fa-refresh" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
<input
id="loginTotp"
type="{{ cipher.viewPassword ? 'text' : 'password' }}"
name="Login.Totp"
class="monospaced"
[(ngModel)]="cipher.login.totp"
[disabled]="!cipher.viewPassword"
appInputVerbatim
/>
</div>
</div>
<!-- Card -->
<div *ngIf="cipher.type === cipherType.Card">
<div class="box-content-row" appBoxRow>
<label for="cardCardholderName">{{ "cardholderName" | i18n }}</label>
<input
id="cardCardholderName"
type="text"
name="Card.CardCardholderName"
[(ngModel)]="cipher.card.cardholderName"
/>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardNumber">{{ "number" | i18n }}</label>
<input
id="cardNumber"
class="monospaced"
type="{{ showCardNumber ? 'text' : 'password' }}"
name="Card.Number"
[(ngModel)]="cipher.card.number"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardNumber()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber }"
></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardBrand">{{ "brand" | i18n }}</label>
<select id="cardBrand" name="Card.Brand" [(ngModel)]="cipher.card.brand">
<option *ngFor="let o of cardBrandOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpMonth">{{ "expirationMonth" | i18n }}</label>
<select id="cardExpMonth" name="Card.ExpMonth" [(ngModel)]="cipher.card.expMonth">
<option *ngFor="let o of cardExpMonthOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpYear">{{ "expirationYear" | i18n }}</label>
<input
id="cardExpYear"
type="text"
name="Card.ExpYear"
[(ngModel)]="cipher.card.expYear"
placeholder="{{ 'ex' | i18n }} {{ currentDate | date: 'yyyy' }}"
/>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="cardCode">{{ "securityCode" | i18n }}</label>
<input
id="cardCode"
class="monospaced"
type="{{ showCardCode ? 'text' : 'password' }}"
name="Card.Code"
[(ngModel)]="cipher.card.code"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardCode()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode }"
></i>
</a>
</div>
</div>
</div>
<!-- Identity -->
<div *ngIf="cipher.type === cipherType.Identity">
<div class="box-content-row" appBoxRow>
<label for="idTitle">{{ "title" | i18n }}</label>
<select id="idTitle" name="Identity.Title" [(ngModel)]="cipher.identity.title">
<option *ngFor="let o of identityTitleOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="idFirstName">{{ "firstName" | i18n }}</label>
<input
id="idFirstName"
type="text"
name="Identity.FirstName"
[(ngModel)]="cipher.identity.firstName"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idMiddleName">{{ "middleName" | i18n }}</label>
<input
id="idMiddleName"
type="text"
name="Identity.MiddleName"
[(ngModel)]="cipher.identity.middleName"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idLastName">{{ "lastName" | i18n }}</label>
<input
id="idLastName"
type="text"
name="Identity.LastName"
[(ngModel)]="cipher.identity.lastName"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idUsername">{{ "username" | i18n }}</label>
<input
id="idUsername"
type="text"
name="Identity.Username"
[(ngModel)]="cipher.identity.username"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idCompany">{{ "company" | i18n }}</label>
<input
id="idCompany"
type="text"
name="Identity.Company"
[(ngModel)]="cipher.identity.company"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idSsn">{{ "ssn" | i18n }}</label>
<input
id="idSsn"
type="text"
name="Identity.SSN"
[(ngModel)]="cipher.identity.ssn"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPassportNumber">{{ "passportNumber" | i18n }}</label>
<input
id="idPassportNumber"
type="text"
name="Identity.PassportNumber"
[(ngModel)]="cipher.identity.passportNumber"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idLicenseNumber">{{ "licenseNumber" | i18n }}</label>
<input
id="idLicenseNumber"
type="text"
name="Identity.LicenseNumber"
[(ngModel)]="cipher.identity.licenseNumber"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idEmail">{{ "email" | i18n }}</label>
<input
id="idEmail"
type="text"
name="Identity.Email"
[(ngModel)]="cipher.identity.email"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPhone">{{ "phone" | i18n }}</label>
<input
id="idPhone"
type="text"
name="Identity.Phone"
[(ngModel)]="cipher.identity.phone"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress1">{{ "address1" | i18n }}</label>
<input
id="idAddress1"
type="text"
name="Identity.Address1"
[(ngModel)]="cipher.identity.address1"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress2">{{ "address2" | i18n }}</label>
<input
id="idAddress2"
type="text"
name="Identity.Address2"
[(ngModel)]="cipher.identity.address2"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress3">{{ "address3" | i18n }}</label>
<input
id="idAddress3"
type="text"
name="Identity.Address3"
[(ngModel)]="cipher.identity.address3"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idCity">{{ "cityTown" | i18n }}</label>
<input
id="idCity"
type="text"
name="Identity.City"
[(ngModel)]="cipher.identity.city"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idState">{{ "stateProvince" | i18n }}</label>
<input
id="idState"
type="text"
name="Identity.State"
[(ngModel)]="cipher.identity.state"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idPostalCode">{{ "zipPostalCode" | i18n }}</label>
<input
id="idPostalCode"
type="text"
name="Identity.PostalCode"
[(ngModel)]="cipher.identity.postalCode"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="idCountry">{{ "country" | i18n }}</label>
<input
id="idCountry"
type="text"
name="Identity.Country"
[(ngModel)]="cipher.identity.country"
/>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.type === cipherType.Login">
<div class="box-content">
<ng-container *ngIf="cipher.login.hasUris">
<div
class="box-content-row box-content-row-multi"
appBoxRow
*ngFor="let u of cipher.login.uris; let i = index; trackBy: trackByFunction"
>
<a href="#" appStopClick (click)="removeUri(u)" appA11yTitle="{{ 'remove' | i18n }}">
<i class="fa fa-minus-circle fa-lg" aria-hidden="true" role="button"></i>
</a>
<div class="row-main">
<label for="loginUri{{ i }}">{{ "uriPosition" | i18n: i + 1 }}</label>
<input
id="loginUri{{ i }}"
type="text"
name="Login.Uris[{{ i }}].Uri"
[(ngModel)]="u.uri"
placeholder="{{ 'ex' | i18n }} https://google.com"
appInputVerbatim
/>
<label for="loginUriMatch{{ i }}" class="sr-only">
{{ "matchDetection" | i18n }} {{ i + 1 }}
</label>
<select
id="loginUriMatch{{ i }}"
name="Login.Uris[{{ i }}].Match"
[(ngModel)]="u.match"
[hidden]="u.showOptions === false || (u.showOptions == null && u.match == null)"
(change)="loginUriMatchChanged(u)"
>
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">
{{ o.name }}
</option>
</select>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleOptions' | i18n }}"
(click)="toggleUriOptions(u)"
>
<i class="fa fa-lg fa-cog" aria-hidden="true"></i>
</a>
</div>
</div>
</ng-container>
<a
href="#"
appStopClick
appBlurClick
(click)="addUri()"
class="box-content-row"
role="button"
>
<i class="fa fa-plus-circle fa-fw fa-lg" aria-hidden="true"></i> {{ "newUri" | i18n }}
</a>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="folder">{{ "folder" | i18n }}</label>
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option>
</select>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="favorite">{{ "favorite" | i18n }}</label>
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite" />
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="canUseReprompt">
<label for="passwordPrompt"
>{{ "passwordPrompt" | i18n }}
<a href="#" appA11yTitle="{{ 'learnMore' | i18n }}" (click)="openHelpReprompt()">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</label>
<input
id="passwordPrompt"
type="checkbox"
name="PasswordPrompt"
[ngModel]="reprompt"
(change)="repromptChanged()"
/>
</div>
<a
class="box-content-row box-content-row-flex text-default"
href="#"
appStopClick
appBlurClick
(click)="attachments()"
*ngIf="editMode && !cloneMode"
role="button"
>
<div class="row-main">{{ "attachments" | i18n }}</div>
<i class="fa fa-chevron-right row-sub-icon" aria-hidden="true"></i>
</a>
<a
class="box-content-row box-content-row-flex text-default"
href="#"
appStopClick
appBlurClick
(click)="editCollections()"
*ngIf="editMode && !cloneMode && cipher.organizationId"
role="button"
>
<div class="row-main">{{ "collections" | i18n }}</div>
<i class="fa fa-chevron-right row-sub-icon" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<label for="notes">{{ "notes" | i18n }}</label>
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"></textarea>
</div>
</div>
</div>
<app-vault-add-edit-custom-fields
[cipher]="cipher"
[thisCipherType]="cipher.type"
[editMode]="editMode"
>
</app-vault-add-edit-custom-fields>
<div class="box" *ngIf="allowOwnershipOptions()">
<div class="box-header">
{{ "ownership" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="organizationId">{{ "whoOwnsThisItem" | i18n }}</label>
<select
id="organizationId"
class="form-control"
name="OrganizationId"
[(ngModel)]="cipher.organizationId"
(change)="organizationChanged()"
>
<option *ngFor="let o of ownershipOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
</div>
</div>
<div class="box" *ngIf="(!editMode || cloneMode) && cipher.organizationId">
<div class="box-header">
{{ "collections" | i18n }}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div
class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index"
appBoxRow
>
<label for="collection_{{ i }}">{{ c.name }}</label>
<input
id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button appBlurClick type="button" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="share()"
appA11yTitle="{{ 'moveToOrganization' | i18n }}"
*ngIf="editMode && cipher && !cipher.organizationId && !cloneMode"
>
<i class="fa fa-arrow-circle-o-right fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode && !cloneMode"
[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>

View File

@@ -1,103 +1,125 @@
import {
Component,
NgZone,
OnChanges,
OnDestroy,
ViewChild
} from '@angular/core';
import { NgForm } from '@angular/forms';
import { Component, NgZone, OnChanges, OnDestroy, ViewChild } from "@angular/core";
import { NgForm } from "@angular/forms";
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/add-edit.component';
import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/add-edit.component";
const BroadcasterSubscriptionId = 'AddEditComponent';
const BroadcasterSubscriptionId = "AddEditComponent";
@Component({
selector: 'app-vault-add-edit',
templateUrl: 'add-edit.component.html',
selector: "app-vault-add-edit",
templateUrl: "add-edit.component.html",
})
export class AddEditComponent extends BaseAddEditComponent implements OnChanges, OnDestroy {
@ViewChild('form')
private form: NgForm;
constructor(cipherService: CipherService, folderService: FolderService,
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
auditService: AuditService, stateService: StateService,
collectionService: CollectionService, messagingService: MessagingService,
eventService: EventService, policyService: PolicyService,
passwordRepromptService: PasswordRepromptService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, logService: LogService,
organizationService: OrganizationService) {
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
collectionService, messagingService, eventService, policyService, logService,
passwordRepromptService, organizationService);
}
@ViewChild("form")
private form: NgForm;
constructor(
cipherService: CipherService,
folderService: FolderService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
auditService: AuditService,
stateService: StateService,
collectionService: CollectionService,
messagingService: MessagingService,
eventService: EventService,
policyService: PolicyService,
passwordRepromptService: PasswordRepromptService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
logService: LogService,
organizationService: OrganizationService
) {
super(
cipherService,
folderService,
i18nService,
platformUtilsService,
auditService,
stateService,
collectionService,
messagingService,
eventService,
policyService,
logService,
passwordRepromptService,
organizationService
);
}
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
// We use ngOnChanges for everything else instead.
}
async ngOnChanges() {
await super.init();
await this.load();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async load() {
if (document.querySelectorAll('app-vault-add-edit .ng-dirty').length === 0 ||
(this.cipher != null && this.cipherId !== this.cipher.id)) {
this.cipher = null;
async ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
super.load();
}
});
});
// We use ngOnChanges for everything else instead.
}
onWindowHidden() {
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach(field => {
field.showValue = false;
});
}
}
async ngOnChanges() {
await super.init();
await this.load();
}
allowOwnershipOptions(): boolean {
return (!this.editMode || this.cloneMode) && this.ownershipOptions
&& (this.ownershipOptions.length > 1 || !this.allowPersonal);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
markPasswordAsDirty() {
this.form.controls['Login.Password'].markAsDirty();
async load() {
if (
document.querySelectorAll("app-vault-add-edit .ng-dirty").length === 0 ||
(this.cipher != null && this.cipherId !== this.cipher.id)
) {
this.cipher = null;
}
super.load();
}
openHelpReprompt() {
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/managing-items/#protect-individual-items');
onWindowHidden() {
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach((field) => {
field.showValue = false;
});
}
}
allowOwnershipOptions(): boolean {
return (
(!this.editMode || this.cloneMode) &&
this.ownershipOptions &&
(this.ownershipOptions.length > 1 || !this.allowPersonal)
);
}
markPasswordAsDirty() {
this.form.controls["Login.Password"].markAsDirty();
}
openHelpReprompt() {
this.platformUtilsService.launchUri(
"https://bitwarden.com/help/article/managing-items/#protect-individual-items"
);
}
}

View File

@@ -1,53 +1,76 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="attachmentsTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box" *ngIf="cipher && cipher.hasAttachments">
<div class="box-header" id="attachmentsTitle">
{{'attachments' | i18n}}
</div>
<div class="box-content no-hover">
<div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments">
<div class="row-main">
{{a.fileName}}
</div>
<small class="row-sub-label">{{a.sizeName}}</small>
<div class="action-buttons no-pad">
<button class="row-btn btn" type="button" appStopClick appBlurClick
appA11yTitle="{{'delete' | i18n}}" (click)="delete(a)" #deleteBtn
[appApiAction]="deletePromises[a.id]" [disabled]="deleteBtn.loading">
<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>
</div>
</div>
<div class="box">
<div class="box-header">
{{'newAttachment' | i18n}}
</div>
<div class="box-content no-hover">
<div class="box-content-row">
<label for="file">{{'file' | i18n}}</label>
<input type="file" id="file" name="file" required>
</div>
</div>
<div class="box-footer">
{{'maxFileSize' | i18n}}
</div>
</div>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"
[disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box" *ngIf="cipher && cipher.hasAttachments">
<div class="box-header" id="attachmentsTitle">
{{ "attachments" | i18n }}
</div>
<div class="box-content no-hover">
<div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments">
<div class="row-main">
{{ a.fileName }}
</div>
<small class="row-sub-label">{{ a.sizeName }}</small>
<div class="action-buttons no-pad">
<button
class="row-btn btn"
type="button"
appStopClick
appBlurClick
appA11yTitle="{{ 'delete' | i18n }}"
(click)="delete(a)"
#deleteBtn
[appApiAction]="deletePromises[a.id]"
[disabled]="deleteBtn.loading"
>
<i
class="fa fa-trash-o fa-lg fa-fw"
[hidden]="deleteBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{ "newAttachment" | i18n }}
</div>
<div class="box-content no-hover">
<div class="box-content-row">
<label for="file">{{ "file" | i18n }}</label>
<input type="file" id="file" name="file" required />
</div>
</div>
<div class="box-footer">
{{ "maxFileSize" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@@ -1,25 +1,38 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/components/attachments.component';
import { AttachmentsComponent as BaseAttachmentsComponent } from "jslib-angular/components/attachments.component";
@Component({
selector: 'app-vault-attachments',
templateUrl: 'attachments.component.html',
selector: "app-vault-attachments",
templateUrl: "attachments.component.html",
})
export class AttachmentsComponent extends BaseAttachmentsComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, logService: LogService,
stateService: StateService) {
super(cipherService, i18nService, cryptoService, platformUtilsService,
apiService, window, logService, stateService);
}
constructor(
cipherService: CipherService,
i18nService: I18nService,
cryptoService: CryptoService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
stateService: StateService
) {
super(
cipherService,
i18nService,
cryptoService,
platformUtilsService,
apiService,
window,
logService,
stateService
);
}
}

View File

@@ -1,39 +1,61 @@
<div class="content">
<cdk-virtual-scroll-viewport itemSize="42" minBufferPx="400" maxBufferPx="600" *ngIf="ciphers.length">
<div class="list">
<a *cdkVirtualFor="let c of ciphers; trackBy: trackByFn" appStopClick (click)="selectCipher(c)"
(contextmenu)="rightClickCipher(c)" href="#" title="{{'viewItem' | i18n}}"
[ngClass]="{'active': c.id === activeCipherId}" class="flex-list-item virtual-scroll-item">
<app-vault-icon [cipher]="c"></app-vault-icon>
<div class="flex-cipher-list-item">
<span class="text">
{{c.name}}
<ng-container *ngIf="c.organizationId">
<i class="fa fa-cube text-muted" title="{{'shared' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'shared' | i18n}}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i class="fa fa-paperclip text-muted" title="{{'attachments' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'attachments' | i18n}}</span>
</ng-container>
</span>
<span *ngIf="c.subTitle" class="detail">{{c.subTitle}}</span>
</div>
</a>
<cdk-virtual-scroll-viewport
itemSize="42"
minBufferPx="400"
maxBufferPx="600"
*ngIf="ciphers.length"
>
<div class="list">
<a
*cdkVirtualFor="let c of ciphers; trackBy: trackByFn"
appStopClick
(click)="selectCipher(c)"
(contextmenu)="rightClickCipher(c)"
href="#"
title="{{ 'viewItem' | i18n }}"
[ngClass]="{ active: c.id === activeCipherId }"
class="flex-list-item virtual-scroll-item"
>
<app-vault-icon [cipher]="c"></app-vault-icon>
<div class="flex-cipher-list-item">
<span class="text">
{{ c.name }}
<ng-container *ngIf="c.organizationId">
<i class="fa fa-cube text-muted" title="{{ 'shared' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="c.hasAttachments">
<i
class="fa fa-paperclip text-muted"
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
</span>
<span *ngIf="c.subTitle" class="detail">{{ c.subTitle }}</span>
</div>
</cdk-virtual-scroll-viewport>
<div class="no-items" *ngIf="!ciphers.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{'noItemsInList' | i18n}}</p>
<button (click)="addCipher()" class="btn block primary link">{{'addItem' | i18n}}</button>
</ng-container>
</a>
</div>
</cdk-virtual-scroll-viewport>
<div class="no-items" *ngIf="!ciphers.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{ "noItemsInList" | i18n }}</p>
<button (click)="addCipher()" class="btn block primary link">{{ "addItem" | i18n }}</button>
</ng-container>
</div>
</div>
<div class="footer">
<button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()"
class="block primary" appA11yTitle="{{'addItem' | i18n}}" [disabled]="deleted">
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
<button
appBlurClick
(click)="addCipher()"
(contextmenu)="addCipherOptions()"
class="block primary"
appA11yTitle="{{ 'addItem' | i18n }}"
[disabled]="deleted"
>
<i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button>
</div>

View File

@@ -1,28 +1,27 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { CiphersComponent as BaseCiphersComponent } from "jslib-angular/components/ciphers.component";
import { SearchService } from "jslib-common/abstractions/search.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { SearchBarService } from '../layout/search/search-bar.service';
import { SearchBarService } from "../layout/search/search-bar.service";
@Component({
selector: 'app-vault-ciphers',
templateUrl: 'ciphers.component.html',
selector: "app-vault-ciphers",
templateUrl: "ciphers.component.html",
})
export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService);
constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService);
searchBarService.searchText.subscribe((searchText) => {
this.searchText = searchText;
this.search(200);
});
}
searchBarService.searchText.subscribe(searchText => {
this.searchText = searchText;
this.search(200);
});
}
trackByFn(index: number, c: CipherView) {
return c.id;
}
trackByFn(index: number, c: CipherView) {
return c.id;
}
}

View File

@@ -1,32 +1,48 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="collectionsTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="collectionsTitle">
{{'collections' | i18n}}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index" appBoxRow>
<label for="collection_{{i}}">{{c.name}}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
name="Collection[{{i}}].Checked">
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="collectionsTitle">
{{ "collections" | i18n }}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div
class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index"
appBoxRow
>
<label for="collection_{{ i }}">{{ c.name }}</label>
<input
id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"
[disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@@ -1,21 +1,25 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { CollectionsComponent as BaseCollectionsComponent } from 'jslib-angular/components/collections.component';
import { CollectionsComponent as BaseCollectionsComponent } from "jslib-angular/components/collections.component";
@Component({
selector: 'app-vault-collections',
templateUrl: 'collections.component.html',
selector: "app-vault-collections",
templateUrl: "collections.component.html",
})
export class CollectionsComponent extends BaseCollectionsComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
logService: LogService) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
}
constructor(
cipherService: CipherService,
i18nService: I18nService,
collectionService: CollectionService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
}
}

View File

@@ -1,36 +1,45 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="exportTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [formGroup]="exportForm">
<div class="modal-body">
<app-callout type="warning" title="{{'vaultExportDisabled' | i18n}}" *ngIf="disabledByPolicy">
{{'personalVaultExportPolicyInEffect' | i18n}}
</app-callout>
<div class="box">
<div class="box-header" id="exportTitle">
{{'exportVault' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="format">{{'fileFormat' | i18n}}</label>
<select class="form-control" id="format" name="Format" formControlName="format">
<option *ngFor="let f of formatOptions" [value]="f.value">{{f.name}}</option>
</select>
</div>
<app-verify-master-password ngDefaultControl formControlName="secret" name="secret">
</app-verify-master-password>
</div>
<div class="box-footer">
<p>{{'confirmIdentity' | i18n}}</p>
</div>
</div>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [formGroup]="exportForm">
<div class="modal-body">
<app-callout
type="warning"
title="{{ 'vaultExportDisabled' | i18n }}"
*ngIf="disabledByPolicy"
>
{{ "personalVaultExportPolicyInEffect" | i18n }}
</app-callout>
<div class="box">
<div class="box-header" id="exportTitle">
{{ "exportVault" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="format">{{ "fileFormat" | i18n }}</label>
<select class="form-control" id="format" name="Format" formControlName="format">
<option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option>
</select>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'submit' | i18n}}"
[disabled]="disabledByPolicy">
<i class="fa fa-download fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
<app-verify-master-password ngDefaultControl formControlName="secret" name="secret">
</app-verify-master-password>
</div>
<div class="box-footer">
<p>{{ "confirmIdentity" | i18n }}</p>
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'submit' | i18n }}"
[disabled]="disabledByPolicy"
>
<i class="fa fa-download fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@@ -1,57 +1,78 @@
import {
Component,
OnInit,
} from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import * as os from 'os';
import * as os from "os";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { ExportService } from 'jslib-common/abstractions/export.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { ExportComponent as BaseExportComponent } from 'jslib-angular/components/export.component';
import { ExportComponent as BaseExportComponent } from "jslib-angular/components/export.component";
const BroadcasterSubscriptionId = 'ExportComponent';
const BroadcasterSubscriptionId = "ExportComponent";
@Component({
selector: 'app-export',
templateUrl: 'export.component.html',
selector: "app-export",
templateUrl: "export.component.html",
})
export class ExportComponent extends BaseExportComponent implements OnInit {
constructor(cryptoService: CryptoService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, exportService: ExportService,
eventService: EventService, policyService: PolicyService,
userVerificationService: UserVerificationService, fb: FormBuilder,
private broadcasterService: BroadcasterService, logService: LogService) {
super(cryptoService, i18nService, platformUtilsService, exportService, eventService,
policyService, window, logService, userVerificationService, fb);
}
constructor(
cryptoService: CryptoService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
exportService: ExportService,
eventService: EventService,
policyService: PolicyService,
userVerificationService: UserVerificationService,
fb: FormBuilder,
private broadcasterService: BroadcasterService,
logService: LogService
) {
super(
cryptoService,
i18nService,
platformUtilsService,
exportService,
eventService,
policyService,
window,
logService,
userVerificationService,
fb
);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
this.i18nService.t('encExportKeyWarningDesc') +
os.EOL + os.EOL +
this.i18nService.t('encExportAccountWarningDesc'),
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
this.i18nService.t('cancel'), 'warning',
true);
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t('exportWarningDesc'),
this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'),
this.i18nService.t('cancel'), 'warning');
}
async warningDialog() {
if (this.encryptedFormat) {
return await this.platformUtilsService.showDialog(
this.i18nService.t("encExportKeyWarningDesc") +
os.EOL +
os.EOL +
this.i18nService.t("encExportAccountWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning",
true
);
} else {
return await this.platformUtilsService.showDialog(
this.i18nService.t("exportWarningDesc"),
this.i18nService.t("confirmVaultExport"),
this.i18nService.t("exportVault"),
this.i18nService.t("cancel"),
"warning"
);
}
}
}

View File

@@ -1,37 +1,66 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="folderAddEditTitle">
<div class="modal-dialog modal-sm" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="folderAddEditTitle">
{{title}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="folder.name"
[appAutofocus]="!editMode">
</div>
</div>
</div>
<div class="modal-dialog modal-sm" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="folderAddEditTitle">
{{ title }}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
name="Name"
[(ngModel)]="folder.name"
[appAutofocus]="!editMode"
/>
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"
[disabled]="form.loading">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
<div class="right">
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
aria-hidden="true"></i>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
<div class="right">
<button
#deleteBtn
appBlurClick
type="button"
(click)="delete()"
class="danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[appApiAction]="deletePromise"
>
<i
class="fa fa-trash-o fa-lg fa-fw"
[hidden]="deleteBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,21 +1,23 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
FolderAddEditComponent as BaseFolderAddEditComponent,
} from 'jslib-angular/components/folder-add-edit.component';
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "jslib-angular/components/folder-add-edit.component";
@Component({
selector: 'app-folder-add-edit',
templateUrl: 'folder-add-edit.component.html',
selector: "app-folder-add-edit",
templateUrl: "folder-add-edit.component.html",
})
export class FolderAddEditComponent extends BaseFolderAddEditComponent {
constructor(folderService: FolderService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, logService: LogService) {
super(folderService, i18nService, platformUtilsService, logService);
}
constructor(
folderService: FolderService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(folderService, i18nService, platformUtilsService, logService);
}
}

View File

@@ -1,104 +1,146 @@
<div class="mac-bar"></div>
<div class="content">
<div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedAll}">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allItems' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedFavorites}">
<a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{'favorites' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedTrash}">
<a href="#" appStopClick appBlurClick (click)="selectTrash()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{'trash' | i18n}}
</a>
</li>
</ul>
<h2>{{'types' | i18n}}</h2>
<ul>
<li [ngClass]="{active: selectedType === cipherType.Login}">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
<i class="fa fa-fw fa-globe" aria-hidden="true"></i>&nbsp;{{'typeLogin' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === cipherType.Card}">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Card)">
<i class="fa fa-fw fa-credit-card" aria-hidden="true"></i>&nbsp;{{'typeCard' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === cipherType.Identity}">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Identity)">
<i class="fa fa-fw fa-id-card-o" aria-hidden="true"></i>&nbsp;{{'typeIdentity' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedType === cipherType.SecureNote}">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.SecureNote)">
<i class="fa fa-fw fa-sticky-note-o" aria-hidden="true"></i>&nbsp;{{'typeSecureNote' | i18n}}
</a>
</li>
</ul>
<p *ngIf="!loaded" class="text-muted">{{'loading' | i18n}}</p>
<ng-container *ngIf="loaded">
<div class="heading">
<h2>{{'folders' | i18n}}</h2>
<button appBlurClick (click)="addFolder()" appA11yTitle="{{'addFolder' | i18n}}">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
</button>
</div>
<ul>
<ng-template #recursiveFolders let-folders>
<li *ngFor="let f of folders"
[ngClass]="{active: selectedFolder && f.node.id === selectedFolderId}">
<a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)">
<i *ngIf="f.children.length" class="fa-fw fa" title="{{'toggleCollapse' | i18n}}" aria-hidden="true"
[ngClass]="{'fa-caret-right': isCollapsed(f.node), 'fa-caret-down': !isCollapsed(f.node)}"
(click)="collapse(f.node)" appStopProp></i>
<i *ngIf="f.children.length === 0" class="fa-fw fa fa-folder-o" aria-hidden="true"></i>
&nbsp;{{f.node.name}}
<span appStopProp appStopClick (click)="editFolder(f.node)" role="button"
appA11yTitle="{{'editFolder' | i18n}}" *ngIf="f.node.id">
<i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
</span>
</a>
<ul class="fa-ul" *ngIf="f.children.length && !isCollapsed(f.node)">
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: f.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: nestedFolders }"></ng-container>
<div class="inner-content">
<h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedFavorites }">
<a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedTrash }">
<a href="#" appStopClick appBlurClick (click)="selectTrash()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</a>
</li>
</ul>
<h2>{{ "types" | i18n }}</h2>
<ul>
<li [ngClass]="{ active: selectedType === cipherType.Login }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
<i class="fa fa-fw fa-globe" aria-hidden="true"></i>&nbsp;{{ "typeLogin" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.Card }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Card)">
<i class="fa fa-fw fa-credit-card" aria-hidden="true"></i>&nbsp;{{ "typeCard" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.Identity }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Identity)">
<i class="fa fa-fw fa-id-card-o" aria-hidden="true"></i>&nbsp;{{ "typeIdentity" | i18n }}
</a>
</li>
<li [ngClass]="{ active: selectedType === cipherType.SecureNote }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.SecureNote)">
<i class="fa fa-fw fa-sticky-note-o" aria-hidden="true"></i>&nbsp;{{
"typeSecureNote" | i18n
}}
</a>
</li>
</ul>
<p *ngIf="!loaded" class="text-muted">{{ "loading" | i18n }}</p>
<ng-container *ngIf="loaded">
<div class="heading">
<h2>{{ "folders" | i18n }}</h2>
<button appBlurClick (click)="addFolder()" appA11yTitle="{{ 'addFolder' | i18n }}">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
</button>
</div>
<ul>
<ng-template #recursiveFolders let-folders>
<li
*ngFor="let f of folders"
[ngClass]="{ active: selectedFolder && f.node.id === selectedFolderId }"
>
<a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)">
<i
*ngIf="f.children.length"
class="fa-fw fa"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'fa-caret-right': isCollapsed(f.node),
'fa-caret-down': !isCollapsed(f.node)
}"
(click)="collapse(f.node)"
appStopProp
></i>
<i
*ngIf="f.children.length === 0"
class="fa-fw fa fa-folder-o"
aria-hidden="true"
></i>
&nbsp;{{ f.node.name }}
<span
appStopProp
appStopClick
(click)="editFolder(f.node)"
role="button"
appA11yTitle="{{ 'editFolder' | i18n }}"
*ngIf="f.node.id"
>
<i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
</span>
</a>
<ul class="fa-ul" *ngIf="f.children.length && !isCollapsed(f.node)">
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }"
>
</ng-container>
</ul>
<div *ngIf="collections && collections.length">
<h2>{{'collections' | i18n}}</h2>
<ul>
<ng-template #recursiveCollections let-collections>
<li *ngFor="let c of collections" [ngClass]="{active: c.node.id === selectedCollectionId}">
<a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)">
<i *ngIf="c.children.length" class="fa-fw fa" title="{{'toggleCollapse' | i18n}}" aria-hidden="true"
[ngClass]="{'fa-caret-right': isCollapsed(c.node), 'fa-caret-down': !isCollapsed(c.node)}"
(click)="collapse(c.node)" appStopProp></i>
<i *ngIf="c.children.length === 0" class="fa-fw fa fa-cube" aria-hidden="true"></i>
&nbsp;{{c.node.name}}
</a>
<ul class="fa-ul" *ngIf="c.children.length && !isCollapsed(c.node)">
<ng-container
*ngTemplateOutlet="recursiveCollections; context:{ $implicit: c.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveCollections; context:{ $implicit: nestedCollections }">
</ng-container>
</ul>
</div>
</ng-container>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
></ng-container>
</ul>
<div *ngIf="collections && collections.length">
<h2>{{ "collections" | i18n }}</h2>
<ul>
<ng-template #recursiveCollections let-collections>
<li
*ngFor="let c of collections"
[ngClass]="{ active: c.node.id === selectedCollectionId }"
>
<a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)">
<i
*ngIf="c.children.length"
class="fa-fw fa"
title="{{ 'toggleCollapse' | i18n }}"
aria-hidden="true"
[ngClass]="{
'fa-caret-right': isCollapsed(c.node),
'fa-caret-down': !isCollapsed(c.node)
}"
(click)="collapse(c.node)"
appStopProp
></i>
<i *ngIf="c.children.length === 0" class="fa-fw fa fa-cube" aria-hidden="true"></i>
&nbsp;{{ c.node.name }}
</a>
<ul class="fa-ul" *ngIf="c.children.length && !isCollapsed(c.node)">
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
>
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
>
</ng-container>
</ul>
</div>
</ng-container>
</div>
<div class="footer">
<app-nav class="nav"></app-nav>
</div>
</div>

View File

@@ -1,18 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/components/groupings.component';
import { GroupingsComponent as BaseGroupingsComponent } from "jslib-angular/components/groupings.component";
@Component({
selector: 'app-vault-groupings',
templateUrl: 'groupings.component.html',
selector: "app-vault-groupings",
templateUrl: "groupings.component.html",
})
export class GroupingsComponent extends BaseGroupingsComponent {
constructor(collectionService: CollectionService, folderService: FolderService,
stateService: StateService) {
super(collectionService, folderService, stateService);
}
constructor(
collectionService: CollectionService,
folderService: FolderService,
stateService: StateService
) {
super(collectionService, folderService, stateService);
}
}

View File

@@ -1,40 +1,54 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="passwordGenHistoryTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="passwordGenHistoryTitle">
{{'passwordHistory' | i18n}}
</div>
<div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<div class="password-wrapper monospaced" appSelectCopy
[innerHTML]="h.password | colorPassword"></div>
<span class="detail">{{h.date | date:'medium'}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
(click)="copy(h.password)" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="!history.length">
{{'noPasswordsInList' | i18n}}
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="passwordGenHistoryTitle">
{{ "passwordHistory" | i18n }}
</div>
<div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<div
class="password-wrapper monospaced"
appSelectCopy
[innerHTML]="h.password | colorPassword"
></div>
<span class="detail">{{ h.date | date: "medium" }}</span>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(h.password)"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="right">
<button appBlurClick type="button" (click)="clear()" class="danger"
appA11yTitle="{{'clear' | i18n}}">
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
</div>
<div class="box-content-row" *ngIf="!history.length">
{{ "noPasswordsInList" | i18n }}
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="clear()"
class="danger"
appA11yTitle="{{ 'clear' | i18n }}"
>
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,20 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
} from 'jslib-angular/components/password-generator-history.component';
import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component";
@Component({
selector: 'app-password-generator-history',
templateUrl: 'password-generator-history.component.html',
selector: "app-password-generator-history",
templateUrl: "password-generator-history.component.html",
})
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
constructor(
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
}

View File

@@ -1,130 +1,230 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'generatePassword' | i18n}}">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-body">
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{'passwordGeneratorPolicyInEffect' | i18n}}
</app-callout>
<div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</div>
<div class="box">
<div class="box-content condensed">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()">
<i class="fa fa-fw fa-refresh" aria-hidden="true"></i> {{'regeneratePassword' | i18n}}
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="copy()">
<i class="fa fa-fw fa-clone" aria-hidden="true"></i> {{'copyPassword' | i18n}}
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleOptions()" appA11yTitle="{{'toggleVisibility' | i18n}}">
<i class="fa fa-plus-square-o" aria-hidden="true" [hidden]="showOptions"></i>
<i class="fa fa-minus-square-o" aria-hidden="true" [hidden]="!showOptions"></i>
{{'options' | i18n}}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio">
<label class="sr-only radio-header">{{'type' | i18n}}</label>
<div class="radio-group text-default" appBoxRow name="PassTypeOptions"
*ngFor="let o of passTypeOptions">
<input type="radio" class="radio" [(ngModel)]="options.type" name="Type_{{o.value}}"
id="type_{{o.value}}" [value]="o.value" (change)="saveOptions()"
[checked]="options.type === o.value">
<label class="unstyled" for="type_{{o.value}}">
{{o.name}}
</label>
</div>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions" *ngIf="options.type === 'passphrase'">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{'numWords' | i18n}}</label>
<input id="num-words" type="number" min="3" max="20" (blur)="saveOptions()"
[(ngModel)]="options.numWords">
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{'wordSeparator' | i18n}}</label>
<input id="word-separator" type="text" maxlength="1" (input)="saveOptions()"
[(ngModel)]="options.wordSeparator">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{'capitalize' | i18n}}</label>
<input id="capitalize" type="checkbox" (change)="saveOptions()"
[(ngModel)]="options.capitalize" [disabled]="enforcedPolicyOptions?.capitalize">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{'includeNumber' | i18n}}</label>
<input id="include-number" type="checkbox" (change)="saveOptions()"
[(ngModel)]="options.includeNumber" [disabled]="enforcedPolicyOptions?.includeNumber">
</div>
</div>
</div>
<ng-container *ngIf="options.type === 'password'">
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{'length' | i18n}}</label>
<input id="length" type="number" min="5" max="128" [(ngModel)]="options.length"
(blur)="saveOptions()">
<input id="lengthRange" type="range" min="5" max="128" step="1"
[(ngModel)]="options.length" (change)="sliderChanged()" (input)="sliderInput()">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input id="uppercase" type="checkbox" (change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useUppercase" [(ngModel)]="options.uppercase">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input id="lowercase" type="checkbox" (change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useLowercase" [(ngModel)]="options.lowercase">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input id="numbers" type="checkbox" (change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useNumbers" [(ngModel)]="options.number">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input id="special" type="checkbox" (change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useSpecial" [(ngModel)]="options.special">
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{'minNumbers' | i18n}}</label>
<input id="min-number" type="number" min="0" max="9" (blur)="saveOptions()"
[(ngModel)]="options.minNumber">
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{'minSpecial' | i18n}}</label>
<input id="min-special" type="number" min="0" max="9" (blur)="saveOptions()"
[(ngModel)]="options.minSpecial">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{'ambiguous' | i18n}}</label>
<input id="ambiguous" type="checkbox" (change)="saveOptions()"
[(ngModel)]="avoidAmbiguous">
</div>
</div>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button type="button" class="primary" appBlurClick *ngIf="showSelect" (click)="select()"
appA11yTitle="{{'select' | i18n}}">
<i class="fa fa-lg fa-fw fa-check" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{(showSelect ? 'cancel' : 'close') | i18n}}</button>
</div>
<div
class="modal fade"
role="dialog"
aria-modal="true"
attr.aria-label="{{ 'generatePassword' | i18n }}"
>
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-body">
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</div>
<div class="box">
<div class="box-content condensed">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()">
<i class="fa fa-fw fa-refresh" aria-hidden="true"></i>
{{ "regeneratePassword" | i18n }}
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="copy()">
<i class="fa fa-fw fa-clone" aria-hidden="true"></i> {{ "copyPassword" | i18n }}
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleOptions()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
>
<i class="fa fa-plus-square-o" aria-hidden="true" [hidden]="showOptions"></i>
<i class="fa fa-minus-square-o" aria-hidden="true" [hidden]="!showOptions"></i>
{{ "options" | i18n }}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio">
<label class="sr-only radio-header">{{ "type" | i18n }}</label>
<div
class="radio-group text-default"
appBoxRow
name="PassTypeOptions"
*ngFor="let o of passTypeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="options.type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="saveOptions()"
[checked]="options.type === o.value"
/>
<label class="unstyled" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions" *ngIf="options.type === 'passphrase'">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
type="number"
min="3"
max="20"
(blur)="saveOptions()"
[(ngModel)]="options.numWords"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
type="text"
maxlength="1"
(input)="saveOptions()"
[(ngModel)]="options.wordSeparator"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="capitalize"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.capitalize"
[disabled]="enforcedPolicyOptions?.capitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="include-number"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.includeNumber"
[disabled]="enforcedPolicyOptions?.includeNumber"
/>
</div>
</div>
</div>
<ng-container *ngIf="options.type === 'password'">
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
type="number"
min="5"
max="128"
[(ngModel)]="options.length"
(blur)="saveOptions()"
/>
<input
id="lengthRange"
type="range"
min="5"
max="128"
step="1"
[(ngModel)]="options.length"
(change)="sliderChanged()"
(input)="sliderInput()"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input
id="uppercase"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useUppercase"
[(ngModel)]="options.uppercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input
id="lowercase"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useLowercase"
[(ngModel)]="options.lowercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input
id="numbers"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useNumbers"
[(ngModel)]="options.number"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input
id="special"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useSpecial"
[(ngModel)]="options.special"
/>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minNumber"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minSpecial"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{ "ambiguous" | i18n }}</label>
<input
id="ambiguous"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="avoidAmbiguous"
/>
</div>
</div>
</div>
</ng-container>
</div>
<div class="modal-footer">
<button
type="button"
class="primary"
appBlurClick
*ngIf="showSelect"
(click)="select()"
appA11yTitle="{{ 'select' | i18n }}"
>
<i class="fa fa-lg fa-fw fa-check" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">
{{ (showSelect ? "cancel" : "close") | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@@ -1,20 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
PasswordGeneratorComponent as BasePasswordGeneratorComponent,
} from 'jslib-angular/components/password-generator.component';
import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
@Component({
selector: 'app-password-generator',
templateUrl: 'password-generator.component.html',
selector: "app-password-generator",
templateUrl: "password-generator.component.html",
})
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
constructor(
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
}

View File

@@ -1,35 +1,41 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="passwordHistoryTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="passwordHistoryTitle">
{{'passwordHistory' | i18n}}
</div>
<div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<span class="text monospaced">
{{h.password}}
</span>
<span class="detail">{{h.lastUsedDate | date:'medium'}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
(click)="copy(h.password)" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="!history.length">
{{'noPasswordsInList' | i18n}}
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header" id="passwordHistoryTitle">
{{ "passwordHistory" | i18n }}
</div>
<div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<span class="text monospaced">
{{ h.password }}
</span>
<span class="detail">{{ h.lastUsedDate | date: "medium" }}</span>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(h.password)"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
<div class="box-content-row" *ngIf="!history.length">
{{ "noPasswordsInList" | i18n }}
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div>
</div>
</div>
</div>

View File

@@ -1,20 +1,21 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
PasswordHistoryComponent as BasePasswordHistoryComponent,
} from 'jslib-angular/components/password-history.component';
import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "jslib-angular/components/password-history.component";
@Component({
selector: 'app-password-history',
templateUrl: 'password-history.component.html',
selector: "app-password-history",
templateUrl: "password-history.component.html",
})
export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
constructor(cipherService: CipherService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService) {
super(cipherService, platformUtilsService, i18nService, window);
}
constructor(
cipherService: CipherService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(cipherService, platformUtilsService, i18nService, window);
}
}

View File

@@ -1,54 +1,75 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="moveToOrgTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="moveToOrgTitle">
{{'moveToOrganization' | i18n}}
</div>
<div class="box-content" *ngIf="!organizations || !organizations.length">
<div class="box-content-row">
{{'noOrganizationsList' | i18n}}
</div>
</div>
<div class="box-content" *ngIf="organizations && organizations.length">
<div class="box-content-row" appBoxRow>
<label for="organization">{{'organization' | i18n}}</label>
<select id="organization" name="OrganizationId" [(ngModel)]="organizationId"
(change)="filterCollections()">
<option *ngFor="let o of organizations" [ngValue]="o.id">{{o.name}}</option>
</select>
</div>
</div>
<div class="box-footer">
{{'moveToOrgDesc' | i18n}}
</div>
</div>
<div class="box" *ngIf="organizations && organizations.length">
<div class="box-header">
{{'collections' | i18n}}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index" appBoxRow>
<label for="collection_{{i}}">{{c.name}}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked"
name="Collection[{{i}}].Checked">
</div>
</div>
</div>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-body">
<div class="box">
<div class="box-header" id="moveToOrgTitle">
{{ "moveToOrganization" | i18n }}
</div>
<div class="box-content" *ngIf="!organizations || !organizations.length">
<div class="box-content-row">
{{ "noOrganizationsList" | i18n }}
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}"
[disabled]="form.loading || !canSave" *ngIf="organizations && organizations.length">
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
<div class="box-content" *ngIf="organizations && organizations.length">
<div class="box-content-row" appBoxRow>
<label for="organization">{{ "organization" | i18n }}</label>
<select
id="organization"
name="OrganizationId"
[(ngModel)]="organizationId"
(change)="filterCollections()"
>
<option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option>
</select>
</div>
</form>
</div>
</div>
<div class="box-footer">
{{ "moveToOrgDesc" | i18n }}
</div>
</div>
<div class="box" *ngIf="organizations && organizations.length">
<div class="box-header">
{{ "collections" | i18n }}
</div>
<div class="box-content" *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<div class="box-content" *ngIf="collections && collections.length">
<div
class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index"
appBoxRow
>
<label for="collection_{{ i }}">{{ c.name }}</label>
<input
id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button
appBlurClick
type="submit"
class="primary"
appA11yTitle="{{ 'save' | i18n }}"
[disabled]="form.loading || !canSave"
*ngIf="organizations && organizations.length"
>
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading" aria-hidden="true"></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button>
<button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
</div>
</form>
</div>
</div>

View File

@@ -1,23 +1,34 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/share.component';
import { ShareComponent as BaseShareComponent } from "jslib-angular/components/share.component";
@Component({
selector: 'app-vault-share',
templateUrl: 'share.component.html',
selector: "app-vault-share",
templateUrl: "share.component.html",
})
export class ShareComponent extends BaseShareComponent {
constructor(cipherService: CipherService, i18nService: I18nService,
collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
logService: LogService, organizationService: OrganizationService) {
super(collectionService, platformUtilsService, i18nService, cipherService,
logService, organizationService);
}
constructor(
cipherService: CipherService,
i18nService: I18nService,
collectionService: CollectionService,
platformUtilsService: PlatformUtilsService,
logService: LogService,
organizationService: OrganizationService
) {
super(
collectionService,
platformUtilsService,
i18nService,
cipherService,
logService,
organizationService
);
}
}

View File

@@ -1,36 +1,69 @@
<div id="vault" class="vault" attr.aria-hidden="{{showingModal}}">
<app-vault-groupings id="groupings" class="groupings" (onAllClicked)="clearGroupingFilters()" (onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)" (onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()" (onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)" (onTrashClicked)="filterDeleted()">
</app-vault-groupings>
<app-vault-ciphers id="items" class="items" [activeCipherId]="cipherId" (onCipherClicked)="viewCipher($event)"
(onCipherRightClicked)="viewCipherMenu($event)" (onAddCipher)="addCipher($event)"
(onAddCipherOptions)="addCipherOptions()">
</app-vault-ciphers>
<app-vault-view id="details" class="details" *ngIf="cipherId && action === 'view'" [cipherId]="cipherId"
(onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)" (onEditCipher)="editCipherWithoutPasswordPrompt($event)"
(onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" (onRestoredCipher)="restoredCipher($event)"
(onDeletedCipher)="deletedCipher($event)">
</app-vault-view>
<app-vault-add-edit id="addEdit" class="details" *ngIf="action === 'add' || action === 'edit' || action === 'clone'"
[cloneMode]="action === 'clone'"
[folderId]="action === 'add' && folderId !== 'none' ? folderId : null"
[organizationId]="action === 'add' ? addOrganizationId : null"
[collectionIds]="action === 'add' ? addCollectionIds : null"
[type]="action === 'add' ? (addType ? addType : type) : null" [cipherId]="(action === 'edit' || action === 'clone') ? cipherId : null"
(onSavedCipher)="savedCipher($event)" (onDeletedCipher)="deletedCipher($event)"
(onEditAttachments)="editCipherAttachments($event)" (onCancelled)="cancelledAddEdit($event)"
(onShareCipher)="shareCipher($event)" (onEditCollections)="cipherCollections($event)"
(onGeneratePassword)="openPasswordGenerator(true)">
</app-vault-add-edit>
<div id="logo" class="logo" *ngIf="action !== 'add' && action !== 'edit' && action !== 'view' && action !== 'clone'">
<div class="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
<div id="vault" class="vault" attr.aria-hidden="{{ showingModal }}">
<app-vault-groupings
id="groupings"
class="groupings"
(onAllClicked)="clearGroupingFilters()"
(onFavoritesClicked)="filterFavorites()"
(onCipherTypeClicked)="filterCipherType($event)"
(onFolderClicked)="filterFolder($event.id)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event.id)"
(onCollectionClicked)="filterCollection($event.id)"
(onTrashClicked)="filterDeleted()"
>
</app-vault-groupings>
<app-vault-ciphers
id="items"
class="items"
[activeCipherId]="cipherId"
(onCipherClicked)="viewCipher($event)"
(onCipherRightClicked)="viewCipherMenu($event)"
(onAddCipher)="addCipher($event)"
(onAddCipherOptions)="addCipherOptions()"
>
</app-vault-ciphers>
<app-vault-view
id="details"
class="details"
*ngIf="cipherId && action === 'view'"
[cipherId]="cipherId"
(onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)"
(onEditCipher)="editCipherWithoutPasswordPrompt($event)"
(onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)"
(onRestoredCipher)="restoredCipher($event)"
(onDeletedCipher)="deletedCipher($event)"
>
</app-vault-view>
<app-vault-add-edit
id="addEdit"
class="details"
*ngIf="action === 'add' || action === 'edit' || action === 'clone'"
[cloneMode]="action === 'clone'"
[folderId]="action === 'add' && folderId !== 'none' ? folderId : null"
[organizationId]="action === 'add' ? addOrganizationId : null"
[collectionIds]="action === 'add' ? addCollectionIds : null"
[type]="action === 'add' ? (addType ? addType : type) : null"
[cipherId]="action === 'edit' || action === 'clone' ? cipherId : null"
(onSavedCipher)="savedCipher($event)"
(onDeletedCipher)="deletedCipher($event)"
(onEditAttachments)="editCipherAttachments($event)"
(onCancelled)="cancelledAddEdit($event)"
(onShareCipher)="shareCipher($event)"
(onEditCollections)="cipherCollections($event)"
(onGeneratePassword)="openPasswordGenerator(true)"
>
</app-vault-add-edit>
<div
id="logo"
class="logo"
*ngIf="action !== 'add' && action !== 'edit' && action !== 'view' && action !== 'clone'"
>
<div class="content">
<div class="inner-content">
<img class="logo-image" alt="Bitwarden" aria-hidden="true" />
</div>
</div>
</div>
</div>
<ng-template #passwordGenerator></ng-template>
<ng-template #attachments></ng-template>

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +1,66 @@
<div class="box" >
<div class="box-header">
{{'customFields' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let field of cipher.fields">
<div class="row-main">
<span class="row-label">{{field.name}}</span>
<div *ngIf="field.type === fieldType.Text">
{{field.value || '&nbsp;'}}
</div>
<div *ngIf="field.type === fieldType.Hidden">
<span *ngIf="field.showValue" class="monospaced show-whitespace">{{field.value}}</span>
<span *ngIf="!field.showValue" class="monospaced">{{field.maskedValue}}</span>
</div>
<div *ngIf="field.type === fieldType.Boolean">
<i class="fa fa-check-square-o" *ngIf="field.value === 'true'" aria-hidden="true"></i>
<i class="fa fa-square-o" *ngIf="field.value !== 'true'" aria-hidden="true"></i>
<span class="sr-only">{{field.value}}</span>
</div>
<div *ngIf="field.type === fieldType.Linked" class="box-content-row-flex">
<div class="icon icon-small">
<i class="fa fa-link" aria-hidden="true" appA11yTitle="{{'linkedValue' | i18n}}"></i>
<span class="sr-only">{{'linkedValue' | i18n}}</span>
</div>
<span>{{cipher.linkedFieldI18nKey(field.linkedId) | i18n}}</span>
</div>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
*ngIf="field.type === fieldType.Hidden && cipher.viewPassword"
(click)="toggleFieldValue(field)" role="button">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !field.showValue, 'fa-eye-slash': field.showValue}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyValue' | i18n}}"
*ngIf="field.value && field.type !== fieldType.Boolean && field.type !== fieldType.Linked &&
!(field.type === fieldType.Hidden && !cipher.viewPassword)"
(click)="copy(field.value, 'value', field.type === fieldType.Hidden ? 'H_Field' : 'Field')"
role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
<div class="box">
<div class="box-header">
{{ "customFields" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let field of cipher.fields">
<div class="row-main">
<span class="row-label">{{ field.name }}</span>
<div *ngIf="field.type === fieldType.Text">
{{ field.value || "&nbsp;" }}
</div>
<div *ngIf="field.type === fieldType.Hidden">
<span *ngIf="field.showValue" class="monospaced show-whitespace">{{ field.value }}</span>
<span *ngIf="!field.showValue" class="monospaced">{{ field.maskedValue }}</span>
</div>
<div *ngIf="field.type === fieldType.Boolean">
<i class="fa fa-check-square-o" *ngIf="field.value === 'true'" aria-hidden="true"></i>
<i class="fa fa-square-o" *ngIf="field.value !== 'true'" aria-hidden="true"></i>
<span class="sr-only">{{ field.value }}</span>
</div>
<div *ngIf="field.type === fieldType.Linked" class="box-content-row-flex">
<div class="icon icon-small">
<i class="fa fa-link" aria-hidden="true" appA11yTitle="{{ 'linkedValue' | i18n }}"></i>
<span class="sr-only">{{ "linkedValue" | i18n }}</span>
</div>
<span>{{ cipher.linkedFieldI18nKey(field.linkedId) | i18n }}</span>
</div>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
*ngIf="field.type === fieldType.Hidden && cipher.viewPassword"
(click)="toggleFieldValue(field)"
role="button"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !field.showValue, 'fa-eye-slash': field.showValue }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyValue' | i18n }}"
*ngIf="
field.value &&
field.type !== fieldType.Boolean &&
field.type !== fieldType.Linked &&
!(field.type === fieldType.Hidden && !cipher.viewPassword)
"
(click)="
copy(field.value, 'value', field.type === fieldType.Hidden ? 'H_Field' : 'Field')
"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
</div>

View File

@@ -1,19 +1,15 @@
import {
Component,
} from '@angular/core';
import { Component } from "@angular/core";
import { EventService } from 'jslib-common/abstractions/event.service';
import { EventService } from "jslib-common/abstractions/event.service";
import {
ViewCustomFieldsComponent as BaseViewCustomFieldsComponent
} from 'jslib-angular/components/view-custom-fields.component';
import { ViewCustomFieldsComponent as BaseViewCustomFieldsComponent } from "jslib-angular/components/view-custom-fields.component";
@Component({
selector: 'app-vault-view-custom-fields',
templateUrl: 'view-custom-fields.component.html',
selector: "app-vault-view-custom-fields",
templateUrl: "view-custom-fields.component.html",
})
export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent {
constructor(eventService: EventService) {
super(eventService);
}
constructor(eventService: EventService) {
super(eventService);
}
}

View File

@@ -1,265 +1,422 @@
<div class="content">
<div class="inner-content" *ngIf="cipher">
<div class="box">
<div class="box-header">
{{'itemInformation' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row">
<span class="row-label">{{'name' | i18n}}</span>
{{cipher.name}}
</div>
<!-- Login -->
<div *ngIf="cipher.login">
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.username">
<div class="row-main">
<span class="row-label draggable" draggable="true"
(dragstart)="setTextDataOnDrag($event, cipher.login.username)">{{'username' | i18n}}</span>
{{cipher.login.username}}
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyUsername' | i18n}}"
(click)="copy(cipher.login.username, 'username', 'Username')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.password">
<div class="row-main">
<span class="row-label draggable" draggable="true"
(dragstart)="setTextDataOnDrag($event, cipher.login.password)">{{'password' | i18n}}</span>
<div *ngIf="!showPassword" class="monospaced">
{{cipher.login.maskedPassword}}</div>
<div *ngIf="showPassword" class="monospaced password-wrapper" appSelectCopy
[innerHTML]="cipher.login.password | colorPassword"></div>
</div>
<div class="action-buttons" *ngIf=cipher.viewPassword>
<button type="button" #checkPasswordBtn class="row-btn btn" appBlurClick
appA11yTitle="{{'checkPassword' | i18n}}" (click)="checkPassword()"
[appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading">
<i class="fa fa-lg fa-check-circle" [hidden]="checkPasswordBtn.loading"
aria-hidden="true"></i>
<i class="fa fa-lg fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"
aria-hidden="true"></i>
</button>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="togglePassword()" role="button">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
(click)="copy(cipher.login.password, 'password', 'Password')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row box-content-row-flex totp" [ngClass]="{'low': totpLow}"
*ngIf="cipher.login.totp && totpCode">
<div class="row-main">
<span class="row-label draggable" draggable="true"
(dragstart)="setTextDataOnDrag($event, totpCode)">{{'verificationCodeTotp' | i18n}}</span>
<span class="totp-code">{{totpCodeFormatted}}</span>
</div>
<span class="totp-countdown">
<span class="totp-sec">{{totpSec}}</span>
<svg>
<g>
<circle class="totp-circle inner" r="12.6" cy="16" cx="16"
[ngStyle]="{'stroke-dashoffset.px': totpDash}"></circle>
<circle class="totp-circle outer" r="14" cy="16" cx="16"></circle>
</g>
</svg>
</span>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyValue' | i18n}}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<!-- Card -->
<div *ngIf="cipher.card">
<div class="box-content-row" *ngIf="cipher.card.cardholderName">
<span class="row-label">{{'cardholderName' | i18n}}</span>
{{cipher.card.cardholderName}}
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number">
<div class="row-main">
<span class="row-label">{{'number' | i18n}}</span>
<span *ngIf="!showCardNumber" class="monospaced">{{cipher.card.maskedNumber}}</span>
<span *ngIf="showCardNumber" class="monospaced">{{cipher.card.number}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="toggleCardNumber()" role="button">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyNumber' | i18n}}"
(click)="copy(cipher.card.number, 'number', 'Card Number')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="cipher.card.brand">
<span class="row-label">{{'brand' | i18n}}</span>
{{cipher.card.brand}}
</div>
<div class="box-content-row" *ngIf="cipher.card.expiration">
<span class="row-label">{{'expiration' | i18n}}</span>
{{cipher.card.expiration}}
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.code">
<div class="row-main">
<span class="row-label">{{'securityCode' | i18n}}</span>
<span *ngIf="!showCardCode" class="monospaced">{{cipher.card.maskedCode}}</span>
<span *ngIf="showCardCode" class="monospaced">{{cipher.card.code}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="toggleCardCode()" role="button">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode}"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copySecurityCode' | i18n}}"
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<!-- Identity -->
<div *ngIf="cipher.identity">
<div class="box-content-row" *ngIf="cipher.identity.fullName">
<span class="row-label">{{'identityName' | i18n}}</span>
{{cipher.identity.fullName}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.username">
<span class="row-label">{{'username' | i18n}}</span>
{{cipher.identity.username}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.company">
<span class="row-label">{{'company' | i18n}}</span>
{{cipher.identity.company}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.ssn">
<span class="row-label">{{'ssn' | i18n}}</span>
{{cipher.identity.ssn}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.passportNumber">
<span class="row-label">{{'passportNumber' | i18n}}</span>
{{cipher.identity.passportNumber}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.licenseNumber">
<span class="row-label">{{'licenseNumber' | i18n}}</span>
{{cipher.identity.licenseNumber}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.email">
<span class="row-label">{{'email' | i18n}}</span>
{{cipher.identity.email}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.phone">
<span class="row-label">{{'phone' | i18n}}</span>
{{cipher.identity.phone}}
</div>
<div class="box-content-row"
*ngIf="cipher.identity.address1 || cipher.identity.city || cipher.identity.country">
<span class="row-label">{{'address' | i18n}}</span>
<div *ngIf="cipher.identity.address1">{{cipher.identity.address1}}</div>
<div *ngIf="cipher.identity.address2">{{cipher.identity.address2}}</div>
<div *ngIf="cipher.identity.address3">{{cipher.identity.address3}}</div>
<div *ngIf="cipher.identity.fullAddressPart2">{{cipher.identity.fullAddressPart2}}</div>
<div *ngIf="cipher.identity.country">{{cipher.identity.country}}</div>
</div>
</div>
</div>
<div class="inner-content" *ngIf="cipher">
<div class="box">
<div class="box-header">
{{ "itemInformation" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row">
<span class="row-label">{{ "name" | i18n }}</span>
{{ cipher.name }}
</div>
<div class="box" *ngIf="cipher.login && cipher.login.hasUris">
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let u of cipher.login.uris; let i = index">
<div class="row-main">
<span class="row-label" *ngIf="!u.isWebsite">{{'uri' | i18n}}</span>
<span class="row-label" *ngIf="u.isWebsite">{{'website' | i18n}}</span>
<span title="{{u.uri}}">{{u.hostOrUri}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'launch' | i18n}}" *ngIf="u.canLaunch"
(click)="launch(u)" role="button">
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
</a>
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyUri' | i18n}}"
(click)="copy(u.uri, u.isWebsite ? 'website' : 'uri', 'URI')" role="button">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<!-- Login -->
<div *ngIf="cipher.login">
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.username">
<div class="row-main">
<span
class="row-label draggable"
draggable="true"
(dragstart)="setTextDataOnDrag($event, cipher.login.username)"
>{{ "username" | i18n }}</span
>
{{ cipher.login.username }}
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy(cipher.login.username, 'username', 'Username')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.password">
<div class="row-main">
<span
class="row-label draggable"
draggable="true"
(dragstart)="setTextDataOnDrag($event, cipher.login.password)"
>{{ "password" | i18n }}</span
>
<div *ngIf="!showPassword" class="monospaced">
{{ cipher.login.maskedPassword }}
</div>
<div
*ngIf="showPassword"
class="monospaced password-wrapper"
appSelectCopy
[innerHTML]="cipher.login.password | colorPassword"
></div>
</div>
<div class="action-buttons" *ngIf="cipher.viewPassword">
<button
type="button"
#checkPasswordBtn
class="row-btn btn"
appBlurClick
appA11yTitle="{{ 'checkPassword' | i18n }}"
(click)="checkPassword()"
[appApiAction]="checkPasswordPromise"
[disabled]="checkPasswordBtn.loading"
>
<i
class="fa fa-lg fa-check-circle"
[hidden]="checkPasswordBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-lg fa-spinner fa-spin"
[hidden]="!checkPasswordBtn.loading"
aria-hidden="true"
></i>
</button>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
role="button"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(cipher.login.password, 'password', 'Password')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div
class="box-content-row box-content-row-flex totp"
[ngClass]="{ low: totpLow }"
*ngIf="cipher.login.totp && totpCode"
>
<div class="row-main">
<span
class="row-label draggable"
draggable="true"
(dragstart)="setTextDataOnDrag($event, totpCode)"
>{{ "verificationCodeTotp" | i18n }}</span
>
<span class="totp-code">{{ totpCodeFormatted }}</span>
</div>
<span class="totp-countdown">
<span class="totp-sec">{{ totpSec }}</span>
<svg>
<g>
<circle
class="totp-circle inner"
r="12.6"
cy="16"
cx="16"
[ngStyle]="{ 'stroke-dashoffset.px': totpDash }"
></circle>
<circle class="totp-circle outer" r="14" cy="16" cx="16"></circle>
</g>
</svg>
</span>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyValue' | i18n }}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.notes">
<div class="box-header">
{{'notes' | i18n}}
<!-- Card -->
<div *ngIf="cipher.card">
<div class="box-content-row" *ngIf="cipher.card.cardholderName">
<span class="row-label">{{ "cardholderName" | i18n }}</span>
{{ cipher.card.cardholderName }}
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number">
<div class="row-main">
<span class="row-label">{{ "number" | i18n }}</span>
<span *ngIf="!showCardNumber" class="monospaced">{{ cipher.card.maskedNumber }}</span>
<span *ngIf="showCardNumber" class="monospaced">{{ cipher.card.number }}</span>
</div>
<div class="box-content">
<div class="box-content-row pre-wrap">{{cipher.notes}}</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardNumber()"
role="button"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyNumber' | i18n }}"
(click)="copy(cipher.card.number, 'number', 'Card Number')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="cipher.card.brand">
<span class="row-label">{{ "brand" | i18n }}</span>
{{ cipher.card.brand }}
</div>
<div class="box-content-row" *ngIf="cipher.card.expiration">
<span class="row-label">{{ "expiration" | i18n }}</span>
{{ cipher.card.expiration }}
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.code">
<div class="row-main">
<span class="row-label">{{ "securityCode" | i18n }}</span>
<span *ngIf="!showCardCode" class="monospaced">{{ cipher.card.maskedCode }}</span>
<span *ngIf="showCardCode" class="monospaced">{{ cipher.card.code }}</span>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleCardCode()"
role="button"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode }"
></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copySecurityCode' | i18n }}"
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
<app-vault-view-custom-fields *ngIf="cipher.hasFields" [cipher]="cipher"
[promptPassword]="promptPassword.bind(this)" [copy]="copy.bind(this)">
</app-vault-view-custom-fields>
<div class="box" *ngIf="cipher.hasAttachments && (canAccessPremium || cipher.organizationId)">
<div class="box-header">
{{'attachments' | i18n}}
</div>
<div class="box-content">
<a class="box-content-row box-content-row-flex text-default"
*ngFor="let attachment of cipher.attachments" href="#" appStopClick appBlurCLick
(click)="downloadAttachment(attachment)">
<span class="row-main">{{attachment.fileName}}</span>
<small class="row-sub-label">{{attachment.sizeName}}</small>
<i class="fa fa-download fa-fw row-sub-icon" *ngIf="!attachment.downloading" aria-hidden="true"></i>
<i class="fa fa-spinner fa-fw fa-spin row-sub-icon" *ngIf="attachment.downloading"
aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box">
<div class="box-footer">
<div>
<b class="font-weight-semibold">{{'dateUpdated' | i18n}}:</b>
{{cipher.revisionDate | date:'medium'}}
</div>
<div *ngIf="cipher.passwordRevisionDisplayDate">
<b class="font-weight-semibold">{{'datePasswordUpdated' | i18n}}:</b>
{{cipher.passwordRevisionDisplayDate | date:'medium'}}
</div>
<div *ngIf="cipher.hasPasswordHistory">
<b class="font-weight-semibold">{{'passwordHistory' | i18n}}:</b>
<a href="#" (click)="viewHistory()" appStopClick role="button"
appA11yTitle="{{'passwordHistory' | i18n}}, {{cipher.passwordHistory.length}}">
<span aria-hidden="true">{{cipher.passwordHistory.length}}</span>
</a>
</div>
<!-- Identity -->
<div *ngIf="cipher.identity">
<div class="box-content-row" *ngIf="cipher.identity.fullName">
<span class="row-label">{{ "identityName" | i18n }}</span>
{{ cipher.identity.fullName }}
</div>
<div class="box-content-row" *ngIf="cipher.identity.username">
<span class="row-label">{{ "username" | i18n }}</span>
{{ cipher.identity.username }}
</div>
<div class="box-content-row" *ngIf="cipher.identity.company">
<span class="row-label">{{ "company" | i18n }}</span>
{{ cipher.identity.company }}
</div>
<div class="box-content-row" *ngIf="cipher.identity.ssn">
<span class="row-label">{{ "ssn" | i18n }}</span>
{{ cipher.identity.ssn }}
</div>
<div class="box-content-row" *ngIf="cipher.identity.passportNumber">
<span class="row-label">{{ "passportNumber" | i18n }}</span>
{{ cipher.identity.passportNumber }}
</div>
<div class="box-content-row" *ngIf="cipher.identity.licenseNumber">
<span class="row-label">{{ "licenseNumber" | i18n }}</span>
{{ cipher.identity.licenseNumber }}
</div>
<div class="box-content-row" *ngIf="cipher.identity.email">
<span class="row-label">{{ "email" | i18n }}</span>
{{ cipher.identity.email }}
</div>
<div class="box-content-row" *ngIf="cipher.identity.phone">
<span class="row-label">{{ "phone" | i18n }}</span>
{{ cipher.identity.phone }}
</div>
<div
class="box-content-row"
*ngIf="cipher.identity.address1 || cipher.identity.city || cipher.identity.country"
>
<span class="row-label">{{ "address" | i18n }}</span>
<div *ngIf="cipher.identity.address1">{{ cipher.identity.address1 }}</div>
<div *ngIf="cipher.identity.address2">{{ cipher.identity.address2 }}</div>
<div *ngIf="cipher.identity.address3">{{ cipher.identity.address3 }}</div>
<div *ngIf="cipher.identity.fullAddressPart2">
{{ cipher.identity.fullAddressPart2 }}
</div>
<div *ngIf="cipher.identity.country">{{ cipher.identity.country }}</div>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.login && cipher.login.hasUris">
<div class="box-content">
<div
class="box-content-row box-content-row-flex"
*ngFor="let u of cipher.login.uris; let i = index"
>
<div class="row-main">
<span class="row-label" *ngIf="!u.isWebsite">{{ "uri" | i18n }}</span>
<span class="row-label" *ngIf="u.isWebsite">{{ "website" | i18n }}</span>
<span title="{{ u.uri }}">{{ u.hostOrUri }}</span>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'launch' | i18n }}"
*ngIf="u.canLaunch"
(click)="launch(u)"
role="button"
>
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
</a>
<a
class="row-btn"
href="#"
appStopClick
appA11yTitle="{{ 'copyUri' | i18n }}"
(click)="copy(u.uri, u.isWebsite ? 'website' : 'uri', 'URI')"
role="button"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.notes">
<div class="box-header">
{{ "notes" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row pre-wrap">{{ cipher.notes }}</div>
</div>
</div>
<app-vault-view-custom-fields
*ngIf="cipher.hasFields"
[cipher]="cipher"
[promptPassword]="promptPassword.bind(this)"
[copy]="copy.bind(this)"
>
</app-vault-view-custom-fields>
<div class="box" *ngIf="cipher.hasAttachments && (canAccessPremium || cipher.organizationId)">
<div class="box-header">
{{ "attachments" | i18n }}
</div>
<div class="box-content">
<a
class="box-content-row box-content-row-flex text-default"
*ngFor="let attachment of cipher.attachments"
href="#"
appStopClick
appBlurCLick
(click)="downloadAttachment(attachment)"
>
<span class="row-main">{{ attachment.fileName }}</span>
<small class="row-sub-label">{{ attachment.sizeName }}</small>
<i
class="fa fa-download fa-fw row-sub-icon"
*ngIf="!attachment.downloading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-fw fa-spin row-sub-icon"
*ngIf="attachment.downloading"
aria-hidden="true"
></i>
</a>
</div>
</div>
<div class="box">
<div class="box-footer">
<div>
<b class="font-weight-semibold">{{ "dateUpdated" | i18n }}:</b>
{{ cipher.revisionDate | date: "medium" }}
</div>
<div *ngIf="cipher.passwordRevisionDisplayDate">
<b class="font-weight-semibold">{{ "datePasswordUpdated" | i18n }}:</b>
{{ cipher.passwordRevisionDisplayDate | date: "medium" }}
</div>
<div *ngIf="cipher.hasPasswordHistory">
<b class="font-weight-semibold">{{ "passwordHistory" | i18n }}:</b>
<a
href="#"
(click)="viewHistory()"
appStopClick
role="button"
appA11yTitle="{{ 'passwordHistory' | i18n }}, {{ cipher.passwordHistory.length }}"
>
<span aria-hidden="true">{{ cipher.passwordHistory.length }}</span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="footer" *ngIf="cipher">
<button appBlurClick class="primary" (click)="edit()" appA11yTitle="{{'edit' | i18n}}" *ngIf="!cipher.isDeleted">
<i class="fa fa-pencil fa-fw fa-lg" aria-hidden="true"></i>
<button
appBlurClick
class="primary"
(click)="edit()"
appA11yTitle="{{ 'edit' | i18n }}"
*ngIf="!cipher.isDeleted"
>
<i class="fa fa-pencil fa-fw fa-lg" aria-hidden="true"></i>
</button>
<button
appBlurClick
class="primary"
(click)="restore()"
appA11yTitle="{{ 'restore' | i18n }}"
*ngIf="cipher.isDeleted"
>
<i class="fa fa-undo fa-fw fa-lg" aria-hidden="true"></i>
</button>
<button
appBlurClick
class="primary"
*ngIf="!cipher?.organizationId && !cipher.isDeleted"
(click)="clone()"
appA11yTitle="{{ 'clone' | i18n }}"
>
<i class="fa fa-files-o fa-fw fa-lg" aria-hidden="true"></i>
</button>
<div class="right">
<button
appBlurClick
type="button"
(click)="delete()"
class="danger"
appA11yTitle="{{ (cipher.isDeleted ? 'permanentlyDelete' : 'delete') | i18n }}"
>
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button appBlurClick class="primary" (click)="restore()" appA11yTitle="{{'restore' | i18n}}"
*ngIf="cipher.isDeleted">
<i class="fa fa-undo fa-fw fa-lg" aria-hidden="true"></i>
</button>
<button appBlurClick class="primary" *ngIf="!cipher?.organizationId && !cipher.isDeleted" (click)="clone()"
appA11yTitle="{{'clone' | i18n}}">
<i class="fa fa-files-o fa-fw fa-lg" aria-hidden="true"></i>
</button>
<div class="right">
<button appBlurClick type="button" (click)="delete()" class="danger"
appA11yTitle="{{(cipher.isDeleted ? 'permanentlyDelete' : 'delete') | i18n}}">
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button>
</div>
</div>
</div>

View File

@@ -1,92 +1,117 @@
import {
ChangeDetectorRef,
Component,
EventEmitter,
NgZone,
OnChanges,
Output,
} from '@angular/core';
ChangeDetectorRef,
Component,
EventEmitter,
NgZone,
OnChanges,
Output,
} from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuditService } from 'jslib-common/abstractions/audit.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { ViewComponent as BaseViewComponent } from 'jslib-angular/components/view.component';
import { ViewComponent as BaseViewComponent } from "jslib-angular/components/view.component";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
const BroadcasterSubscriptionId = 'ViewComponent';
const BroadcasterSubscriptionId = "ViewComponent";
@Component({
selector: 'app-vault-view',
templateUrl: 'view.component.html',
selector: "app-vault-view",
templateUrl: "view.component.html",
})
export class ViewComponent extends BaseViewComponent implements OnChanges {
@Output() onViewCipherPasswordHistory = new EventEmitter<CipherView>();
@Output() onViewCipherPasswordHistory = new EventEmitter<CipherView>();
constructor(cipherService: CipherService, totpService: TotpService,
tokenService: TokenService, i18nService: I18nService,
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
auditService: AuditService, broadcasterService: BroadcasterService,
ngZone: NgZone, changeDetectorRef: ChangeDetectorRef,
eventService: EventService, apiService: ApiService,
private messagingService: MessagingService, passwordRepromptService: PasswordRepromptService,
logService: LogService, stateService: StateService) {
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService,
auditService, window, broadcasterService, ngZone, changeDetectorRef, eventService,
apiService, passwordRepromptService, logService, stateService);
}
ngOnInit() {
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case 'windowHidden':
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
super.ngOnDestroy();
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async ngOnChanges() {
await super.load();
}
viewHistory() {
this.onViewCipherPasswordHistory.emit(this.cipher);
}
async copy(value: string, typeI18nKey: string, aType: string) {
super.copy(value, typeI18nKey, aType);
this.messagingService.send('minimizeOnCopy');
}
onWindowHidden() {
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach(field => {
field.showValue = false;
});
constructor(
cipherService: CipherService,
totpService: TotpService,
tokenService: TokenService,
i18nService: I18nService,
cryptoService: CryptoService,
platformUtilsService: PlatformUtilsService,
auditService: AuditService,
broadcasterService: BroadcasterService,
ngZone: NgZone,
changeDetectorRef: ChangeDetectorRef,
eventService: EventService,
apiService: ApiService,
private messagingService: MessagingService,
passwordRepromptService: PasswordRepromptService,
logService: LogService,
stateService: StateService
) {
super(
cipherService,
totpService,
tokenService,
i18nService,
cryptoService,
platformUtilsService,
auditService,
window,
broadcasterService,
ngZone,
changeDetectorRef,
eventService,
apiService,
passwordRepromptService,
logService,
stateService
);
}
ngOnInit() {
super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
super.ngOnDestroy();
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async ngOnChanges() {
await super.load();
}
viewHistory() {
this.onViewCipherPasswordHistory.emit(this.cipher);
}
async copy(value: string, typeI18nKey: string, aType: string) {
super.copy(value, typeI18nKey, aType);
this.messagingService.send("minimizeOnCopy");
}
onWindowHidden() {
this.showPassword = false;
this.showCardNumber = false;
this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach((field) => {
field.showValue = false;
});
}
}
}

View File

@@ -1,4 +1,4 @@
import { NativeMessagingProxy } from './proxy/native-messaging-proxy';
import { NativeMessagingProxy } from "./proxy/native-messaging-proxy";
// We need to import the other dependencies using `require` since `import` will
// generate `Error: Cannot find module 'electron'`. The cause of this error is
@@ -6,28 +6,30 @@ import { NativeMessagingProxy } from './proxy/native-messaging-proxy';
// which removes the electron module. This flag is needed for stdin/out to work
// properly on Windows.
if (process.argv.some(arg => arg.indexOf('chrome-extension://') !== -1 || arg.indexOf('{') !== -1)) {
if (process.platform === 'darwin') {
// tslint:disable-next-line
const app = require('electron').app;
app.on('ready', () => {
app.dock.hide();
});
}
process.stdout.on('error', e => {
if (e.code === 'EPIPE') {
process.exit(0);
}
});
const proxy = new NativeMessagingProxy();
proxy.run();
} else {
if (
process.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1)
) {
if (process.platform === "darwin") {
// tslint:disable-next-line
const Main = require('./main').Main;
const app = require("electron").app;
const main = new Main();
main.bootstrap();
app.on("ready", () => {
app.dock.hide();
});
}
process.stdout.on("error", (e) => {
if (e.code === "EPIPE") {
process.exit(0);
}
});
const proxy = new NativeMessagingProxy();
proxy.run();
} else {
// tslint:disable-next-line
const Main = require("./main").Main;
const main = new Main();
main.bootstrap();
}

2
src/global.d.ts vendored
View File

@@ -1 +1 @@
declare module 'forcefocus';
declare module "forcefocus";

View File

@@ -1,16 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data: *; child-src *; frame-src *; connect-src *;">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bitwarden</title>
<base href="">
</head>
<body class="layout_frontend">
<base href="" />
</head>
<body class="layout_frontend">
<app-root>
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i></div>
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i></div>
</app-root>
</body>
</body>
</html>

View File

@@ -1,177 +1,207 @@
import { app } from 'electron';
import * as path from 'path';
import { app } from "electron";
import * as path from "path";
import { I18nService } from './services/i18n.service';
import { I18nService } from "./services/i18n.service";
import { MenuMain } from './main/menu.main';
import { MessagingMain } from './main/messaging.main';
import { PowerMonitorMain } from './main/powerMonitor.main';
import { MenuMain } from "./main/menu.main";
import { MessagingMain } from "./main/messaging.main";
import { PowerMonitorMain } from "./main/powerMonitor.main";
import { BiometricMain } from 'jslib-common/abstractions/biometric.main';
import { BiometricMain } from "jslib-common/abstractions/biometric.main";
import { KeytarStorageListener } from 'jslib-electron/keytarStorageListener';
import { KeytarStorageListener } from "jslib-electron/keytarStorageListener";
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
import { ElectronMainMessagingService } from 'jslib-electron/services/electronMainMessaging.service';
import { ElectronStorageService } from 'jslib-electron/services/electronStorage.service';
import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronMainMessagingService } from "jslib-electron/services/electronMainMessaging.service";
import { ElectronStorageService } from "jslib-electron/services/electronStorage.service";
import { TrayMain } from 'jslib-electron/tray.main';
import { UpdaterMain } from 'jslib-electron/updater.main';
import { WindowMain } from 'jslib-electron/window.main';
import { NativeMessagingMain } from './main/nativeMessaging.main';
import { TrayMain } from "jslib-electron/tray.main";
import { UpdaterMain } from "jslib-electron/updater.main";
import { WindowMain } from "jslib-electron/window.main";
import { NativeMessagingMain } from "./main/nativeMessaging.main";
import { StateService } from 'jslib-common/services/state.service';
import { StateService } from "jslib-common/services/state.service";
export class Main {
logService: ElectronLogService;
i18nService: I18nService;
storageService: ElectronStorageService;
messagingService: ElectronMainMessagingService;
stateService: StateService;
keytarStorageListener: KeytarStorageListener;
logService: ElectronLogService;
i18nService: I18nService;
storageService: ElectronStorageService;
messagingService: ElectronMainMessagingService;
stateService: StateService;
keytarStorageListener: KeytarStorageListener;
windowMain: WindowMain;
messagingMain: MessagingMain;
updaterMain: UpdaterMain;
menuMain: MenuMain;
powerMonitorMain: PowerMonitorMain;
trayMain: TrayMain;
biometricMain: BiometricMain;
nativeMessagingMain: NativeMessagingMain;
windowMain: WindowMain;
messagingMain: MessagingMain;
updaterMain: UpdaterMain;
menuMain: MenuMain;
powerMonitorMain: PowerMonitorMain;
trayMain: TrayMain;
biometricMain: BiometricMain;
nativeMessagingMain: NativeMessagingMain;
constructor() {
// Set paths for portable builds
let appDataPath = null;
if (process.env.BITWARDEN_APPDATA_DIR != null) {
appDataPath = process.env.BITWARDEN_APPDATA_DIR;
} else if (process.platform === 'win32' && process.env.PORTABLE_EXECUTABLE_DIR != null) {
appDataPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, 'bitwarden-appdata');
} else if (process.platform === 'linux' && process.env.SNAP_USER_DATA != null) {
appDataPath = path.join(process.env.SNAP_USER_DATA, 'appdata');
}
app.on('ready', () => {
// on ready stuff...
});
if (appDataPath != null) {
app.setPath('userData', appDataPath);
}
app.setPath('logs', path.join(app.getPath('userData'), 'logs'));
const args = process.argv.slice(1);
const watch = args.some(val => val === '--watch');
if (watch) {
// tslint:disable-next-line
require('electron-reload')(__dirname, {});
}
this.logService = new ElectronLogService(null, app.getPath('userData'));
this.i18nService = new I18nService('en', './locales/');
const storageDefaults: any = {};
// Default vault timeout to "on restart", and action to "lock"
storageDefaults['global.vaultTimeout'] = -1;
storageDefaults['global.vaultTimeoutAction'] = 'lock';
this.storageService = new ElectronStorageService(app.getPath('userData'), storageDefaults);
// TODO: this state service will have access to on disk storage, but not in memory storage.
// If we could get this to work using the stateService singleton that the rest of the app uses we could save
// ourselves from some hacks, like having to manually update the app menu vs. the menu subscribing to events.
this.stateService = new StateService(this.storageService, null, this.logService, null);
this.windowMain = new WindowMain(this.stateService, this.logService, true, undefined, undefined,
arg => this.processDeepLink(arg), win => this.trayMain.setupWindowListeners(win));
this.messagingMain = new MessagingMain(this, this.stateService);
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'desktop',
null, null, null, 'bitwarden');
this.menuMain = new MenuMain(this);
this.powerMonitorMain = new PowerMonitorMain(this);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
this.messagingService = new ElectronMainMessagingService(this.windowMain, message => {
this.messagingMain.onMessage(message);
});
if (process.platform === 'win32') {
const BiometricWindowsMain = require('jslib-electron/biometric.windows.main').default;
this.biometricMain = new BiometricWindowsMain(this.i18nService, this.windowMain, this.stateService, this.logService);
} else if (process.platform === 'darwin') {
const BiometricDarwinMain = require('jslib-electron/biometric.darwin.main').default;
this.biometricMain = new BiometricDarwinMain(this.i18nService, this.stateService);
}
this.keytarStorageListener = new KeytarStorageListener('Bitwarden', this.biometricMain);
this.nativeMessagingMain = new NativeMessagingMain(this.logService, this.windowMain, app.getPath('userData'), app.getPath('exe'));
constructor() {
// Set paths for portable builds
let appDataPath = null;
if (process.env.BITWARDEN_APPDATA_DIR != null) {
appDataPath = process.env.BITWARDEN_APPDATA_DIR;
} else if (process.platform === "win32" && process.env.PORTABLE_EXECUTABLE_DIR != null) {
appDataPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, "bitwarden-appdata");
} else if (process.platform === "linux" && process.env.SNAP_USER_DATA != null) {
appDataPath = path.join(process.env.SNAP_USER_DATA, "appdata");
}
bootstrap() {
this.keytarStorageListener.init();
this.windowMain.init().then(async () => {
const locale = await this.stateService.getLocale();
await this.i18nService.init(locale != null ? locale : app.getLocale());
this.messagingMain.init();
this.menuMain.init();
await this.trayMain.init('Bitwarden', [{
label: this.i18nService.t('lockNow'),
enabled: false,
id: 'lockNow',
click: () => this.messagingService.send('lockVault'),
}]);
if (await this.stateService.getEnableStartToTray()) {
this.trayMain.hideToTray();
}
this.powerMonitorMain.init();
await this.updaterMain.init();
if (this.biometricMain != null) {
await this.biometricMain.init();
}
app.on("ready", () => {
// on ready stuff...
});
if (await this.stateService.getEnableBrowserIntegration()) {
this.nativeMessagingMain.listen();
}
if (appDataPath != null) {
app.setPath("userData", appDataPath);
}
app.setPath("logs", path.join(app.getPath("userData"), "logs"));
app.removeAsDefaultProtocolClient('bitwarden');
if (process.env.NODE_ENV === 'development' && process.platform === 'win32') {
// Fix development build on Windows requirering a different protocol client
app.setAsDefaultProtocolClient('bitwarden', process.execPath, [
process.argv[1],
path.resolve(process.argv[2]),
]);
} else {
app.setAsDefaultProtocolClient('bitwarden');
}
const args = process.argv.slice(1);
const watch = args.some((val) => val === "--watch");
// Process protocol for macOS
app.on('open-url', (event, url) => {
event.preventDefault();
this.processDeepLink([url]);
});
// Handle window visibility events
this.windowMain.win.on('hide', () => {
this.messagingService.send('windowHidden');
});
this.windowMain.win.on('minimize', () => {
this.messagingService.send('windowHidden');
});
}, (e: any) => {
// tslint:disable-next-line
console.error(e);
});
if (watch) {
// tslint:disable-next-line
require("electron-reload")(__dirname, {});
}
private processDeepLink(argv: string[]): void {
argv.filter(s => s.indexOf('bitwarden://') === 0).forEach(s => {
const url = new URL(s);
const code = url.searchParams.get('code');
const receivedState = url.searchParams.get('state');
if (code != null && receivedState != null) {
this.messagingService.send('ssoCallback', { code: code, state: receivedState });
}
});
this.logService = new ElectronLogService(null, app.getPath("userData"));
this.i18nService = new I18nService("en", "./locales/");
const storageDefaults: any = {};
// Default vault timeout to "on restart", and action to "lock"
storageDefaults["global.vaultTimeout"] = -1;
storageDefaults["global.vaultTimeoutAction"] = "lock";
this.storageService = new ElectronStorageService(app.getPath("userData"), storageDefaults);
// TODO: this state service will have access to on disk storage, but not in memory storage.
// If we could get this to work using the stateService singleton that the rest of the app uses we could save
// ourselves from some hacks, like having to manually update the app menu vs. the menu subscribing to events.
this.stateService = new StateService(this.storageService, null, this.logService, null);
this.windowMain = new WindowMain(
this.stateService,
this.logService,
true,
undefined,
undefined,
(arg) => this.processDeepLink(arg),
(win) => this.trayMain.setupWindowListeners(win)
);
this.messagingMain = new MessagingMain(this, this.stateService);
this.updaterMain = new UpdaterMain(
this.i18nService,
this.windowMain,
"desktop",
null,
null,
null,
"bitwarden"
);
this.menuMain = new MenuMain(this);
this.powerMonitorMain = new PowerMonitorMain(this);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
this.messagingMain.onMessage(message);
});
if (process.platform === "win32") {
const BiometricWindowsMain = require("jslib-electron/biometric.windows.main").default;
this.biometricMain = new BiometricWindowsMain(
this.i18nService,
this.windowMain,
this.stateService,
this.logService
);
} else if (process.platform === "darwin") {
const BiometricDarwinMain = require("jslib-electron/biometric.darwin.main").default;
this.biometricMain = new BiometricDarwinMain(this.i18nService, this.stateService);
}
this.keytarStorageListener = new KeytarStorageListener("Bitwarden", this.biometricMain);
this.nativeMessagingMain = new NativeMessagingMain(
this.logService,
this.windowMain,
app.getPath("userData"),
app.getPath("exe")
);
}
bootstrap() {
this.keytarStorageListener.init();
this.windowMain.init().then(
async () => {
const locale = await this.stateService.getLocale();
await this.i18nService.init(locale != null ? locale : app.getLocale());
this.messagingMain.init();
this.menuMain.init();
await this.trayMain.init("Bitwarden", [
{
label: this.i18nService.t("lockNow"),
enabled: false,
id: "lockNow",
click: () => this.messagingService.send("lockVault"),
},
]);
if (await this.stateService.getEnableStartToTray()) {
this.trayMain.hideToTray();
}
this.powerMonitorMain.init();
await this.updaterMain.init();
if (this.biometricMain != null) {
await this.biometricMain.init();
}
if (await this.stateService.getEnableBrowserIntegration()) {
this.nativeMessagingMain.listen();
}
app.removeAsDefaultProtocolClient("bitwarden");
if (process.env.NODE_ENV === "development" && process.platform === "win32") {
// Fix development build on Windows requirering a different protocol client
app.setAsDefaultProtocolClient("bitwarden", process.execPath, [
process.argv[1],
path.resolve(process.argv[2]),
]);
} else {
app.setAsDefaultProtocolClient("bitwarden");
}
// Process protocol for macOS
app.on("open-url", (event, url) => {
event.preventDefault();
this.processDeepLink([url]);
});
// Handle window visibility events
this.windowMain.win.on("hide", () => {
this.messagingService.send("windowHidden");
});
this.windowMain.win.on("minimize", () => {
this.messagingService.send("windowHidden");
});
},
(e: any) => {
// tslint:disable-next-line
console.error(e);
}
);
}
private processDeepLink(argv: string[]): void {
argv
.filter((s) => s.indexOf("bitwarden://") === 0)
.forEach((s) => {
const url = new URL(s);
const code = url.searchParams.get("code");
const receivedState = url.searchParams.get("state");
if (code != null && receivedState != null) {
this.messagingService.send("ssoCallback", { code: code, state: receivedState });
}
});
}
}

View File

@@ -1,96 +1,92 @@
import {
BrowserWindow,
clipboard,
dialog,
MenuItemConstructorOptions,
} from 'electron';
import { BrowserWindow, clipboard, dialog, MenuItemConstructorOptions } from "electron";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { UpdaterMain } from 'jslib-electron/updater.main';
import { isMac, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
import { UpdaterMain } from "jslib-electron/updater.main";
import { isMac, isSnapStore, isWindowsStore } from "jslib-electron/utils";
import { IMenubarMenu } from './menubar';
import { IMenubarMenu } from "./menubar";
export class AboutMenu implements IMenubarMenu {
readonly id: string = 'about';
readonly id: string = "about";
get visible(): boolean {
return !isMac();
}
get visible(): boolean {
return !isMac();
}
get label(): string {
return this.localize('about');
}
get label(): string {
return this.localize("about");
}
get items(): MenuItemConstructorOptions[] {
return [
this.separator,
this.checkForUpdates,
this.aboutBitwarden,
];
}
get items(): MenuItemConstructorOptions[] {
return [this.separator, this.checkForUpdates, this.aboutBitwarden];
}
private readonly _i18nService: I18nService;
private readonly _updater: UpdaterMain;
private readonly _window: BrowserWindow;
private readonly _version: string;
private readonly _i18nService: I18nService;
private readonly _updater: UpdaterMain;
private readonly _window: BrowserWindow;
private readonly _version: string;
constructor(
i18nService: I18nService,
version: string,
window: BrowserWindow,
updater: UpdaterMain,
) {
this._i18nService = i18nService;
this._updater = updater;
this._version = version;
this._window = window;
}
constructor(
i18nService: I18nService,
version: string,
window: BrowserWindow,
updater: UpdaterMain
) {
this._i18nService = i18nService;
this._updater = updater;
this._version = version;
this._window = window;
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get separator(): MenuItemConstructorOptions {
return { type: "separator" };
}
private get checkForUpdates(): MenuItemConstructorOptions {
return {
id: 'checkForUpdates',
label: this.localize('checkForUpdates'),
visible: !isWindowsStore() && !isSnapStore(),
click: () => this.checkForUpdate(),
};
}
private get checkForUpdates(): MenuItemConstructorOptions {
return {
id: "checkForUpdates",
label: this.localize("checkForUpdates"),
visible: !isWindowsStore() && !isSnapStore(),
click: () => this.checkForUpdate(),
};
}
private get aboutBitwarden(): MenuItemConstructorOptions {
return {
id: 'aboutBitwarden',
label: this.localize('aboutBitwarden'),
click: async () => {
const aboutInformation = this.localize('version', this._version) +
'\nShell ' + process.versions.electron +
'\nRenderer ' + process.versions.chrome +
'\nNode ' + process.versions.node +
'\nArchitecture ' + process.arch;
const result = await dialog.showMessageBox(this._window, {
title: 'Bitwarden',
message: 'Bitwarden',
detail: aboutInformation,
type: 'info',
noLink: true,
buttons: [this.localize('ok'), this.localize('copy')],
});
if (result.response === 1) {
clipboard.writeText(aboutInformation);
}
},
};
}
private get aboutBitwarden(): MenuItemConstructorOptions {
return {
id: "aboutBitwarden",
label: this.localize("aboutBitwarden"),
click: async () => {
const aboutInformation =
this.localize("version", this._version) +
"\nShell " +
process.versions.electron +
"\nRenderer " +
process.versions.chrome +
"\nNode " +
process.versions.node +
"\nArchitecture " +
process.arch;
const result = await dialog.showMessageBox(this._window, {
title: "Bitwarden",
message: "Bitwarden",
detail: aboutInformation,
type: "info",
noLink: true,
buttons: [this.localize("ok"), this.localize("copy")],
});
if (result.response === 1) {
clipboard.writeText(aboutInformation);
}
},
};
}
private localize(s: string, p?: string) {
return this._i18nService.t(s, p);
}
private localize(s: string, p?: string) {
return this._i18nService.t(s, p);
}
private async checkForUpdate() {
this._updater.checkForUpdate(true);
}
private async checkForUpdate() {
this._updater.checkForUpdate(true);
}
}

View File

@@ -1,121 +1,116 @@
import {
BrowserWindow,
dialog,
MenuItemConstructorOptions,
shell,
} from 'electron';
import { BrowserWindow, dialog, MenuItemConstructorOptions, shell } from "electron";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { isMacAppStore, isWindowsStore } from 'jslib-electron/utils';
import { isMacAppStore, isWindowsStore } from "jslib-electron/utils";
import { IMenubarMenu } from './menubar';
import { IMenubarMenu } from "./menubar";
export class AccountMenu implements IMenubarMenu {
readonly id: string = 'accountMenu';
readonly id: string = "accountMenu";
get label(): string {
return this.localize('account');
}
get label(): string {
return this.localize("account");
}
get items(): MenuItemConstructorOptions[] {
return [
this.premiumMembership,
this.changeMasterPassword,
this.twoStepLogin,
this.fingerprintPhrase,
];
}
get items(): MenuItemConstructorOptions[] {
return [
this.premiumMembership,
this.changeMasterPassword,
this.twoStepLogin,
this.fingerprintPhrase,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _webVaultUrl: string;
private readonly _window: BrowserWindow;
private readonly _isAuthenticated: boolean;
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _webVaultUrl: string;
private readonly _window: BrowserWindow;
private readonly _isAuthenticated: boolean;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
webVaultUrl: string,
window: BrowserWindow,
isAuthenticated: boolean,
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._webVaultUrl = webVaultUrl;
this._window = window;
this._isAuthenticated = isAuthenticated;
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
webVaultUrl: string,
window: BrowserWindow,
isAuthenticated: boolean
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._webVaultUrl = webVaultUrl;
this._window = window;
this._isAuthenticated = isAuthenticated;
}
private get premiumMembership(): MenuItemConstructorOptions {
return {
label: this.localize('premiumMembership'),
click: () => this.sendMessage('openPremium'),
id: 'premiumMembership',
visible: !isWindowsStore() && !isMacAppStore(),
enabled: this._isAuthenticated,
};
}
private get premiumMembership(): MenuItemConstructorOptions {
return {
label: this.localize("premiumMembership"),
click: () => this.sendMessage("openPremium"),
id: "premiumMembership",
visible: !isWindowsStore() && !isMacAppStore(),
enabled: this._isAuthenticated,
};
}
private get changeMasterPassword(): MenuItemConstructorOptions {
return {
label: this.localize('changeMasterPass'),
id: 'changeMasterPass',
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize('changeMasterPass'),
message: this.localize('changeMasterPass'),
detail: this.localize('changeMasterPasswordConfirmation'),
buttons: [this.localize('yes'), this.localize('no')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
shell.openExternal(this._webVaultUrl);
}
},
enabled: this._isAuthenticated,
};
}
private get changeMasterPassword(): MenuItemConstructorOptions {
return {
label: this.localize("changeMasterPass"),
id: "changeMasterPass",
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize("changeMasterPass"),
message: this.localize("changeMasterPass"),
detail: this.localize("changeMasterPasswordConfirmation"),
buttons: [this.localize("yes"), this.localize("no")],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
shell.openExternal(this._webVaultUrl);
}
},
enabled: this._isAuthenticated,
};
}
private get twoStepLogin(): MenuItemConstructorOptions {
return {
label: this.localize('twoStepLogin'),
id: 'twoStepLogin',
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize('twoStepLogin'),
message: this.localize('twoStepLogin'),
detail: this.localize('twoStepLoginConfirmation'),
buttons: [this.localize('yes'), this.localize('no')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
shell.openExternal(this._webVaultUrl);
}
},
enabled: this._isAuthenticated,
};
}
private get twoStepLogin(): MenuItemConstructorOptions {
return {
label: this.localize("twoStepLogin"),
id: "twoStepLogin",
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize("twoStepLogin"),
message: this.localize("twoStepLogin"),
detail: this.localize("twoStepLoginConfirmation"),
buttons: [this.localize("yes"), this.localize("no")],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
shell.openExternal(this._webVaultUrl);
}
},
enabled: this._isAuthenticated,
};
}
private get fingerprintPhrase(): MenuItemConstructorOptions {
return {
label: this.localize('fingerprintPhrase'),
id: 'fingerprintPhrase',
click: () => this.sendMessage('showFingerprintPhrase'),
enabled: this._isAuthenticated,
};
}
private get fingerprintPhrase(): MenuItemConstructorOptions {
return {
label: this.localize("fingerprintPhrase"),
id: "fingerprintPhrase",
click: () => this.sendMessage("showFingerprintPhrase"),
enabled: this._isAuthenticated,
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
}

View File

@@ -1,237 +1,229 @@
import {
BrowserWindow,
dialog,
MenuItem,
MenuItemConstructorOptions,
} from 'electron';
import { BrowserWindow, dialog, MenuItem, MenuItemConstructorOptions } from "electron";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { UpdaterMain } from 'jslib-electron/updater.main';
import { isMacAppStore, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
import { UpdaterMain } from "jslib-electron/updater.main";
import { isMacAppStore, isSnapStore, isWindowsStore } from "jslib-electron/utils";
import { IMenubarMenu } from './menubar';
import { IMenubarMenu } from "./menubar";
import { MenuAccount } from './menu.updater';
import { MenuAccount } from "./menu.updater";
// AKA: "FirstMenu" or "MacMenu" - the first menu that shows on all macOs apps
export class BitwardenMenu implements IMenubarMenu {
readonly id: string = 'bitwarden';
readonly label: string = 'Bitwarden';
readonly id: string = "bitwarden";
readonly label: string = "Bitwarden";
get items(): MenuItemConstructorOptions[] {
return [
this.aboutBitwarden,
this.checkForUpdates,
this.separator,
this.settings,
this.lock,
this.lockAll,
this.logOut,
this.services,
this.separator,
this.hideBitwarden,
this.hideOthers,
this.showAll,
this.separator,
this.quitBitwarden,
];
get items(): MenuItemConstructorOptions[] {
return [
this.aboutBitwarden,
this.checkForUpdates,
this.separator,
this.settings,
this.lock,
this.lockAll,
this.logOut,
this.services,
this.separator,
this.hideBitwarden,
this.hideOthers,
this.showAll,
this.separator,
this.quitBitwarden,
];
}
private readonly _i18nService: I18nService;
private readonly _updater: UpdaterMain;
private readonly _messagingService: MessagingService;
private readonly _accounts: { [userId: string]: MenuAccount };
private readonly _window: BrowserWindow;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
updater: UpdaterMain,
window: BrowserWindow,
accounts: { [userId: string]: MenuAccount }
) {
this._i18nService = i18nService;
this._updater = updater;
this._messagingService = messagingService;
this._window = window;
this._accounts = accounts;
}
private get hasAccounts(): boolean {
return this._accounts != null && Object.keys(this._accounts).length > 0;
}
private get aboutBitwarden(): MenuItemConstructorOptions {
return {
id: "aboutBitwarden",
label: this.localize("aboutBitwarden"),
role: "about",
visible: isMacAppStore(),
};
}
private get checkForUpdates(): MenuItemConstructorOptions {
return {
id: "checkForUpdates",
label: this.localize("checkForUpdates"),
click: (menuItem) => this.checkForUpdate(menuItem),
visible: !isMacAppStore() && !isWindowsStore() && !isSnapStore(),
};
}
private get separator(): MenuItemConstructorOptions {
return {
type: "separator",
};
}
private get settings(): MenuItemConstructorOptions {
return {
id: "settings",
label: this.localize(process.platform === "darwin" ? "preferences" : "settings"),
click: () => this.sendMessage("openSettings"),
accelerator: "CmdOrCtrl+,",
};
}
private get lock(): MenuItemConstructorOptions {
return {
id: "lock",
label: this.localize("lockVault"),
submenu: this.lockSubmenu,
enabled: this.hasAccounts,
};
}
private get lockSubmenu(): MenuItemConstructorOptions[] {
const value: MenuItemConstructorOptions[] = [];
for (const userId in this._accounts) {
if (userId == null) {
continue;
}
value.push({
label: this._accounts[userId].email,
id: `lockNow_${this._accounts[userId].userId}`,
click: () => this.sendMessage("lockVault", { userId: this._accounts[userId].userId }),
enabled: !this._accounts[userId].isLocked,
visible: this._accounts[userId].isAuthenticated,
});
}
return value;
}
private readonly _i18nService: I18nService;
private readonly _updater: UpdaterMain;
private readonly _messagingService: MessagingService;
private readonly _accounts: { [userId: string]: MenuAccount };
private readonly _window: BrowserWindow;
private get lockAll(): MenuItemConstructorOptions {
return {
id: "lockAllNow",
label: this.localize("lockAllVaults"),
click: () => this.sendMessage("lockAllVaults"),
accelerator: "CmdOrCtrl+L",
enabled: this.hasAccounts,
};
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
updater: UpdaterMain,
window: BrowserWindow,
accounts: { [userId: string]: MenuAccount },
) {
this._i18nService = i18nService;
this._updater = updater;
this._messagingService = messagingService;
this._window = window;
this._accounts = accounts;
private get logOut(): MenuItemConstructorOptions {
return {
id: "logOut",
label: this.localize("logOut"),
submenu: this.logOutSubmenu,
enabled: this.hasAccounts,
};
}
private get logOutSubmenu(): MenuItemConstructorOptions[] {
const value: MenuItemConstructorOptions[] = [];
for (const userId in this._accounts) {
if (userId == null) {
continue;
}
value.push({
label: this._accounts[userId].email,
id: `logOut_${this._accounts[userId].userId}`,
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize("logOut"),
message: this.localize("logOut"),
detail: this.localize("logOutConfirmation"),
buttons: [this.localize("logOut"), this.localize("cancel")],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
this.sendMessage("logout", { userId: this._accounts[userId].userId });
}
},
visible: this._accounts[userId].isAuthenticated,
});
}
return value;
}
private get hasAccounts(): boolean {
return this._accounts != null && Object.keys(this._accounts).length > 0;
}
private get services(): MenuItemConstructorOptions {
return {
id: "services",
label: this.localize("services"),
role: "services",
submenu: [],
visible: isMacAppStore(),
};
}
private get aboutBitwarden(): MenuItemConstructorOptions {
return {
id: 'aboutBitwarden',
label: this.localize('aboutBitwarden'),
role: 'about',
visible: isMacAppStore(),
};
}
private get hideBitwarden(): MenuItemConstructorOptions {
return {
id: "hideBitwarden",
label: this.localize("hideBitwarden"),
role: "hide",
visible: isMacAppStore(),
};
}
private get checkForUpdates(): MenuItemConstructorOptions {
return {
id: 'checkForUpdates',
label: this.localize('checkForUpdates'),
click: menuItem => this.checkForUpdate(menuItem),
visible: !isMacAppStore() && !isWindowsStore() && !isSnapStore(),
};
}
private get hideOthers(): MenuItemConstructorOptions {
return {
id: "hideOthers",
label: this.localize("hideOthers"),
role: "hideOthers",
visible: isMacAppStore(),
};
}
private get separator(): MenuItemConstructorOptions {
return {
type: 'separator',
};
}
private get showAll(): MenuItemConstructorOptions {
return {
id: "showAll",
label: this.localize("showAll"),
role: "unhide",
visible: isMacAppStore(),
};
}
private get settings(): MenuItemConstructorOptions {
return {
id: 'settings',
label: this.localize(process.platform === 'darwin' ?
'preferences' :
'settings'
),
click: () => this.sendMessage('openSettings'),
accelerator: 'CmdOrCtrl+,',
};
}
private get quitBitwarden(): MenuItemConstructorOptions {
return {
id: "quitBitwarden",
label: this.localize("quitBitwarden"),
role: "quit",
visible: isMacAppStore(),
};
}
private get lock(): MenuItemConstructorOptions {
return {
id: 'lock',
label: this.localize('lockVault'),
submenu: this.lockSubmenu,
enabled: this.hasAccounts,
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private get lockSubmenu(): MenuItemConstructorOptions[] {
const value: MenuItemConstructorOptions[] = [];
for (const userId in this._accounts) {
if (userId == null) {
continue;
}
private async checkForUpdate(menuItem: MenuItem) {
menuItem.enabled = false;
this._updater.checkForUpdate(true);
menuItem.enabled = true;
}
value.push({
label: this._accounts[userId].email,
id: `lockNow_${this._accounts[userId].userId}`,
click: () => this.sendMessage('lockVault', { userId: this._accounts[userId].userId }),
enabled: !this._accounts[userId].isLocked,
visible: this._accounts[userId].isAuthenticated,
});
}
return value;
}
private get lockAll(): MenuItemConstructorOptions {
return {
id: 'lockAllNow',
label: this.localize('lockAllVaults'),
click: () => this.sendMessage('lockAllVaults'),
accelerator: 'CmdOrCtrl+L',
enabled: this.hasAccounts,
};
}
private get logOut(): MenuItemConstructorOptions {
return {
id: 'logOut',
label: this.localize('logOut'),
submenu: this.logOutSubmenu,
enabled: this.hasAccounts,
};
}
private get logOutSubmenu(): MenuItemConstructorOptions[] {
const value: MenuItemConstructorOptions[] = [];
for (const userId in this._accounts) {
if (userId == null) {
continue;
}
value.push({
label: this._accounts[userId].email,
id: `logOut_${this._accounts[userId].userId}`,
click: async () => {
const result = await dialog.showMessageBox(this._window, {
title: this.localize('logOut'),
message: this.localize('logOut'),
detail: this.localize('logOutConfirmation'),
buttons: [this.localize('logOut'), this.localize('cancel')],
cancelId: 1,
defaultId: 0,
noLink: true,
});
if (result.response === 0) {
this.sendMessage('logout', { userId: this._accounts[userId].userId });
}
},
visible: this._accounts[userId].isAuthenticated,
});
}
return value;
}
private get services(): MenuItemConstructorOptions {
return {
id: 'services',
label: this.localize('services'),
role: 'services',
submenu: [],
visible: isMacAppStore(),
};
}
private get hideBitwarden(): MenuItemConstructorOptions {
return {
id: 'hideBitwarden',
label: this.localize('hideBitwarden'),
role: 'hide',
visible: isMacAppStore(),
};
}
private get hideOthers(): MenuItemConstructorOptions {
return {
id: 'hideOthers',
label: this.localize('hideOthers'),
role: 'hideOthers',
visible: isMacAppStore(),
};
}
private get showAll(): MenuItemConstructorOptions {
return {
id: 'showAll',
label: this.localize('showAll'),
role: 'unhide',
visible: isMacAppStore(),
};
}
private get quitBitwarden(): MenuItemConstructorOptions {
return {
id: 'quitBitwarden',
label: this.localize('quitBitwarden'),
role: 'quit',
visible: isMacAppStore(),
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private async checkForUpdate(menuItem: MenuItem) {
menuItem.enabled = false;
this._updater.checkForUpdate(true);
menuItem.enabled = true;
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
}

View File

@@ -1,134 +1,134 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { IMenubarMenu } from './menubar';
import { IMenubarMenu } from "./menubar";
import { MenuItemConstructorOptions } from 'electron';
import { MenuItemConstructorOptions } from "electron";
export class EditMenu implements IMenubarMenu {
readonly id: string = 'editMenu';
readonly id: string = "editMenu";
get label(): string {
return this.localize('edit');
}
get label(): string {
return this.localize("edit");
}
get items(): MenuItemConstructorOptions[] {
return [
this.undo,
this.redo,
this.separator,
this.cut,
this.copy,
this.paste,
this.separator,
this.selectAll,
this.separator,
this.copyUsername,
this.copyPassword,
this.copyVerificationCodeTotp,
];
}
get items(): MenuItemConstructorOptions[] {
return [
this.undo,
this.redo,
this.separator,
this.cut,
this.copy,
this.paste,
this.separator,
this.selectAll,
this.separator,
this.copyUsername,
this.copyPassword,
this.copyVerificationCodeTotp,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean,
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
private get undo(): MenuItemConstructorOptions {
return {
id: 'undo',
label: this.localize('undo'),
role: 'undo',
};
}
private get undo(): MenuItemConstructorOptions {
return {
id: "undo",
label: this.localize("undo"),
role: "undo",
};
}
private get redo(): MenuItemConstructorOptions {
return {
id: 'redo',
label: this.localize('redo'),
role: 'redo',
};
}
private get redo(): MenuItemConstructorOptions {
return {
id: "redo",
label: this.localize("redo"),
role: "redo",
};
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get separator(): MenuItemConstructorOptions {
return { type: "separator" };
}
private get cut(): MenuItemConstructorOptions {
return {
id: 'cut',
label: this.localize('cut'),
role: 'cut',
};
}
private get cut(): MenuItemConstructorOptions {
return {
id: "cut",
label: this.localize("cut"),
role: "cut",
};
}
private get copy(): MenuItemConstructorOptions {
return {
id: 'copy',
label: this.localize('copy'),
role: 'copy',
};
}
private get copy(): MenuItemConstructorOptions {
return {
id: "copy",
label: this.localize("copy"),
role: "copy",
};
}
private get paste(): MenuItemConstructorOptions {
return {
id: 'paste',
label: this.localize('paste'),
role: 'paste',
};
}
private get paste(): MenuItemConstructorOptions {
return {
id: "paste",
label: this.localize("paste"),
role: "paste",
};
}
private get selectAll(): MenuItemConstructorOptions {
return {
id: 'selectAll',
label: this.localize('selectAll'),
role: 'selectAll',
};
}
private get selectAll(): MenuItemConstructorOptions {
return {
id: "selectAll",
label: this.localize("selectAll"),
role: "selectAll",
};
}
private get copyUsername(): MenuItemConstructorOptions {
return {
label: this.localize('copyUsername'),
id: 'copyUsername',
click: () => this.sendMessage('copyUsername'),
accelerator: 'CmdOrCtrl+U',
enabled: this._isAuthenticated,
};
}
private get copyUsername(): MenuItemConstructorOptions {
return {
label: this.localize("copyUsername"),
id: "copyUsername",
click: () => this.sendMessage("copyUsername"),
accelerator: "CmdOrCtrl+U",
enabled: this._isAuthenticated,
};
}
private get copyPassword(): MenuItemConstructorOptions {
return {
label: this.localize('copyPassword'),
id: 'copyPassword',
click: () => this.sendMessage('copyPassword'),
accelerator: 'CmdOrCtrl+P',
enabled: this._isAuthenticated,
};
}
private get copyPassword(): MenuItemConstructorOptions {
return {
label: this.localize("copyPassword"),
id: "copyPassword",
click: () => this.sendMessage("copyPassword"),
accelerator: "CmdOrCtrl+P",
enabled: this._isAuthenticated,
};
}
private get copyVerificationCodeTotp(): MenuItemConstructorOptions {
return {
label: this.localize('copyVerificationCodeTotp'),
id: 'copyTotp',
click: () => this.sendMessage('copyTotp'),
accelerator: 'CmdOrCtrl+T',
};
}
private get copyVerificationCodeTotp(): MenuItemConstructorOptions {
return {
label: this.localize("copyVerificationCodeTotp"),
id: "copyTotp",
click: () => this.sendMessage("copyTotp"),
accelerator: "CmdOrCtrl+T",
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
}

View File

@@ -1,134 +1,134 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { isMacAppStore } from 'jslib-electron/utils';
import { isMacAppStore } from "jslib-electron/utils";
import { IMenubarMenu } from './menubar';
import { IMenubarMenu } from "./menubar";
import { MenuItemConstructorOptions } from 'electron';
import { MenuItemConstructorOptions } from "electron";
export class FileMenu implements IMenubarMenu {
readonly id: string = 'fileMenu';
readonly id: string = "fileMenu";
get label(): string {
return this.localize('file');
}
get label(): string {
return this.localize("file");
}
get items(): MenuItemConstructorOptions[] {
return [
this.addNewLogin,
this.addNewItem,
this.addNewFolder,
this.separator,
this.syncVault,
this.exportVault,
this.quitBitwarden,
];
}
get items(): MenuItemConstructorOptions[] {
return [
this.addNewLogin,
this.addNewItem,
this.addNewFolder,
this.separator,
this.syncVault,
this.exportVault,
this.quitBitwarden,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean,
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
private get addNewLogin(): MenuItemConstructorOptions {
return {
label: this.localize('addNewLogin'),
click: () => this.sendMessage('newLogin'),
accelerator: 'CmdOrCtrl+N',
id: 'addNewLogin',
};
}
private get addNewLogin(): MenuItemConstructorOptions {
return {
label: this.localize("addNewLogin"),
click: () => this.sendMessage("newLogin"),
accelerator: "CmdOrCtrl+N",
id: "addNewLogin",
};
}
private get addNewItem(): MenuItemConstructorOptions {
return {
label: this.localize('addNewItem'),
id: 'addNewItem',
submenu: this.addNewItemSubmenu,
enabled: this._isAuthenticated,
};
}
private get addNewItem(): MenuItemConstructorOptions {
return {
label: this.localize("addNewItem"),
id: "addNewItem",
submenu: this.addNewItemSubmenu,
enabled: this._isAuthenticated,
};
}
private get addNewItemSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'typeLogin',
label: this.localize('typeLogin'),
click: () => this.sendMessage('newLogin'),
accelerator: 'CmdOrCtrl+Shift+L',
},
{
id: 'typeCard',
label: this.localize('typeCard'),
click: () => this.sendMessage('newCard'),
accelerator: 'CmdOrCtrl+Shift+C',
},
{
id: 'typeIdentity',
label: this.localize('typeIdentity'),
click: () => this.sendMessage('newIdentity'),
accelerator: 'CmdOrCtrl+Shift+I',
},
{
id: 'typeSecureNote',
label: this.localize('typeSecureNote'),
click: () => this.sendMessage('newSecureNote'),
accelerator: 'CmdOrCtrl+Shift+S',
},
];
}
private get addNewItemSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: "typeLogin",
label: this.localize("typeLogin"),
click: () => this.sendMessage("newLogin"),
accelerator: "CmdOrCtrl+Shift+L",
},
{
id: "typeCard",
label: this.localize("typeCard"),
click: () => this.sendMessage("newCard"),
accelerator: "CmdOrCtrl+Shift+C",
},
{
id: "typeIdentity",
label: this.localize("typeIdentity"),
click: () => this.sendMessage("newIdentity"),
accelerator: "CmdOrCtrl+Shift+I",
},
{
id: "typeSecureNote",
label: this.localize("typeSecureNote"),
click: () => this.sendMessage("newSecureNote"),
accelerator: "CmdOrCtrl+Shift+S",
},
];
}
private get addNewFolder(): MenuItemConstructorOptions {
return {
id: 'addNewFolder',
label: this.localize('addNewFolder'),
click: () => this.sendMessage('newFolder'),
};
}
private get addNewFolder(): MenuItemConstructorOptions {
return {
id: "addNewFolder",
label: this.localize("addNewFolder"),
click: () => this.sendMessage("newFolder"),
};
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get separator(): MenuItemConstructorOptions {
return { type: "separator" };
}
private get syncVault(): MenuItemConstructorOptions {
return {
id: 'syncVault',
label: this.localize('syncVault'),
click: () => this.sendMessage('syncVault'),
};
}
private get syncVault(): MenuItemConstructorOptions {
return {
id: "syncVault",
label: this.localize("syncVault"),
click: () => this.sendMessage("syncVault"),
};
}
private get exportVault(): MenuItemConstructorOptions {
return {
id: 'exportVault',
label: this.localize('exportVault'),
click: () => this.sendMessage('exportVault'),
};
}
private get exportVault(): MenuItemConstructorOptions {
return {
id: "exportVault",
label: this.localize("exportVault"),
click: () => this.sendMessage("exportVault"),
};
}
private get quitBitwarden(): MenuItemConstructorOptions {
return {
id: 'quitBitwarden',
label: this.localize('quitBitwarden'),
visible: !isMacAppStore(),
role: 'quit',
};
}
private get quitBitwarden(): MenuItemConstructorOptions {
return {
id: "quitBitwarden",
label: this.localize("quitBitwarden"),
visible: !isMacAppStore(),
role: "quit",
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
}

View File

@@ -1,223 +1,228 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { IMenubarMenu } from './menubar';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { IMenubarMenu } from "./menubar";
import { shell } from 'electron';
import { shell } from "electron";
import { isMacAppStore, isWindowsStore } from 'jslib-electron/utils';
import { isMacAppStore, isWindowsStore } from "jslib-electron/utils";
import { MenuItemConstructorOptions } from 'electron';
import { MenuItemConstructorOptions } from "electron";
export class HelpMenu implements IMenubarMenu {
readonly id: string = 'help';
readonly id: string = "help";
get label(): string {
return this.localize('help');
}
get label(): string {
return this.localize("help");
}
get items(): MenuItemConstructorOptions[] {
return [
this.emailUs,
this.visitOurWebsite,
this.fileBugReport,
this.legal,
this.separator,
this.followUs,
this.separator,
this.goToWebVault,
this.separator,
this.getMobileApp,
this.getBrowserExtension,
];
}
get items(): MenuItemConstructorOptions[] {
return [
this.emailUs,
this.visitOurWebsite,
this.fileBugReport,
this.legal,
this.separator,
this.followUs,
this.separator,
this.goToWebVault,
this.separator,
this.getMobileApp,
this.getBrowserExtension,
];
}
private readonly _i18nService: I18nService;
private readonly _webVaultUrl: string;
private readonly _i18nService: I18nService;
private readonly _webVaultUrl: string;
constructor(
i18nService: I18nService,
webVaultUrl: string
) {
this._i18nService = i18nService;
this._webVaultUrl = webVaultUrl;
}
constructor(i18nService: I18nService, webVaultUrl: string) {
this._i18nService = i18nService;
this._webVaultUrl = webVaultUrl;
}
private get emailUs(): MenuItemConstructorOptions {
return {
id: 'emailUs',
label: this.localize('emailUs'),
click: () => shell.openExternal('mailTo:hello@bitwarden.com'),
};
}
private get emailUs(): MenuItemConstructorOptions {
return {
id: "emailUs",
label: this.localize("emailUs"),
click: () => shell.openExternal("mailTo:hello@bitwarden.com"),
};
}
private get visitOurWebsite(): MenuItemConstructorOptions {
return {
id: 'visitOurWebsite',
label: this.localize('visitOurWebsite'),
click: () => shell.openExternal('https://bitwarden.com/contact'),
};
}
private get visitOurWebsite(): MenuItemConstructorOptions {
return {
id: "visitOurWebsite",
label: this.localize("visitOurWebsite"),
click: () => shell.openExternal("https://bitwarden.com/contact"),
};
}
private get fileBugReport(): MenuItemConstructorOptions {
return {
id: 'fileBugReport',
label: this.localize('fileBugReport'),
click: () => shell.openExternal('https://github.com/bitwarden/desktop/issues'),
};
}
private get fileBugReport(): MenuItemConstructorOptions {
return {
id: "fileBugReport",
label: this.localize("fileBugReport"),
click: () => shell.openExternal("https://github.com/bitwarden/desktop/issues"),
};
}
private get legal(): MenuItemConstructorOptions {
return {
id: 'legal',
label: this.localize('legal'),
visible: !isMacAppStore(),
submenu: this.legalSubmenu,
};
}
private get legal(): MenuItemConstructorOptions {
return {
id: "legal",
label: this.localize("legal"),
visible: !isMacAppStore(),
submenu: this.legalSubmenu,
};
}
private get legalSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'termsOfService',
label: this.localize('termsOfService'),
click: () => shell.openExternal('https://bitwarden.com/terms/'),
},
{
id: 'privacyPolicy',
label: this.localize('privacyPolicy'),
click: () => shell.openExternal('https://bitwarden.com/privacy/'),
},
];
}
private get legalSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: "termsOfService",
label: this.localize("termsOfService"),
click: () => shell.openExternal("https://bitwarden.com/terms/"),
},
{
id: "privacyPolicy",
label: this.localize("privacyPolicy"),
click: () => shell.openExternal("https://bitwarden.com/privacy/"),
},
];
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get separator(): MenuItemConstructorOptions {
return { type: "separator" };
}
private get followUs(): MenuItemConstructorOptions {
return {
id: 'followUs',
label: this.localize('followUs'),
submenu: this.followUsSubmenu,
};
}
private get followUs(): MenuItemConstructorOptions {
return {
id: "followUs",
label: this.localize("followUs"),
submenu: this.followUsSubmenu,
};
}
private get followUsSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'blog',
label: this.localize('blog'),
click: () => shell.openExternal('https://blog.bitwarden.com'),
},
{
id: 'twitter',
label: 'Twitter',
click: () => shell.openExternal('https://twitter.com/bitwarden'),
},
{
id: 'facebook',
label: 'Facebook',
click: () => shell.openExternal('https://www.facebook.com/bitwarden/'),
},
{
id: 'github',
label: 'GitHub',
click: () => shell.openExternal('https://github.com/bitwarden'),
},
];
}
private get followUsSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: "blog",
label: this.localize("blog"),
click: () => shell.openExternal("https://blog.bitwarden.com"),
},
{
id: "twitter",
label: "Twitter",
click: () => shell.openExternal("https://twitter.com/bitwarden"),
},
{
id: "facebook",
label: "Facebook",
click: () => shell.openExternal("https://www.facebook.com/bitwarden/"),
},
{
id: "github",
label: "GitHub",
click: () => shell.openExternal("https://github.com/bitwarden"),
},
];
}
private get goToWebVault(): MenuItemConstructorOptions {
return {
id: 'goToWebVault',
label: this.localize('goToWebVault'),
click: () => shell.openExternal(this._webVaultUrl),
};
}
private get goToWebVault(): MenuItemConstructorOptions {
return {
id: "goToWebVault",
label: this.localize("goToWebVault"),
click: () => shell.openExternal(this._webVaultUrl),
};
}
private get getMobileApp(): MenuItemConstructorOptions {
return {
id: 'getMobileApp',
label: this.localize('getMobileApp'),
visible: !isWindowsStore(),
submenu: this.getMobileAppSubmenu,
};
}
private get getMobileApp(): MenuItemConstructorOptions {
return {
id: "getMobileApp",
label: this.localize("getMobileApp"),
visible: !isWindowsStore(),
submenu: this.getMobileAppSubmenu,
};
}
private get getMobileAppSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'iOS',
label: 'iOS',
click: () => {
shell.openExternal('https://itunes.apple.com/app/' +
'bitwarden-free-password-manager/id1137397744?mt=8');
},
},
{
id: 'android',
label: 'Android',
click: () => {
shell.openExternal('https://play.google.com/store/apps/' +
'details?id=com.x8bit.bitwarden');
},
},
];
}
private get getMobileAppSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: "iOS",
label: "iOS",
click: () => {
shell.openExternal(
"https://itunes.apple.com/app/" + "bitwarden-free-password-manager/id1137397744?mt=8"
);
},
},
{
id: "android",
label: "Android",
click: () => {
shell.openExternal(
"https://play.google.com/store/apps/" + "details?id=com.x8bit.bitwarden"
);
},
},
];
}
private get getBrowserExtension(): MenuItemConstructorOptions {
return {
id: 'getBrowserExtension',
label: this.localize('getBrowserExtension'),
visible: !isWindowsStore(),
submenu: this.getBrowserExtensionSubmenu,
};
}
private get getBrowserExtension(): MenuItemConstructorOptions {
return {
id: "getBrowserExtension",
label: this.localize("getBrowserExtension"),
visible: !isWindowsStore(),
submenu: this.getBrowserExtensionSubmenu,
};
}
private get getBrowserExtensionSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: 'chrome',
label: 'Chrome',
click: () => {
shell.openExternal('https://chrome.google.com/webstore/detail/' +
'bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb');
},
},
{
id: 'firefox',
label: 'Firefox',
click: () => {
shell.openExternal('https://addons.mozilla.org/firefox/addon/' +
'bitwarden-password-manager/');
},
},
{
id: 'firefox',
label: 'Opera',
click: () => {
shell.openExternal('https://addons.opera.com/extensions/details/' +
'bitwarden-free-password-manager/');
},
},
{
id: 'firefox',
label: 'Edge',
click: () => {
shell.openExternal('https://microsoftedge.microsoft.com/addons/' +
'detail/jbkfoedolllekgbhcbcoahefnbanhhlh');
},
},
{
id: 'safari',
label: 'Safari',
click: () => {
shell.openExternal('https://bitwarden.com/download/');
},
},
];
}
private get getBrowserExtensionSubmenu(): MenuItemConstructorOptions[] {
return [
{
id: "chrome",
label: "Chrome",
click: () => {
shell.openExternal(
"https://chrome.google.com/webstore/detail/" +
"bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb"
);
},
},
{
id: "firefox",
label: "Firefox",
click: () => {
shell.openExternal(
"https://addons.mozilla.org/firefox/addon/" + "bitwarden-password-manager/"
);
},
},
{
id: "firefox",
label: "Opera",
click: () => {
shell.openExternal(
"https://addons.opera.com/extensions/details/" + "bitwarden-free-password-manager/"
);
},
},
{
id: "firefox",
label: "Edge",
click: () => {
shell.openExternal(
"https://microsoftedge.microsoft.com/addons/" +
"detail/jbkfoedolllekgbhcbcoahefnbanhhlh"
);
},
},
{
id: "safari",
label: "Safari",
click: () => {
shell.openExternal("https://bitwarden.com/download/");
},
},
];
}
private localize(s: string) {
return this._i18nService.t(s);
}
private localize(s: string) {
return this._i18nService.t(s);
}
}

View File

@@ -1,54 +1,52 @@
import {
app,
Menu,
} from 'electron';
import { app, Menu } from "electron";
import { Main } from '../main';
import { Main } from "../main";
import { BaseMenu } from 'jslib-electron/baseMenu';
import { BaseMenu } from "jslib-electron/baseMenu";
import { MenuUpdateRequest } from './menu.updater';
import { Menubar } from './menubar';
import { MenuUpdateRequest } from "./menu.updater";
import { Menubar } from "./menubar";
const cloudWebVaultUrl: string = 'https://vault.bitwarden.com';
const cloudWebVaultUrl: string = "https://vault.bitwarden.com";
export class MenuMain extends BaseMenu {
constructor(private main: Main) {
super(main.i18nService, main.windowMain);
}
constructor(private main: Main) {
super(main.i18nService, main.windowMain);
}
async init() {
this.initContextMenu();
await this.setMenu();
}
async init() {
this.initContextMenu();
await this.setMenu();
}
async updateApplicationMenuState(updateRequest: MenuUpdateRequest) {
await this.setMenu(updateRequest);
}
async updateApplicationMenuState(updateRequest: MenuUpdateRequest) {
await this.setMenu(updateRequest);
}
private async setMenu(updateRequest?: MenuUpdateRequest) {
Menu.setApplicationMenu(new Menubar(
this.main.i18nService,
this.main.messagingService,
this.main.updaterMain,
this.windowMain,
await this.getWebVaultUrl(),
app.getVersion(),
updateRequest,
).menu);
}
private async setMenu(updateRequest?: MenuUpdateRequest) {
Menu.setApplicationMenu(
new Menubar(
this.main.i18nService,
this.main.messagingService,
this.main.updaterMain,
this.windowMain,
await this.getWebVaultUrl(),
app.getVersion(),
updateRequest
).menu
);
}
private async getWebVaultUrl() {
let webVaultUrl = cloudWebVaultUrl;
const urlsObj: any = await this.main.stateService.getEnvironmentUrls();
if (urlsObj != null) {
if (urlsObj.base != null) {
webVaultUrl = urlsObj.base;
} else if (urlsObj.webVault != null) {
webVaultUrl = urlsObj.webVault;
}
}
return webVaultUrl;
private async getWebVaultUrl() {
let webVaultUrl = cloudWebVaultUrl;
const urlsObj: any = await this.main.stateService.getEnvironmentUrls();
if (urlsObj != null) {
if (urlsObj.base != null) {
webVaultUrl = urlsObj.base;
} else if (urlsObj.webVault != null) {
webVaultUrl = urlsObj.webVault;
}
}
return webVaultUrl;
}
}

View File

@@ -1,12 +1,12 @@
export class MenuUpdateRequest {
hideChangeMasterPassword: boolean;
activeUserId: string;
accounts: { [userId: string]: MenuAccount };
hideChangeMasterPassword: boolean;
activeUserId: string;
accounts: { [userId: string]: MenuAccount };
}
export class MenuAccount {
isAuthenticated: boolean;
isLocked: boolean;
userId: string;
email: string;
isAuthenticated: boolean;
isLocked: boolean;
userId: string;
email: string;
}

View File

@@ -1,140 +1,139 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { IMenubarMenu } from './menubar';
import { IMenubarMenu } from "./menubar";
import { MenuItemConstructorOptions } from 'electron';
import { MenuItemConstructorOptions } from "electron";
export class ViewMenu implements IMenubarMenu {
readonly id: 'viewMenu';
readonly id: "viewMenu";
get label(): string {
return this.localize('view');
}
get label(): string {
return this.localize("view");
}
get items(): MenuItemConstructorOptions[] {
return [
this.searchVault,
this.separator,
this.passwordGenerator,
this.passwordHistory,
this.separator,
this.zoomIn,
this.zoomOut,
this.resetZoom,
this.separator,
this.toggleFullscreen,
this.separator,
this.reload,
this.toggleDevTools,
];
}
get items(): MenuItemConstructorOptions[] {
return [
this.searchVault,
this.separator,
this.passwordGenerator,
this.passwordHistory,
this.separator,
this.zoomIn,
this.zoomOut,
this.resetZoom,
this.separator,
this.toggleFullscreen,
this.separator,
this.reload,
this.toggleDevTools,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _isAuthenticated: boolean;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean,
)
{
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
isAuthenticated: boolean
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._isAuthenticated = isAuthenticated;
}
private get searchVault(): MenuItemConstructorOptions {
return {
id: 'searchVault',
label: this.localize('searchVault'),
click: () => this.sendMessage('focusSearch'),
accelerator: 'CmdOrCtrl+F',
enabled: this._isAuthenticated,
};
}
private get searchVault(): MenuItemConstructorOptions {
return {
id: "searchVault",
label: this.localize("searchVault"),
click: () => this.sendMessage("focusSearch"),
accelerator: "CmdOrCtrl+F",
enabled: this._isAuthenticated,
};
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get separator(): MenuItemConstructorOptions {
return { type: "separator" };
}
private get passwordGenerator(): MenuItemConstructorOptions {
return {
id: 'passwordGenerator',
label: this.localize('passwordGenerator'),
click: () => this.sendMessage('openPasswordGenerator'),
accelerator: 'CmdOrCtrl+G',
enabled: this._isAuthenticated,
};
}
private get passwordGenerator(): MenuItemConstructorOptions {
return {
id: "passwordGenerator",
label: this.localize("passwordGenerator"),
click: () => this.sendMessage("openPasswordGenerator"),
accelerator: "CmdOrCtrl+G",
enabled: this._isAuthenticated,
};
}
private get passwordHistory(): MenuItemConstructorOptions {
return {
id: 'passwordHistory',
label: this.localize('passwordHistory'),
click: () => this.sendMessage('openPasswordHistory'),
enabled: this._isAuthenticated,
};
}
private get passwordHistory(): MenuItemConstructorOptions {
return {
id: "passwordHistory",
label: this.localize("passwordHistory"),
click: () => this.sendMessage("openPasswordHistory"),
enabled: this._isAuthenticated,
};
}
private get zoomIn(): MenuItemConstructorOptions {
return {
id: 'zoomIn',
label: this.localize('zoomIn'),
role: 'zoomIn',
accelerator: 'CmdOrCtrl+=',
};
}
private get zoomIn(): MenuItemConstructorOptions {
return {
id: "zoomIn",
label: this.localize("zoomIn"),
role: "zoomIn",
accelerator: "CmdOrCtrl+=",
};
}
private get zoomOut(): MenuItemConstructorOptions {
return {
id: 'zoomOut',
label: this.localize('zoomOut'),
role: 'zoomOut',
accelerator: 'CmdOrCtrl+-',
};
}
private get zoomOut(): MenuItemConstructorOptions {
return {
id: "zoomOut",
label: this.localize("zoomOut"),
role: "zoomOut",
accelerator: "CmdOrCtrl+-",
};
}
private get resetZoom(): MenuItemConstructorOptions {
return {
id: 'resetZoom',
label: this.localize('resetZoom'),
role: 'resetZoom',
accelerator: 'CmdOrCtrl+0',
};
}
private get resetZoom(): MenuItemConstructorOptions {
return {
id: "resetZoom",
label: this.localize("resetZoom"),
role: "resetZoom",
accelerator: "CmdOrCtrl+0",
};
}
private get toggleFullscreen(): MenuItemConstructorOptions {
return {
id: 'toggleFullScreen',
label: this.localize('toggleFullScreen'),
role: 'togglefullscreen',
};
}
private get toggleFullscreen(): MenuItemConstructorOptions {
return {
id: "toggleFullScreen",
label: this.localize("toggleFullScreen"),
role: "togglefullscreen",
};
}
private get reload(): MenuItemConstructorOptions {
return {
id: 'reload',
label: this.localize('reload'),
role: 'forceReload',
};
}
private get reload(): MenuItemConstructorOptions {
return {
id: "reload",
label: this.localize("reload"),
role: "forceReload",
};
}
private get toggleDevTools(): MenuItemConstructorOptions {
return {
id: 'toggleDevTools',
label: this.localize('toggleDevTools'),
role: 'toggleDevTools',
accelerator: 'F12',
};
}
private get toggleDevTools(): MenuItemConstructorOptions {
return {
id: "toggleDevTools",
label: this.localize("toggleDevTools"),
role: "toggleDevTools",
accelerator: "F12",
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
private sendMessage(message: string) {
this._messagingService.send(message);
}
}

View File

@@ -1,111 +1,111 @@
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { isMacAppStore } from 'jslib-electron/utils';
import { WindowMain } from 'jslib-electron/window.main';
import { isMacAppStore } from "jslib-electron/utils";
import { WindowMain } from "jslib-electron/window.main";
import { IMenubarMenu } from './menubar';
import { IMenubarMenu } from "./menubar";
import { MenuItemConstructorOptions } from 'electron';
import { MenuItemConstructorOptions } from "electron";
export class WindowMenu implements IMenubarMenu {
readonly id: string;
readonly id: string;
get label(): string {
return this.localize('window');
}
get label(): string {
return this.localize("window");
}
get items(): MenuItemConstructorOptions[] {
return [
this.minimize,
this.hideToMenu,
this.alwaysOnTop,
this.zoom,
this.separator,
this.bringAllToFront,
this.close,
];
}
get items(): MenuItemConstructorOptions[] {
return [
this.minimize,
this.hideToMenu,
this.alwaysOnTop,
this.zoom,
this.separator,
this.bringAllToFront,
this.close,
];
}
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _window: WindowMain;
private readonly _i18nService: I18nService;
private readonly _messagingService: MessagingService;
private readonly _window: WindowMain;
constructor(
i18nService: I18nService,
messagingService: MessagingService,
windowMain: WindowMain,
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._window = windowMain;
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
windowMain: WindowMain
) {
this._i18nService = i18nService;
this._messagingService = messagingService;
this._window = windowMain;
}
private get minimize(): MenuItemConstructorOptions {
return {
id: 'minimize',
label: this.localize('minimize'),
role: 'minimize',
visible: isMacAppStore(),
};
}
private get minimize(): MenuItemConstructorOptions {
return {
id: "minimize",
label: this.localize("minimize"),
role: "minimize",
visible: isMacAppStore(),
};
}
private get hideToMenu(): MenuItemConstructorOptions {
return {
id: 'hideToMenu',
label: this.localize(isMacAppStore() ? 'hideToMenuBar' : 'hideToTray'),
click: () => this.sendMessage('hideToTray'),
accelerator: 'CmdOrCtrl+Shift+M',
};
}
private get hideToMenu(): MenuItemConstructorOptions {
return {
id: "hideToMenu",
label: this.localize(isMacAppStore() ? "hideToMenuBar" : "hideToTray"),
click: () => this.sendMessage("hideToTray"),
accelerator: "CmdOrCtrl+Shift+M",
};
}
private get alwaysOnTop(): MenuItemConstructorOptions {
return {
id: 'alwaysOnTop',
label: this.localize('alwaysOnTop'),
type: 'checkbox',
checked: this._window.win.isAlwaysOnTop(),
click: () => this._window.toggleAlwaysOnTop(),
accelerator: 'CmdOrCtrl+Shift+T',
};
}
private get alwaysOnTop(): MenuItemConstructorOptions {
return {
id: "alwaysOnTop",
label: this.localize("alwaysOnTop"),
type: "checkbox",
checked: this._window.win.isAlwaysOnTop(),
click: () => this._window.toggleAlwaysOnTop(),
accelerator: "CmdOrCtrl+Shift+T",
};
}
private get zoom(): MenuItemConstructorOptions {
return {
id: 'zoom',
label: this.localize('zoom'),
role: 'zoom',
visible: isMacAppStore(),
};
}
private get zoom(): MenuItemConstructorOptions {
return {
id: "zoom",
label: this.localize("zoom"),
role: "zoom",
visible: isMacAppStore(),
};
}
private get separator(): MenuItemConstructorOptions {
return { type: 'separator' };
}
private get separator(): MenuItemConstructorOptions {
return { type: "separator" };
}
private get bringAllToFront(): MenuItemConstructorOptions {
return {
id: 'bringAllToFront',
label: this.localize('bringAllToFront'),
role: 'front',
visible: isMacAppStore(),
};
}
private get bringAllToFront(): MenuItemConstructorOptions {
return {
id: "bringAllToFront",
label: this.localize("bringAllToFront"),
role: "front",
visible: isMacAppStore(),
};
}
private get close(): MenuItemConstructorOptions {
return {
id: 'close',
label: this.localize('close'),
role: 'close',
visible: isMacAppStore(),
};
}
private get close(): MenuItemConstructorOptions {
return {
id: "close",
label: this.localize("close"),
role: "close",
visible: isMacAppStore(),
};
}
private localize(s: string) {
return this._i18nService.t(s);
}
private localize(s: string) {
return this._i18nService.t(s);
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
private sendMessage(message: string, args?: any) {
this._messagingService.send(message, args);
}
}

View File

@@ -1,105 +1,90 @@
import {
Menu,
MenuItemConstructorOptions,
} from 'electron';
import { Menu, MenuItemConstructorOptions } from "electron";
import { AboutMenu } from './menu.about';
import { AccountMenu } from './menu.account';
import { BitwardenMenu } from './menu.bitwarden';
import { EditMenu } from './menu.edit';
import { FileMenu } from './menu.file';
import { HelpMenu } from './menu.help';
import { MenuUpdateRequest } from './menu.updater';
import { ViewMenu } from './menu.view';
import { WindowMenu } from './menu.window';
import { AboutMenu } from "./menu.about";
import { AccountMenu } from "./menu.account";
import { BitwardenMenu } from "./menu.bitwarden";
import { EditMenu } from "./menu.edit";
import { FileMenu } from "./menu.file";
import { HelpMenu } from "./menu.help";
import { MenuUpdateRequest } from "./menu.updater";
import { ViewMenu } from "./menu.view";
import { WindowMenu } from "./menu.window";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { UpdaterMain } from 'jslib-electron/updater.main';
import { WindowMain } from 'jslib-electron/window.main';
import { UpdaterMain } from "jslib-electron/updater.main";
import { WindowMain } from "jslib-electron/window.main";
export interface IMenubarMenu {
id: string;
label: string;
visible?: boolean; // Assumes true if null
items: MenuItemConstructorOptions[];
id: string;
label: string;
visible?: boolean; // Assumes true if null
items: MenuItemConstructorOptions[];
}
export class Menubar {
private readonly items: IMenubarMenu[];
private readonly items: IMenubarMenu[];
get menu(): Menu {
const template: MenuItemConstructorOptions[] = [];
if (this.items != null) {
this.items.forEach((item: IMenubarMenu) => {
if (item != null) {
template.push({
id: item.id,
label: item.label,
submenu: item.items,
visible: item.visible ?? true,
});
}
});
get menu(): Menu {
const template: MenuItemConstructorOptions[] = [];
if (this.items != null) {
this.items.forEach((item: IMenubarMenu) => {
if (item != null) {
template.push({
id: item.id,
label: item.label,
submenu: item.items,
visible: item.visible ?? true,
});
}
return Menu.buildFromTemplate(template);
});
}
return Menu.buildFromTemplate(template);
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
updaterMain: UpdaterMain,
windowMain: WindowMain,
webVaultUrl: string,
appVersion: string,
updateRequest?: MenuUpdateRequest,
) {
this.items = [
new BitwardenMenu(
i18nService,
messagingService,
updaterMain,
windowMain.win,
updateRequest?.accounts
),
new FileMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
),
new EditMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
),
new ViewMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
),
new AccountMenu(
i18nService,
messagingService,
webVaultUrl,
windowMain.win,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
),
new WindowMenu(
i18nService,
messagingService,
windowMain,
),
new AboutMenu(
i18nService,
appVersion,
windowMain.win,
updaterMain,
),
new HelpMenu(
i18nService,
webVaultUrl,
),
];
}
constructor(
i18nService: I18nService,
messagingService: MessagingService,
updaterMain: UpdaterMain,
windowMain: WindowMain,
webVaultUrl: string,
appVersion: string,
updateRequest?: MenuUpdateRequest
) {
this.items = [
new BitwardenMenu(
i18nService,
messagingService,
updaterMain,
windowMain.win,
updateRequest?.accounts
),
new FileMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true
),
new EditMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true
),
new ViewMenu(
i18nService,
messagingService,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true
),
new AccountMenu(
i18nService,
messagingService,
webVaultUrl,
windowMain.win,
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true
),
new WindowMenu(i18nService, messagingService, windowMain),
new AboutMenu(i18nService, appVersion, windowMain.win, updaterMain),
new HelpMenu(i18nService, webVaultUrl),
];
}
}

View File

@@ -1,154 +1,157 @@
import { app, ipcMain } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import { app, ipcMain } from "electron";
import * as fs from "fs";
import * as path from "path";
import { Main } from '../main';
import { Main } from "../main";
import { StateService } from 'jslib-common/abstractions/state.service';
import { StateService } from "jslib-common/abstractions/state.service";
import { MenuUpdateRequest } from './menu.updater';
import { MenuUpdateRequest } from "./menu.updater";
const SyncInterval = 5 * 60 * 1000; // 5 minutes
export class MessagingMain {
private syncTimeout: NodeJS.Timer;
private syncTimeout: NodeJS.Timer;
constructor(private main: Main, private stateService: StateService) { }
constructor(private main: Main, private stateService: StateService) {}
init() {
init() {
this.scheduleNextSync();
if (process.platform === "linux") {
this.stateService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile()));
} else {
const loginSettings = app.getLoginItemSettings();
this.stateService.setOpenAtLogin(loginSettings.openAtLogin);
}
ipcMain.on("messagingService", async (event: any, message: any) => this.onMessage(message));
}
onMessage(message: any) {
switch (message.command) {
case "scheduleNextSync":
this.scheduleNextSync();
if (process.platform === 'linux') {
this.stateService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile()));
} else {
const loginSettings = app.getLoginItemSettings();
this.stateService.setOpenAtLogin(loginSettings.openAtLogin);
}
ipcMain.on('messagingService', async (event: any, message: any) => this.onMessage(message));
break;
case "updateAppMenu":
this.main.menuMain.updateApplicationMenuState(message.updateRequest);
this.updateTrayMenu(message.updateRequest);
break;
case "minimizeOnCopy":
this.stateService.getMinimizeOnCopyToClipboard().then((shouldMinimize) => {
if (shouldMinimize && this.main.windowMain.win !== null) {
this.main.windowMain.win.minimize();
}
});
break;
case "showTray":
this.main.trayMain.showTray();
break;
case "removeTray":
this.main.trayMain.removeTray();
break;
case "hideToTray":
this.main.trayMain.hideToTray();
break;
case "addOpenAtLogin":
this.addOpenAtLogin();
break;
case "removeOpenAtLogin":
this.removeOpenAtLogin();
case "setFocus":
this.setFocus();
break;
case "getWindowIsFocused":
this.windowIsFocused();
break;
case "enableBrowserIntegration":
this.main.nativeMessagingMain.generateManifests();
this.main.nativeMessagingMain.listen();
break;
case "disableBrowserIntegration":
this.main.nativeMessagingMain.removeManifests();
this.main.nativeMessagingMain.stop();
break;
default:
break;
}
}
private scheduleNextSync() {
if (this.syncTimeout) {
global.clearTimeout(this.syncTimeout);
}
onMessage(message: any) {
switch (message.command) {
case 'scheduleNextSync':
this.scheduleNextSync();
break;
case 'updateAppMenu':
this.main.menuMain.updateApplicationMenuState(message.updateRequest);
this.updateTrayMenu(message.updateRequest);
break;
case 'minimizeOnCopy':
this.stateService.getMinimizeOnCopyToClipboard().then(
shouldMinimize => {
if (shouldMinimize && this.main.windowMain.win !== null) {
this.main.windowMain.win.minimize();
}
});
break;
case 'showTray':
this.main.trayMain.showTray();
break;
case 'removeTray':
this.main.trayMain.removeTray();
break;
case 'hideToTray':
this.main.trayMain.hideToTray();
break;
case 'addOpenAtLogin':
this.addOpenAtLogin();
break;
case 'removeOpenAtLogin':
this.removeOpenAtLogin();
case 'setFocus':
this.setFocus();
break;
case 'getWindowIsFocused':
this.windowIsFocused();
break;
case 'enableBrowserIntegration':
this.main.nativeMessagingMain.generateManifests();
this.main.nativeMessagingMain.listen();
break;
case 'disableBrowserIntegration':
this.main.nativeMessagingMain.removeManifests();
this.main.nativeMessagingMain.stop();
break;
default:
break;
}
this.syncTimeout = global.setTimeout(() => {
if (this.main.windowMain.win == null) {
return;
}
this.main.windowMain.win.webContents.send("messagingService", {
command: "checkSyncVault",
});
}, SyncInterval);
}
private updateTrayMenu(updateRequest: MenuUpdateRequest) {
if (
this.main.trayMain == null ||
this.main.trayMain.contextMenu == null ||
updateRequest?.activeUserId == null
) {
return;
}
private scheduleNextSync() {
if (this.syncTimeout) {
global.clearTimeout(this.syncTimeout);
}
this.syncTimeout = global.setTimeout(() => {
if (this.main.windowMain.win == null) {
return;
}
this.main.windowMain.win.webContents.send('messagingService', {
command: 'checkSyncVault',
});
}, SyncInterval);
const lockNowTrayMenuItem = this.main.trayMain.contextMenu.getMenuItemById("lockNow");
const activeAccount = updateRequest.accounts[updateRequest.activeUserId];
if (lockNowTrayMenuItem != null && activeAccount != null) {
lockNowTrayMenuItem.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
}
this.main.trayMain.updateContextMenu();
}
private updateTrayMenu(updateRequest: MenuUpdateRequest) {
if (this.main.trayMain == null || this.main.trayMain.contextMenu == null || updateRequest?.activeUserId == null) {
return;
}
const lockNowTrayMenuItem = this.main.trayMain.contextMenu.getMenuItemById('lockNow');
const activeAccount = updateRequest.accounts[updateRequest.activeUserId];
if (lockNowTrayMenuItem != null && activeAccount != null) {
lockNowTrayMenuItem.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
}
this.main.trayMain.updateContextMenu();
}
private addOpenAtLogin() {
if (process.platform === 'linux') {
const data = `[Desktop Entry]
private addOpenAtLogin() {
if (process.platform === "linux") {
const data = `[Desktop Entry]
Type=Application
Version=${app.getVersion()}
Name=Bitwarden
Comment=Bitwarden startup script
Exec=${app.getPath('exe')}
Exec=${app.getPath("exe")}
StartupNotify=false
Terminal=false`;
const dir = path.dirname(this.linuxStartupFile());
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
fs.writeFileSync(this.linuxStartupFile(), data);
} else {
app.setLoginItemSettings({openAtLogin: true});
}
const dir = path.dirname(this.linuxStartupFile());
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
fs.writeFileSync(this.linuxStartupFile(), data);
} else {
app.setLoginItemSettings({ openAtLogin: true });
}
}
private removeOpenAtLogin() {
if (process.platform === 'linux') {
if (fs.existsSync(this.linuxStartupFile())) {
fs.unlinkSync(this.linuxStartupFile());
}
} else {
app.setLoginItemSettings({openAtLogin: false});
}
private removeOpenAtLogin() {
if (process.platform === "linux") {
if (fs.existsSync(this.linuxStartupFile())) {
fs.unlinkSync(this.linuxStartupFile());
}
} else {
app.setLoginItemSettings({ openAtLogin: false });
}
}
private linuxStartupFile(): string {
return path.join(app.getPath('home'), '.config', 'autostart', 'bitwarden.desktop');
}
private linuxStartupFile(): string {
return path.join(app.getPath("home"), ".config", "autostart", "bitwarden.desktop");
}
private setFocus() {
this.main.trayMain.restoreFromTray();
this.main.windowMain.win.focusOnWebView();
}
private setFocus() {
this.main.trayMain.restoreFromTray();
this.main.windowMain.win.focusOnWebView();
}
private windowIsFocused() {
const windowIsFocused = this.main.windowMain.win.isFocused();
this.main.windowMain.win.webContents.send('messagingService', {
command: 'windowIsFocused',
windowIsFocused: windowIsFocused,
});
}
private windowIsFocused() {
const windowIsFocused = this.main.windowMain.win.isFocused();
this.main.windowMain.win.webContents.send("messagingService", {
command: "windowIsFocused",
windowIsFocused: windowIsFocused,
});
}
}

Some files were not shown because too many files have changed in this diff Show More