1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-26 12:25:20 +01: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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
141 changed files with 12454 additions and 10311 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -1,4 +1,5 @@
## Type of change ## Type of change
- [ ] Bug fix - [ ] Bug fix
- [ ] New feature development - [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@ -6,27 +7,26 @@
- [ ] Other - [ ] Other
## Objective ## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding--> <!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes ## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes--> <!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories--> <!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why - **file.ext:** Description of what was changed and why
## Screenshots ## Screenshots
<!--Required for any UI changes. Delete if not applicable--> <!--Required for any UI changes. Delete if not applicable-->
## Testing requirements ## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing--> <!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit ## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required) - [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team) - [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team) - [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@ -128,8 +128,8 @@ jobs:
- name: Install Node dependencies - name: Install Node dependencies
run: npm ci run: npm ci
# - name: Run linter - name: Run linter
# run: npm run lint run: npm run lint
- name: Build application - name: Build application
run: npm run dist:lin run: npm run dist:lin
@ -224,8 +224,8 @@ jobs:
- name: Install Node dependencies - name: Install Node dependencies
run: npm ci run: npm ci
# - name: Run linter - name: Run linter
# run: npm run lint run: npm run lint
- name: Build & Sign (dev) - name: Build & Sign (dev)
env: env:
@ -477,8 +477,8 @@ jobs:
- name: Install Node dependencies - name: Install Node dependencies
run: npm ci run: npm ci
# - name: Run linter - name: Run linter
# run: npm run lint run: npm run lint
- name: Build application (dev) - name: Build application (dev)
run: npm run build run: npm run build

View File

@ -5,7 +5,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: {} inputs: {}
schedule: schedule:
- cron: '0 0 * * 5' - cron: "0 0 * * 5"
jobs: jobs:
crowdin-sync: crowdin-sync:

View File

@ -93,7 +93,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
draft: true draft: true
snap: snap:
name: Deploy Snap name: Deploy Snap
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@ -129,7 +128,6 @@ jobs:
snapcraft upload dist/bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable snapcraft upload dist/bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable
snapcraft logout snapcraft logout
choco: choco:
name: Deploy Choco name: Deploy Choco
runs-on: windows-2019 runs-on: windows-2019

4
.vscode/launch.json vendored
View File

@ -10,9 +10,7 @@
"windows": { "windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
}, },
"args": [ "args": ["."]
"."
]
} }
] ]
} }

View File

@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved: Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one - **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code - **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Report a bug or submit a bugfix:** Use Github issues and pull requests - **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (l10n) section below
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (l10n) section below
## Contributor Agreement ## Contributor Agreement
@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/deskt
## Pull Request Guidelines ## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request - use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch - commit any pull requests against the `master` branch
* include a link to your Community Forums post - include a link to your Community Forums post
# Localization (l10n) # Localization (l10n)

View File

@ -16,8 +16,7 @@ The Bitwarden desktop app is written using Electron and Angular. The application
- [Node.js](https://nodejs.org) v16.13.1 (LTS) or greater - [Node.js](https://nodejs.org) v16.13.1 (LTS) or greater
- NPM v8 - NPM v8
- Windows users: To compile the native node modules used in the app you will need the *Visual C++ toolset*, available through the standard Visual Studio installer. You will also need to install the *Microsoft Build Tools 2015* and *Windows 10 SDK 17134* as additional dependencies in the Visual Studio installer. - Windows users: To compile the native node modules used in the app you will need the _Visual C++ toolset_, available through the standard Visual Studio installer. You will also need to install the _Microsoft Build Tools 2015_ and _Windows 10 SDK 17134_ as additional dependencies in the Visual Studio installer.
**Run the app** **Run the app**
@ -31,7 +30,7 @@ npm run electron
Native Messaging (communication with the browser extension) works by having the browser start a lightweight proxy application baked into our desktop binary. To setup an environment which allows Native Messaging (communication with the browser extension) works by having the browser start a lightweight proxy application baked into our desktop binary. To setup an environment which allows
for easy debugging you will need to build the application for distribution, i.e. `npm run dist:<platform>`, start the dist version and enable desktop integration. This will write some manifests for easy debugging you will need to build the application for distribution, i.e. `npm run dist:<platform>`, start the dist version and enable desktop integration. This will write some manifests
to disk, Consult the [native manifests](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#Manifest_location) documentation for more details of the manifest to disk, Consult the [native manifests](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#Manifest_location) documentation for more details of the manifest
format, and the exact locations for the different platforms. *Note* that disabling the desktop integration will delete the manifests, and the files will need to be updated again. format, and the exact locations for the different platforms. _Note_ that disabling the desktop integration will delete the manifests, and the files will need to be updated again.
The generated manifests are pre-configured with the production ID for the browser extensions. In order to use them with the development builds, the browser extension ID of the development build The generated manifests are pre-configured with the production ID for the browser extensions. In order to use them with the development builds, the browser extension ID of the development build
needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser. needs to be added to the `allowed_extensions` section of the manifest. These IDs are generated by the browser, and can be found in the extension settings within the browser.

View File

@ -27,7 +27,7 @@
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib", "symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
"symlink:mac": "npm run symlink:lin", "symlink:mac": "npm run symlink:lin",
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib", "symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
"lint": "tslint 'src/**/*.ts'", "lint": "tslint 'src/**/*.ts' && prettier --check .",
"lint:fix": "tslint 'src/**/*.ts' --fix", "lint:fix": "tslint 'src/**/*.ts' --fix",
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"", "build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
"build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"",

View File

@ -1,33 +1,40 @@
require('dotenv').config(); require("dotenv").config();
const path = require('path'); const path = require("path");
const fse = require('fs-extra'); const fse = require("fs-extra");
const { notarize } = require('electron-notarize'); const { notarize } = require("electron-notarize");
const { deepAssign } = require('builder-util'); const { deepAssign } = require("builder-util");
exports.default = run; exports.default = run;
async function run(context) { async function run(context) {
console.log('## After sign'); console.log("## After sign");
// console.log(context); // console.log(context);
const appName = context.packager.appInfo.productFilename; const appName = context.packager.appInfo.productFilename;
const appPath = `${context.appOutDir}/${appName}.app`; const appPath = `${context.appOutDir}/${appName}.app`;
const macBuild = context.electronPlatformName === 'darwin'; const macBuild = context.electronPlatformName === "darwin";
const copyPlugIn = ['darwin', 'mas'].includes(context.electronPlatformName); const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName);
if (copyPlugIn) { if (copyPlugIn) {
// Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552 // Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552
const plugIn = path.join(__dirname, '../PlugIns'); const plugIn = path.join(__dirname, "../PlugIns");
if (fse.existsSync(plugIn)) { if (fse.existsSync(plugIn)) {
fse.mkdirSync(path.join(appPath, 'Contents/PlugIns')); fse.mkdirSync(path.join(appPath, "Contents/PlugIns"));
fse.copySync(path.join(plugIn, 'safari.appex'), path.join(appPath, 'Contents/PlugIns/safari.appex')); fse.copySync(
path.join(plugIn, "safari.appex"),
path.join(appPath, "Contents/PlugIns/safari.appex")
);
// Resign to sign safari extension // Resign to sign safari extension
if (context.electronPlatformName === 'mas') { if (context.electronPlatformName === "mas") {
const masBuildOptions = deepAssign({}, context.packager.platformSpecificBuildOptions, context.packager.config.mas); const masBuildOptions = deepAssign(
if (context.targets.some(e => e.name === 'mas-dev')) { {},
context.packager.platformSpecificBuildOptions,
context.packager.config.mas
);
if (context.targets.some((e) => e.name === "mas-dev")) {
deepAssign(masBuildOptions, { deepAssign(masBuildOptions, {
type: 'development', type: "development",
}); });
} }
if (context.packager.packagerOptions.prepackaged == null) { if (context.packager.packagerOptions.prepackaged == null) {
@ -40,11 +47,11 @@ async function run(context) {
} }
if (macBuild) { if (macBuild) {
console.log('### Notarizing ' + appPath); console.log("### Notarizing " + appPath);
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID; const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`; const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
return await notarize({ return await notarize({
appBundleId: 'com.bitwarden.desktop', appBundleId: "com.bitwarden.desktop",
appPath: appPath, appPath: appPath,
appleId: appleId, appleId: appleId,
appleIdPassword: appleIdPassword, appleIdPassword: appleIdPassword,

View File

@ -1,9 +1,6 @@
exports.default = async function (configuration) { exports.default = async function (configuration) {
if ( if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && console.log(`[*] Signing file: ${configuration.path}`);
configuration.path.slice(-4) == ".exe"
) {
console.log(`[*] Signing file: ${configuration.path}`)
require("child_process").execSync( require("child_process").execSync(
`azuresigntool sign -v ` + `azuresigntool sign -v ` +
`-kvu ${process.env.SIGNING_VAULT_URL} ` + `-kvu ${process.env.SIGNING_VAULT_URL} ` +
@ -16,7 +13,7 @@ exports.default = async function(configuration) {
`-tr http://timestamp.digicert.com ` + `-tr http://timestamp.digicert.com ` +
`${configuration.path}`, `${configuration.path}`,
{ {
stdio: "inherit" stdio: "inherit",
} }
); );
} }

View File

@ -4,54 +4,85 @@
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{'selfHostedEnvironment' | i18n}} {{ "selfHostedEnvironment" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label> <label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl" <input
placeholder="{{'ex' | i18n}} https://bitwarden.company.com" appInputVerbatim> id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
placeholder="{{ 'ex' | i18n }} https://bitwarden.company.com"
appInputVerbatim
/>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}} {{ "selfHostedEnvironmentFooter" | i18n }}
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
<button type="button" (click)="toggleCustom()" appA11yTitle="{{'toggleVisibility' | i18n}}"> <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-plus-square-o" [hidden]="showCustom" aria-hidden="true"></i>
<i class="fa fa-minus-square-o" [hidden]="!showCustom" aria-hidden="true"></i> <i class="fa fa-minus-square-o" [hidden]="!showCustom" aria-hidden="true"></i>
{{'customEnvironment' | i18n}} {{ "customEnvironment" | i18n }}
</button> </button>
</div> </div>
<div class="box-content" [hidden]="!showCustom"> <div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label> <label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl" <input
appInputVerbatim> id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
appInputVerbatim
/>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label> <label for="apiUrl">{{ "apiUrl" | i18n }}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" appInputVerbatim> <input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" appInputVerbatim />
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label> <label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl" <input
appInputVerbatim> id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
appInputVerbatim
/>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{'notificationsUrl' | i18n}}</label> <label for="notificationsUrl">{{ "notificationsUrl" | i18n }}</label>
<input id="notificationsUrl" type="text" name="NotificationsUrl" <input
[(ngModel)]="notificationsUrl" appInputVerbatim> id="notificationsUrl"
type="text"
name="NotificationsUrl"
[(ngModel)]="notificationsUrl"
appInputVerbatim
/>
</div> </div>
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label> <label for="iconsUrl">{{ "iconsUrl" | i18n }}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl" appInputVerbatim> <input
id="iconsUrl"
type="text"
name="IconsUrl"
[(ngModel)]="iconsUrl"
appInputVerbatim
/>
</div> </div>
</div> </div>
<div class="box-footer" [hidden]="!showCustom"> <div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}} {{ "customEnvironmentFooter" | i18n }}
</div> </div>
</div> </div>
</div> </div>
@ -59,7 +90,7 @@
<button appBlurClick type="submit" class="primary" appA11yTitle="{{ 'save' | i18n }}"> <button appBlurClick type="submit" class="primary" appA11yTitle="{{ 'save' | i18n }}">
<i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i> <i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i>
</button> </button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div> </div>
</form> </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 { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-environment', selector: "app-environment",
templateUrl: 'environment.component.html', templateUrl: "environment.component.html",
}) })
export class EnvironmentComponent extends BaseEnvironmentComponent { export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, constructor(
i18nService: I18nService) { platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
i18nService: I18nService
) {
super(platformUtilsService, environmentService, i18nService); super(platformUtilsService, environmentService, i18nService);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,53 +1,67 @@
import { import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
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 { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.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({ @Component({
selector: 'app-login', selector: "app-login",
templateUrl: 'login.component.html', templateUrl: "login.component.html",
}) })
export class LoginComponent extends BaseLoginComponent implements OnDestroy { 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, constructor(
syncService: SyncService, private modalService: ModalService, authService: AuthService,
platformUtilsService: PlatformUtilsService, stateService: StateService, router: Router,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService, i18nService: I18nService,
cryptoFunctionService: CryptoFunctionService, private broadcasterService: BroadcasterService, syncService: SyncService,
ngZone: NgZone, private messagingService: MessagingService, private modalService: ModalService,
logService: LogService) { platformUtilsService: PlatformUtilsService,
super(authService, router, platformUtilsService, i18nService, stateService, environmentService, stateService: StateService,
passwordGenerationService, cryptoFunctionService, logService, ngZone); 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 = () => { super.onSuccessfulLogin = () => {
return syncService.fullSync(true); return syncService.fullSync(true);
}; };
@ -58,10 +72,10 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => { this.ngZone.run(() => {
switch (message.command) { switch (message.command) {
case 'windowHidden': case "windowHidden":
this.onWindowHidden(); this.onWindowHidden();
break; break;
case 'windowIsFocused': case "windowIsFocused":
if (this.deferFocus === null) { if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused; this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) { if (!this.deferFocus) {
@ -76,7 +90,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
} }
}); });
}); });
this.messagingService.send('getWindowIsFocused'); this.messagingService.send("getWindowIsFocused");
} }
ngOnDestroy() { ngOnDestroy() {
@ -84,7 +98,10 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
} }
async settings() { async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(EnvironmentComponent, this.environmentModal); const [modal, childComponent] = await this.modalService.openViewRef(
EnvironmentComponent,
this.environmentModal
);
modal.onShown.subscribe(() => { modal.onShown.subscribe(() => {
this.showingModal = true; this.showingModal = true;
@ -105,8 +122,8 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
async submit() { async submit() {
await super.submit(); await super.submit();
if (this.captchaSiteKey) { if (this.captchaSiteKey) {
const content = document.getElementById('content') as HTMLDivElement; const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute('style', 'width:335px'); content.setAttribute("style", "width:335px");
} }
} }
} }

View File

@ -4,64 +4,85 @@
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="premiumTitle"> <div class="box-header" id="premiumTitle">
{{'premiumMembership' | i18n}} {{ "premiumMembership" | i18n }}
</div> </div>
<div class="box-content box-content-padded"> <div class="box-content box-content-padded">
<div *ngIf="!isPremium"> <div *ngIf="!isPremium">
<p class="text-center lead">{{'premiumNotCurrentMember' | i18n}}</p> <p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{'premiumSignUpAndGet' | i18n}}</p> <p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="fa-ul"> <ul class="fa-ul">
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpStorage' | i18n}} {{ "premiumSignUpStorage" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTwoStep' | i18n}} {{ "premiumSignUpTwoStep" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpReports' | i18n}} {{ "premiumSignUpReports" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpTotp' | i18n}} {{ "premiumSignUpTotp" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpSupport' | i18n}} {{ "premiumSignUpSupport" | i18n }}
</li> </li>
<li> <li>
<i class="fa-li fa fa-check text-success" aria-hidden="true"></i> <i class="fa-li fa fa-check text-success" aria-hidden="true"></i>
{{'premiumSignUpFuture' | i18n}} {{ "premiumSignUpFuture" | i18n }}
</li> </li>
</ul> </ul>
<p class="text-center lead no-margin"> <p class="text-center lead no-margin">
{{'premiumPrice' | i18n : (price | currency:'$')}} {{ "premiumPrice" | i18n: (price | currency: "$") }}
</p> </p>
</div> </div>
<div *ngIf="isPremium"> <div *ngIf="isPremium">
<p class="text-center lead">{{'premiumCurrentMember' | i18n}}</p> <p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<p class="text-center">{{'premiumCurrentMemberThanks' | i18n}}</p> <p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium"> <button type="button" class="primary" appBlurClick (click)="manage()" *ngIf="isPremium">
<b>{{'premiumManage' | i18n}}</b> <b>{{ "premiumManage" | i18n }}</b>
</button> </button>
<button #purchaseBtn type="button" class="primary" appBlurClick (click)="purchase()" *ngIf="!isPremium" <button
[disabled]="purchaseBtn.loading"> #purchaseBtn
<b>{{'premiumPurchase' | i18n}}</b> type="button"
class="primary"
appBlurClick
(click)="purchase()"
*ngIf="!isPremium"
[disabled]="purchaseBtn.loading"
>
<b>{{ "premiumPurchase" | i18n }}</b>
</button> </button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right" *ngIf="!isPremium"> <div class="right" *ngIf="!isPremium">
<button #refreshBtn type="button" appBlurClick (click)="refresh()" [disabled]="refreshBtn.loading" <button
appA11yTitle="{{'premiumRefresh' | i18n}}" [appApiAction]="refreshPromise"> #refreshBtn
<i class="fa fa-refresh fa-lg fa-fw" [hidden]="refreshBtn.loading" aria-hidden="true"></i> type="button"
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!refreshBtn.loading" appBlurClick
aria-hidden="true"></i> (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> </button>
</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 { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.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({ @Component({
selector: 'app-premium', selector: "app-premium",
templateUrl: 'premium.component.html', templateUrl: "premium.component.html",
}) })
export class PremiumComponent extends BasePremiumComponent { export class PremiumComponent extends BasePremiumComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, constructor(
apiService: ApiService, logService: LogService, i18nService: I18nService,
stateService: StateService) { platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService,
stateService: StateService
) {
super(i18nService, platformUtilsService, apiService, logService, stateService); super(i18nService, platformUtilsService, apiService, logService, stateService);
} }
} }

View File

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

View File

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

View File

@ -1,16 +1,26 @@
<div id="remove-password-page" *ngIf="!loading"> <div id="remove-password-page" *ngIf="!loading">
<div class="content"> <div class="content">
<h1>{{'removeMasterPassword' | i18n}}</h1> <h1>{{ "removeMasterPassword" | i18n }}</h1>
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p> <p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
<div class="buttons"> <div class="buttons">
<button type="submit" class="btn primary block" [disabled]="actionPromise" appBlurClick <button
(click)="convert()"> type="submit"
<b [hidden]="continuing">{{'removeMasterPassword' | i18n}}</b> 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> <i class="fa fa-spinner fa-spin" [hidden]="!continuing" aria-hidden="true"></i>
</button> </button>
<button type="button" class="btn secondary block" [disabled]="actionPromise" appBlurClick <button
(click)="leave()"> type="button"
<b [hidden]="leaving">{{'leaveOrganization' | i18n}}</b> 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> <i class="fa fa-spinner fa-spin" [hidden]="!leaving" aria-hidden="true"></i>
</button> </button>
</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({ @Component({
selector: 'app-remove-password', selector: "app-remove-password",
templateUrl: 'remove-password.component.html', templateUrl: "remove-password.component.html",
}) })
export class RemovePasswordComponent extends BaseRemovePasswordComponent { export class RemovePasswordComponent extends BaseRemovePasswordComponent {}
}

View File

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

View File

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

View File

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

View File

@ -1,32 +1,29 @@
import { import { Component, OnInit } from "@angular/core";
Component, import { FormControl } from "@angular/forms";
OnInit, import { debounceTime } from "rxjs/operators";
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { DeviceType } from 'jslib-common/enums/deviceType'; import { DeviceType } from "jslib-common/enums/deviceType";
import { ThemeType } from 'jslib-common/enums/themeType'; import { ThemeType } from "jslib-common/enums/themeType";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.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 { Utils } from "jslib-common/misc/utils";
import { isWindowsStore } from 'jslib-electron/utils'; import { isWindowsStore } from "jslib-electron/utils";
import { StorageLocation } from 'jslib-common/enums/storageLocation'; import { StorageLocation } from "jslib-common/enums/storageLocation";
@Component({ @Component({
selector: 'app-settings', selector: "app-settings",
templateUrl: 'settings.component.html', templateUrl: "settings.component.html",
}) })
export class SettingsComponent implements OnInit { export class SettingsComponent implements OnInit {
vaultTimeoutAction: string; vaultTimeoutAction: string;
@ -68,50 +65,55 @@ export class SettingsComponent implements OnInit {
vaultTimeout: FormControl = new FormControl(null); vaultTimeout: FormControl = new FormControl(null);
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, constructor(
private vaultTimeoutService: VaultTimeoutService, private stateService: StateService, private i18nService: I18nService,
private messagingService: MessagingService, private cryptoService: CryptoService, private platformUtilsService: PlatformUtilsService,
private modalService: ModalService) { private vaultTimeoutService: VaultTimeoutService,
private stateService: StateService,
private messagingService: MessagingService,
private cryptoService: CryptoService,
private modalService: ModalService
) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop; const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622 // Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop; this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
const trayKey = isMac ? 'enableMenuBar' : 'enableTray'; const trayKey = isMac ? "enableMenuBar" : "enableTray";
this.enableTrayText = this.i18nService.t(trayKey); this.enableTrayText = this.i18nService.t(trayKey);
this.enableTrayDescText = this.i18nService.t(trayKey + 'Desc'); this.enableTrayDescText = this.i18nService.t(trayKey + "Desc");
const minToTrayKey = isMac ? 'enableMinToMenuBar' : 'enableMinToTray'; const minToTrayKey = isMac ? "enableMinToMenuBar" : "enableMinToTray";
this.enableMinToTrayText = this.i18nService.t(minToTrayKey); this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + 'Desc'); this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + "Desc");
const closeToTrayKey = isMac ? 'enableCloseToMenuBar' : 'enableCloseToTray'; const closeToTrayKey = isMac ? "enableCloseToMenuBar" : "enableCloseToTray";
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey); this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + 'Desc'); this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + "Desc");
const startToTrayKey = isMac ? 'startToMenuBar' : 'startToTray'; const startToTrayKey = isMac ? "startToMenuBar" : "startToTray";
this.startToTrayText = this.i18nService.t(startToTrayKey); this.startToTrayText = this.i18nService.t(startToTrayKey);
this.startToTrayDescText = this.i18nService.t(startToTrayKey + 'Desc'); this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc");
this.vaultTimeouts = [ this.vaultTimeouts = [
// { name: i18nService.t('immediately'), value: 0 }, // { name: i18nService.t('immediately'), value: 0 },
{ name: i18nService.t('oneMinute'), value: 1 }, { name: i18nService.t("oneMinute"), value: 1 },
{ name: i18nService.t('fiveMinutes'), value: 5 }, { name: i18nService.t("fiveMinutes"), value: 5 },
{ name: i18nService.t('fifteenMinutes'), value: 15 }, { name: i18nService.t("fifteenMinutes"), value: 15 },
{ name: i18nService.t('thirtyMinutes'), value: 30 }, { name: i18nService.t("thirtyMinutes"), value: 30 },
{ name: i18nService.t('oneHour'), value: 60 }, { name: i18nService.t("oneHour"), value: 60 },
{ name: i18nService.t('fourHours'), value: 240 }, { name: i18nService.t("fourHours"), value: 240 },
{ name: i18nService.t('onIdle'), value: -4 }, { name: i18nService.t("onIdle"), value: -4 },
{ name: i18nService.t('onSleep'), value: -3 }, { name: i18nService.t("onSleep"), value: -3 },
]; ];
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) { if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
this.vaultTimeouts.push({ name: i18nService.t('onLocked'), value: -2 }); this.vaultTimeouts.push({ name: i18nService.t("onLocked"), value: -2 });
} }
this.vaultTimeouts = this.vaultTimeouts.concat([ this.vaultTimeouts = this.vaultTimeouts.concat([
{ name: i18nService.t('onRestart'), value: -1 }, { name: i18nService.t("onRestart"), value: -1 },
{ name: i18nService.t('never'), value: null }, { name: i18nService.t("never"), value: null },
]); ]);
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => { this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
@ -119,32 +121,32 @@ export class SettingsComponent implements OnInit {
}); });
const localeOptions: any[] = []; const localeOptions: any[] = [];
i18nService.supportedTranslationLocales.forEach(locale => { i18nService.supportedTranslationLocales.forEach((locale) => {
let name = locale; let name = locale;
if (i18nService.localeNames.has(locale)) { if (i18nService.localeNames.has(locale)) {
name += (' - ' + i18nService.localeNames.get(locale)); name += " - " + i18nService.localeNames.get(locale);
} }
localeOptions.push({ name: name, value: locale }); localeOptions.push({ name: name, value: locale });
}); });
localeOptions.sort(Utils.getSortFunction(i18nService, 'name')); localeOptions.sort(Utils.getSortFunction(i18nService, "name"));
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null }); localeOptions.splice(0, 0, { name: i18nService.t("default"), value: null });
this.localeOptions = localeOptions; this.localeOptions = localeOptions;
this.themeOptions = [ this.themeOptions = [
{ name: i18nService.t('default'), value: null }, { name: i18nService.t("default"), value: null },
{ name: i18nService.t('light'), value: ThemeType.Light }, { name: i18nService.t("light"), value: ThemeType.Light },
{ name: i18nService.t('dark'), value: ThemeType.Dark }, { name: i18nService.t("dark"), value: ThemeType.Dark },
{ name: 'Nord', value: ThemeType.Nord }, { name: "Nord", value: ThemeType.Nord },
]; ];
this.clearClipboardOptions = [ this.clearClipboardOptions = [
{ name: i18nService.t('never'), value: null }, { name: i18nService.t("never"), value: null },
{ name: i18nService.t('tenSeconds'), value: 10 }, { name: i18nService.t("tenSeconds"), value: 10 },
{ name: i18nService.t('twentySeconds'), value: 20 }, { name: i18nService.t("twentySeconds"), value: 20 },
{ name: i18nService.t('thirtySeconds'), value: 30 }, { name: i18nService.t("thirtySeconds"), value: 30 },
{ name: i18nService.t('oneMinute'), value: 60 }, { name: i18nService.t("oneMinute"), value: 60 },
{ name: i18nService.t('twoMinutes'), value: 120 }, { name: i18nService.t("twoMinutes"), value: 120 },
{ name: i18nService.t('fiveMinutes'), value: 300 }, { name: i18nService.t("fiveMinutes"), value: 300 },
]; ];
} }
@ -156,7 +158,8 @@ export class SettingsComponent implements OnInit {
this.pin = pinSet[0] || pinSet[1]; this.pin = pinSet[0] || pinSet[1];
this.disableFavicons = await this.stateService.getDisableFavicon(); this.disableFavicons = await this.stateService.getDisableFavicon();
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration(); this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
this.enableBrowserIntegrationFingerprint = await this.stateService.getEnableBrowserIntegrationFingerprint(); this.enableBrowserIntegrationFingerprint =
await this.stateService.getEnableBrowserIntegrationFingerprint();
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray(); this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
this.enableCloseToTray = await this.stateService.getEnableCloseToTray(); this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
this.enableTray = await this.stateService.getEnableTray(); this.enableTray = await this.stateService.getEnableTray();
@ -176,13 +179,16 @@ export class SettingsComponent implements OnInit {
} }
async saveVaultTimeoutOptions() { async saveVaultTimeoutOptions() {
if (this.vaultTimeoutAction === 'logOut') { if (this.vaultTimeoutAction === "logOut") {
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('vaultTimeoutLogOutConfirmation'), this.i18nService.t("vaultTimeoutLogOutConfirmation"),
this.i18nService.t('vaultTimeoutLogOutConfirmationTitle'), this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning'); this.i18nService.t("yes"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) { if (!confirmed) {
this.vaultTimeoutAction = 'lock'; this.vaultTimeoutAction = "lock";
return; return;
} }
} }
@ -193,11 +199,18 @@ export class SettingsComponent implements OnInit {
} }
if (!this.vaultTimeout.valid) { if (!this.vaultTimeout.valid) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('vaultTimeoutTooLarge')); this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("vaultTimeoutTooLarge")
);
return; return;
} }
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction); await this.vaultTimeoutService.setVaultTimeoutOptions(
this.vaultTimeout.value,
this.vaultTimeoutAction
);
} }
async updatePin() { async updatePin() {
@ -252,8 +265,10 @@ export class SettingsComponent implements OnInit {
async saveFavicons() { async saveFavicons() {
await this.stateService.setDisableFavicon(this.disableFavicons); await this.stateService.setDisableFavicon(this.disableFavicons);
await this.stateService.setDisableFavicon(this.disableFavicons, { storageLocation: StorageLocation.Disk }); await this.stateService.setDisableFavicon(this.disableFavicons, {
this.messagingService.send('refreshCiphers'); storageLocation: StorageLocation.Disk,
});
this.messagingService.send("refreshCiphers");
} }
async saveMinToTray() { async saveMinToTray() {
@ -270,10 +285,18 @@ export class SettingsComponent implements OnInit {
} }
async saveTray() { async saveTray() {
if (this.requireEnableTray && !this.enableTray && (this.startToTray || this.enableCloseToTray)) { if (
this.requireEnableTray &&
!this.enableTray &&
(this.startToTray || this.enableCloseToTray)
) {
const confirm = await this.platformUtilsService.showDialog( const confirm = await this.platformUtilsService.showDialog(
this.i18nService.t('confirmTrayDesc'), this.i18nService.t('confirmTrayTitle'), this.i18nService.t("confirmTrayDesc"),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); this.i18nService.t("confirmTrayTitle"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (confirm) { if (confirm) {
this.startToTray = false; this.startToTray = false;
@ -288,7 +311,7 @@ export class SettingsComponent implements OnInit {
} }
await this.stateService.setEnableTray(this.enableTray); await this.stateService.setEnableTray(this.enableTray);
this.messagingService.send(this.enableTray ? 'showTray' : 'removeTray'); this.messagingService.send(this.enableTray ? "showTray" : "removeTray");
} }
async saveStartToTray() { async saveStartToTray() {
@ -323,30 +346,38 @@ export class SettingsComponent implements OnInit {
async saveOpenAtLogin() { async saveOpenAtLogin() {
this.stateService.setOpenAtLogin(this.openAtLogin); this.stateService.setOpenAtLogin(this.openAtLogin);
this.messagingService.send(this.openAtLogin ? 'addOpenAtLogin' : 'removeOpenAtLogin'); this.messagingService.send(this.openAtLogin ? "addOpenAtLogin" : "removeOpenAtLogin");
} }
async saveBrowserIntegration() { async saveBrowserIntegration() {
if (process.platform === 'darwin' && !this.platformUtilsService.isMacAppStore()) { if (process.platform === "darwin" && !this.platformUtilsService.isMacAppStore()) {
await this.platformUtilsService.showDialog( await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationMasOnlyDesc'), this.i18nService.t("browserIntegrationMasOnlyDesc"),
this.i18nService.t('browserIntegrationMasOnlyTitle'), this.i18nService.t("browserIntegrationMasOnlyTitle"),
this.i18nService.t('ok'), null, 'warning'); this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false; this.enableBrowserIntegration = false;
return; return;
} else if (isWindowsStore()) { } else if (isWindowsStore()) {
await this.platformUtilsService.showDialog( await this.platformUtilsService.showDialog(
this.i18nService.t('browserIntegrationWindowsStoreDesc'), this.i18nService.t("browserIntegrationWindowsStoreDesc"),
this.i18nService.t('browserIntegrationWindowsStoreTitle'), this.i18nService.t("browserIntegrationWindowsStoreTitle"),
this.i18nService.t('ok'), null, 'warning'); this.i18nService.t("ok"),
null,
"warning"
);
this.enableBrowserIntegration = false; this.enableBrowserIntegration = false;
return; return;
} }
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration); await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration'); this.messagingService.send(
this.enableBrowserIntegration ? "enableBrowserIntegration" : "disableBrowserIntegration"
);
if (!this.enableBrowserIntegration) { if (!this.enableBrowserIntegration) {
this.enableBrowserIntegrationFingerprint = false; this.enableBrowserIntegrationFingerprint = false;
@ -355,6 +386,8 @@ export class SettingsComponent implements OnInit {
} }
async saveBrowserIntegrationFingerprint() { async saveBrowserIntegrationFingerprint() {
await this.stateService.setEnableBrowserIntegrationFingerprint(this.enableBrowserIntegrationFingerprint); await this.stateService.setEnableBrowserIntegrationFingerprint(
this.enableBrowserIntegrationFingerprint
);
} }
} }

View File

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

View File

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

View File

@ -4,24 +4,29 @@
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="twoStepTitle"> <div class="box-header" id="twoStepTitle">
{{'twoStepOptions' | i18n}} {{ "twoStepOptions" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row" <a
(click)="choose(p)"> href="#"
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right"> 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="text">{{ p.name }}</span>
<span class="detail">{{ p.description }}</span> <span class="detail">{{ p.description }}</span>
</a> </a>
<a href="#" appStopClick class="box-content-row" (click)="recover()"> <a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span> <span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span> <span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

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

View File

@ -1,55 +1,69 @@
import { import { Component, ViewChild, ViewContainerRef } from "@angular/core";
Component,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { import { ActivatedRoute, Router } from "@angular/router";
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 { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.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({ @Component({
selector: 'app-two-factor', selector: "app-two-factor",
templateUrl: 'two-factor.component.html', templateUrl: "two-factor.component.html",
}) })
export class TwoFactorComponent extends BaseTwoFactorComponent { 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, constructor(
i18nService: I18nService, apiService: ApiService, authService: AuthService,
platformUtilsService: PlatformUtilsService, syncService: SyncService, router: Router,
environmentService: EnvironmentService, private modalService: ModalService, i18nService: I18nService,
stateService: StateService, route: ActivatedRoute, apiService: ApiService,
logService: LogService) { platformUtilsService: PlatformUtilsService,
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService, syncService: SyncService,
stateService, route, logService); environmentService: EnvironmentService,
private modalService: ModalService,
stateService: StateService,
route: ActivatedRoute,
logService: LogService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
route,
logService
);
super.onSuccessfulLogin = () => { super.onSuccessfulLogin = () => {
return syncService.fullSync(true); return syncService.fullSync(true);
}; };
} }
async anotherMethod() { async anotherMethod() {
const [modal, childComponent] = await this.modalService.openViewRef(TwoFactorOptionsComponent, this.twoFactorOptionsModal); const [modal, childComponent] = await this.modalService.openViewRef(
TwoFactorOptionsComponent,
this.twoFactorOptionsModal
);
modal.onShown.subscribe(() => { modal.onShown.subscribe(() => {
this.showingModal = true; this.showingModal = true;

View File

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

View File

@ -1,17 +1,17 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.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 { interface MasterPasswordScore {
Color: string; Color: string;
@ -20,46 +20,62 @@ interface MasterPasswordScore {
} }
@Component({ @Component({
selector: 'app-update-temp-password', selector: "app-update-temp-password",
templateUrl: 'update-temp-password.component.html', templateUrl: "update-temp-password.component.html",
}) })
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
get masterPasswordScoreStyle(): MasterPasswordScore { get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) { switch (this.masterPasswordScore) {
case 4: case 4:
return { return {
Color: 'bg-success', Color: "bg-success",
Text: 'strong', Text: "strong",
Width: scoreWidth, Width: scoreWidth,
}; };
case 3: case 3:
return { return {
Color: 'bg-primary', Color: "bg-primary",
Text: 'good', Text: "good",
Width: scoreWidth, Width: scoreWidth,
}; };
case 2: case 2:
return { return {
Color: 'bg-warning', Color: "bg-warning",
Text: 'weak', Text: "weak",
Width: scoreWidth, Width: scoreWidth,
}; };
default: default:
return { return {
Color: 'bg-danger', Color: "bg-danger",
Text: 'weak', Text: "weak",
Width: scoreWidth, Width: scoreWidth,
}; };
} }
} }
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, constructor(
passwordGenerationService: PasswordGenerationService, policyService: PolicyService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, syncService: SyncService, passwordGenerationService: PasswordGenerationService,
logService: LogService, stateService: StateService) { policyService: PolicyService,
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService, cryptoService: CryptoService,
messagingService, apiService, stateService, syncService, logService); 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"> <app-callout type="info" *ngIf="vaultTimeoutPolicy">
{{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}} {{ "vaultTimeoutPolicyInEffect" | i18n: vaultTimeoutPolicyHours:vaultTimeoutPolicyMinutes }}
</app-callout> </app-callout>
<div [formGroup]="form"> <div [formGroup]="form">
<div class="form-group"> <div class="form-group">
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label> <label for="vaultTimeout">{{ "vaultTimeout" | i18n }}</label>
<select id="vaultTimeout" name="VaultTimeout" formControlName="vaultTimeout" class="form-control" appAutofocus> <select
id="vaultTimeout"
name="VaultTimeout"
formControlName="vaultTimeout"
class="form-control"
appAutofocus
>
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option> <option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
</select> </select>
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small> <small class="form-text text-muted">{{ "vaultTimeoutDesc" | i18n }}</small>
</div> </div>
<div class="form-group row" *ngIf="showCustom" formGroupName="custom"> <div class="form-group row" *ngIf="showCustom" formGroupName="custom">
<div class="col"> <div class="col">
<label for="hours">{{'hours' | i18n}}</label> <label for="hours">{{ "hours" | i18n }}</label>
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours"> <input
id="hours"
class="form-control"
type="number"
min="0"
name="hours"
formControlName="hours"
/>
</div> </div>
<div class="col"> <div class="col">
<label for="minutes">{{'minutes' | i18n}}</label> <label for="minutes">{{ "minutes" | i18n }}</label>
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes" formControlName="minutes"> <input
id="minutes"
class="form-control"
type="number"
min="0"
max="59"
name="minutes"
formControlName="minutes"
/>
</div> </div>
</div> </div>
<div class="form-group"></div> <!-- Styling fix --> <div class="form-group"></div>
<!-- Styling fix -->
</div> </div>

View File

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

View File

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

View File

@ -6,61 +6,57 @@ import {
Type, Type,
ViewChild, ViewChild,
ViewContainerRef, ViewContainerRef,
} from '@angular/core'; } from "@angular/core";
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from "@angular/platform-browser";
import { Router } from '@angular/router'; import { Router } from "@angular/router";
import { import { IndividualConfig, ToastrService } from "ngx-toastr";
IndividualConfig,
ToastrService,
} from 'ngx-toastr';
import { PremiumComponent } from './accounts/premium.component'; import { PremiumComponent } from "./accounts/premium.component";
import { SettingsComponent } from './accounts/settings.component'; import { SettingsComponent } from "./accounts/settings.component";
import { PasswordGeneratorHistoryComponent } from './vault/password-generator-history.component'; import { PasswordGeneratorHistoryComponent } from "./vault/password-generator-history.component";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from "jslib-common/abstractions/event.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { NotificationsService } from 'jslib-common/abstractions/notifications.service'; import { NotificationsService } from "jslib-common/abstractions/notifications.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { SettingsService } from 'jslib-common/abstractions/settings.service'; import { SettingsService } from "jslib-common/abstractions/settings.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { SystemService } from 'jslib-common/abstractions/system.service'; import { SystemService } from "jslib-common/abstractions/system.service";
import { TokenService } from 'jslib-common/abstractions/token.service'; import { TokenService } from "jslib-common/abstractions/token.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { CipherType } from 'jslib-common/enums/cipherType'; import { CipherType } from "jslib-common/enums/cipherType";
import { ExportComponent } from './vault/export.component'; import { ExportComponent } from "./vault/export.component";
import { FolderAddEditComponent } from './vault/folder-add-edit.component'; import { FolderAddEditComponent } from "./vault/folder-add-edit.component";
import { PasswordGeneratorComponent } from './vault/password-generator.component'; import { PasswordGeneratorComponent } from "./vault/password-generator.component";
import { ModalRef } from 'jslib-angular/components/modal/modal.ref'; import { ModalRef } from "jslib-angular/components/modal/modal.ref";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { MenuUpdateRequest } from 'src/main/menu.updater'; import { MenuUpdateRequest } from "src/main/menu.updater";
const BroadcasterSubscriptionId = 'AppComponent'; const BroadcasterSubscriptionId = "AppComponent";
const IdleTimeout = 60000 * 10; // 10 minutes const IdleTimeout = 60000 * 10; // 10 minutes
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
@Component({ @Component({
selector: 'app-root', selector: "app-root",
styles: [], styles: [],
template: ` template: ` <ng-template #settings></ng-template>
<ng-template #settings></ng-template>
<ng-template #premium></ng-template> <ng-template #premium></ng-template>
<ng-template #passwordHistory></ng-template> <ng-template #passwordHistory></ng-template>
<ng-template #appFolderAddEdit></ng-template> <ng-template #appFolderAddEdit></ng-template>
@ -70,13 +66,15 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
<div id="container"><router-outlet></router-outlet></div>`, <div id="container"><router-outlet></router-outlet></div>`,
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef; @ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
@ViewChild('premium', { read: ViewContainerRef, static: true }) premiumRef: ViewContainerRef; @ViewChild("premium", { read: ViewContainerRef, static: true }) premiumRef: ViewContainerRef;
@ViewChild('passwordHistory', { read: ViewContainerRef, static: true }) passwordHistoryRef: ViewContainerRef; @ViewChild("passwordHistory", { read: ViewContainerRef, static: true })
@ViewChild('exportVault', { read: ViewContainerRef, static: true }) exportVaultModalRef: ViewContainerRef; passwordHistoryRef: ViewContainerRef;
@ViewChild('appFolderAddEdit', { read: ViewContainerRef, static: true }) @ViewChild("exportVault", { read: ViewContainerRef, static: true })
exportVaultModalRef: ViewContainerRef;
@ViewChild("appFolderAddEdit", { read: ViewContainerRef, static: true })
folderAddEditModalRef: ViewContainerRef; folderAddEditModalRef: ViewContainerRef;
@ViewChild('appPasswordGenerator', { read: ViewContainerRef, static: true }) @ViewChild("appPasswordGenerator", { read: ViewContainerRef, static: true })
passwordGeneratorModalRef: ViewContainerRef; passwordGeneratorModalRef: ViewContainerRef;
private lastActivity: number = null; private lastActivity: number = null;
@ -84,21 +82,35 @@ export class AppComponent implements OnInit {
private idleTimer: number = null; private idleTimer: number = null;
private isIdle = false; private isIdle = false;
constructor(private broadcasterService: BroadcasterService, constructor(
private tokenService: TokenService, private folderService: FolderService, private broadcasterService: BroadcasterService,
private settingsService: SettingsService, private syncService: SyncService, private tokenService: TokenService,
private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService, private folderService: FolderService,
private authService: AuthService, private router: Router, private settingsService: SettingsService,
private toastrService: ToastrService, private i18nService: I18nService, private syncService: SyncService,
private sanitizer: DomSanitizer, private ngZone: NgZone, private passwordGenerationService: PasswordGenerationService,
private cipherService: CipherService,
private authService: AuthService,
private router: Router,
private toastrService: ToastrService,
private i18nService: I18nService,
private sanitizer: DomSanitizer,
private ngZone: NgZone,
private vaultTimeoutService: VaultTimeoutService, private vaultTimeoutService: VaultTimeoutService,
private cryptoService: CryptoService, private logService: LogService, private cryptoService: CryptoService,
private messagingService: MessagingService, private collectionService: CollectionService, private logService: LogService,
private searchService: SearchService, private notificationsService: NotificationsService, private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService, private systemService: SystemService, private collectionService: CollectionService,
private stateService: StateService, private eventService: EventService, private searchService: SearchService,
private policyService: PolicyService, private modalService: ModalService, private notificationsService: NotificationsService,
private keyConnectorService: KeyConnectorService) { } private platformUtilsService: PlatformUtilsService,
private systemService: SystemService,
private stateService: StateService,
private eventService: EventService,
private policyService: PolicyService,
private modalService: ModalService,
private keyConnectorService: KeyConnectorService
) {}
ngOnInit() { ngOnInit() {
this.ngZone.runOutsideAngular(() => { this.ngZone.runOutsideAngular(() => {
@ -117,13 +129,13 @@ export class AppComponent implements OnInit {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
switch (message.command) { switch (message.command) {
case 'loggedIn': case "loggedIn":
case 'unlocked': case "unlocked":
this.notificationsService.updateConnection(); this.notificationsService.updateConnection();
this.updateAppMenu(); this.updateAppMenu();
this.systemService.cancelProcessReload(); this.systemService.cancelProcessReload();
break; break;
case 'loggedOut': case "loggedOut":
if (this.modal != null) { if (this.modal != null) {
this.modal.close(); this.modal.close();
} }
@ -132,101 +144,127 @@ export class AppComponent implements OnInit {
this.systemService.startProcessReload(); this.systemService.startProcessReload();
await this.systemService.clearPendingClipboard(); await this.systemService.clearPendingClipboard();
break; break;
case 'authBlocked': case "authBlocked":
this.router.navigate(['login']); this.router.navigate(["login"]);
break; break;
case 'logout': case "logout":
await this.logOut(!!message.expired, message.userId); await this.logOut(!!message.expired, message.userId);
break; break;
case 'lockVault': case "lockVault":
await this.vaultTimeoutService.lock(true, message.userId); await this.vaultTimeoutService.lock(true, message.userId);
break; break;
case 'lockAllVaults': case "lockAllVaults":
for (const userId in this.stateService.accounts.getValue()) { for (const userId in this.stateService.accounts.getValue()) {
if (userId != null) { if (userId != null) {
await this.vaultTimeoutService.lock(true, userId); await this.vaultTimeoutService.lock(true, userId);
} }
} }
break; break;
case 'locked': case "locked":
if (this.modal != null) { if (this.modal != null) {
this.modal.close(); this.modal.close();
} }
this.updateAppMenu(); this.updateAppMenu();
if (message.userId == null || message.userId === await this.stateService.getUserId()) { if (
this.router.navigate(['lock']); message.userId == null ||
message.userId === (await this.stateService.getUserId())
) {
this.router.navigate(["lock"]);
} }
this.notificationsService.updateConnection(); this.notificationsService.updateConnection();
await this.systemService.clearPendingClipboard(); await this.systemService.clearPendingClipboard();
this.systemService.startProcessReload(); this.systemService.startProcessReload();
break; break;
case 'reloadProcess': case "reloadProcess":
window.location.reload(true); window.location.reload(true);
break; break;
case 'syncStarted': case "syncStarted":
break; break;
case 'syncCompleted': case "syncCompleted":
await this.updateAppMenu(); await this.updateAppMenu();
break; break;
case 'openSettings': case "openSettings":
await this.openModal<SettingsComponent>(SettingsComponent, this.settingsRef); await this.openModal<SettingsComponent>(SettingsComponent, this.settingsRef);
break; break;
case 'openPremium': case "openPremium":
await this.openModal<PremiumComponent>(PremiumComponent, this.premiumRef); await this.openModal<PremiumComponent>(PremiumComponent, this.premiumRef);
break; break;
case 'showFingerprintPhrase': case "showFingerprintPhrase":
const fingerprint = await this.cryptoService.getFingerprint( const fingerprint = await this.cryptoService.getFingerprint(
await this.stateService.getUserId()); await this.stateService.getUserId()
);
const result = await this.platformUtilsService.showDialog( const result = await this.platformUtilsService.showDialog(
this.i18nService.t('yourAccountsFingerprint') + ':\n' + fingerprint.join('-'), this.i18nService.t("yourAccountsFingerprint") + ":\n" + fingerprint.join("-"),
this.i18nService.t('fingerprintPhrase'), this.i18nService.t('learnMore'), this.i18nService.t("fingerprintPhrase"),
this.i18nService.t('close')); this.i18nService.t("learnMore"),
this.i18nService.t("close")
);
if (result) { if (result) {
this.platformUtilsService.launchUri( this.platformUtilsService.launchUri(
'https://help.bitwarden.com/article/fingerprint-phrase/'); "https://help.bitwarden.com/article/fingerprint-phrase/"
);
} }
break; break;
case 'openPasswordHistory': case "openPasswordHistory":
await this.openModal<PasswordGeneratorHistoryComponent>( await this.openModal<PasswordGeneratorHistoryComponent>(
PasswordGeneratorHistoryComponent, this.passwordHistoryRef); PasswordGeneratorHistoryComponent,
this.passwordHistoryRef
);
break; break;
case 'showToast': case "showToast":
this.showToast(message); this.showToast(message);
break; break;
case 'copiedToClipboard': case "copiedToClipboard":
if (!message.clearing) { if (!message.clearing) {
this.systemService.clearClipboard(message.clipboardValue, message.clearMs); this.systemService.clearClipboard(message.clipboardValue, message.clearMs);
} }
break; break;
case 'ssoCallback': case "ssoCallback":
this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } }); this.router.navigate(["sso"], {
queryParams: { code: message.code, state: message.state },
});
break; break;
case 'premiumRequired': case "premiumRequired":
const premiumConfirmed = await this.platformUtilsService.showDialog( const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'), this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t('learnMore'), this.i18nService.t('cancel')); this.i18nService.t("premiumRequired"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
if (premiumConfirmed) { if (premiumConfirmed) {
await this.openModal<PremiumComponent>(PremiumComponent, this.premiumRef); await this.openModal<PremiumComponent>(PremiumComponent, this.premiumRef);
} }
break; break;
case 'emailVerificationRequired': case "emailVerificationRequired":
const emailVerificationConfirmed = await this.platformUtilsService.showDialog( const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('emailVerificationRequiredDesc'), this.i18nService.t("emailVerificationRequiredDesc"),
this.i18nService.t('emailVerificationRequired'), this.i18nService.t("emailVerificationRequired"),
this.i18nService.t('learnMore'), this.i18nService.t('cancel')); this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
if (emailVerificationConfirmed) { if (emailVerificationConfirmed) {
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/create-bitwarden-account/'); this.platformUtilsService.launchUri(
"https://bitwarden.com/help/article/create-bitwarden-account/"
);
} }
break; break;
case 'syncVault': case "syncVault":
try { try {
await this.syncService.fullSync(true, true); await this.syncService.fullSync(true, true);
this.platformUtilsService.showToast('success', null, this.i18nService.t('syncingComplete')); this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("syncingComplete")
);
} catch { } catch {
this.platformUtilsService.showToast('error', null, this.i18nService.t('syncingFailed')); this.platformUtilsService.showToast(
"error",
null,
this.i18nService.t("syncingFailed")
);
} }
break; break;
case 'checkSyncVault': case "checkSyncVault":
try { try {
const lastSync = await this.syncService.getLastSync(); const lastSync = await this.syncService.getLastSync();
let lastSyncAgo = SyncInterval + 1; let lastSyncAgo = SyncInterval + 1;
@ -240,37 +278,37 @@ export class AppComponent implements OnInit {
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} }
this.messagingService.send('scheduleNextSync'); this.messagingService.send("scheduleNextSync");
break; break;
case 'exportVault': case "exportVault":
await this.openExportVault(); await this.openExportVault();
break; break;
case 'newLogin': case "newLogin":
this.routeToVault('add', CipherType.Login); this.routeToVault("add", CipherType.Login);
break; break;
case 'newCard': case "newCard":
this.routeToVault('add', CipherType.Card); this.routeToVault("add", CipherType.Card);
break; break;
case 'newIdentity': case "newIdentity":
this.routeToVault('add', CipherType.Identity); this.routeToVault("add", CipherType.Identity);
break; break;
case 'newSecureNote': case "newSecureNote":
this.routeToVault('add', CipherType.SecureNote); this.routeToVault("add", CipherType.SecureNote);
break; break;
default: default:
break; break;
case 'newFolder': case "newFolder":
await this.addFolder(); await this.addFolder();
break; break;
case 'openPasswordGenerator': case "openPasswordGenerator":
// openPasswordGenerator has extended functionality if called in the vault // openPasswordGenerator has extended functionality if called in the vault
if (!this.router.url.includes('vault')) { if (!this.router.url.includes("vault")) {
await this.openPasswordGenerator(); await this.openPasswordGenerator();
} }
break; break;
case 'convertAccountToKeyConnector': case "convertAccountToKeyConnector":
await this.keyConnectorService.setConvertAccountRequired(true); await this.keyConnectorService.setConvertAccountRequired(true);
this.router.navigate(['/remove-password']); this.router.navigate(["/remove-password"]);
break; break;
} }
}); });
@ -286,7 +324,10 @@ export class AppComponent implements OnInit {
this.modal.close(); this.modal.close();
} }
const [modal, childComponent] = await this.modalService.openViewRef(ExportComponent, this.exportVaultModalRef); const [modal, childComponent] = await this.modalService.openViewRef(
ExportComponent,
this.exportVaultModalRef
);
this.modal = modal; this.modal = modal;
childComponent.onSaved.subscribe(() => { childComponent.onSaved.subscribe(() => {
@ -303,8 +344,11 @@ export class AppComponent implements OnInit {
this.modal.close(); this.modal.close();
} }
const [modal, childComponent] = await this.modalService.openViewRef(FolderAddEditComponent, const [modal, childComponent] = await this.modalService.openViewRef(
this.folderAddEditModalRef, comp => comp.folderId = null); FolderAddEditComponent,
this.folderAddEditModalRef,
(comp) => (comp.folderId = null)
);
this.modal = modal; this.modal = modal;
childComponent.onSavedFolder.subscribe(async () => { childComponent.onSavedFolder.subscribe(async () => {
@ -322,8 +366,11 @@ export class AppComponent implements OnInit {
this.modal.close(); this.modal.close();
} }
[this.modal] = await this.modalService.openViewRef(PasswordGeneratorComponent, this.folderAddEditModalRef, [this.modal] = await this.modalService.openViewRef(
comp => comp.showSelect = false); PasswordGeneratorComponent,
this.folderAddEditModalRef,
(comp) => (comp.showSelect = false)
);
this.modal.onClosed.subscribe(() => { this.modal.onClosed.subscribe(() => {
this.modal = null; this.modal = null;
@ -361,7 +408,7 @@ export class AppComponent implements OnInit {
}; };
} }
this.messagingService.send('updateAppMenu', { updateRequest: updateRequest }); this.messagingService.send("updateAppMenu", { updateRequest: updateRequest });
} }
private async logOut(expired: boolean, userId?: string) { private async logOut(expired: boolean, userId?: string) {
@ -382,12 +429,15 @@ export class AppComponent implements OnInit {
await this.stateService.setBiometricLocked(true, { userId: userId }); await this.stateService.setBiometricLocked(true, { userId: userId });
if (userId == null || userId === await this.stateService.getUserId()) { if (userId == null || userId === (await this.stateService.getUserId())) {
this.searchService.clearIndex(); this.searchService.clearIndex();
this.authService.logOut(async () => { this.authService.logOut(async () => {
if (expired) { if (expired) {
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'), this.platformUtilsService.showToast(
this.i18nService.t('loginExpired')); "warning",
this.i18nService.t("loggedOut"),
this.i18nService.t("loginExpired")
);
} }
}); });
} }
@ -395,15 +445,15 @@ export class AppComponent implements OnInit {
await this.stateService.clean({ userId: userId }); await this.stateService.clean({ userId: userId });
if (this.stateService.activeAccount.getValue() == null) { if (this.stateService.activeAccount.getValue() == null) {
this.router.navigate(['login']); this.router.navigate(["login"]);
} else { } else {
const locked = await this.vaultTimeoutService.isLocked(); const locked = await this.vaultTimeoutService.isLocked();
if (locked) { if (locked) {
this.messagingService.send('locked'); this.messagingService.send("locked");
} else { } else {
this.messagingService.send('unlocked'); this.messagingService.send("unlocked");
this.messagingService.send('syncVault'); this.messagingService.send("syncVault");
this.router.navigate(['vault']); this.router.navigate(["vault"]);
} }
} }
@ -411,7 +461,7 @@ export class AppComponent implements OnInit {
} }
private async recordActivity() { private async recordActivity() {
const now = (new Date()).getTime(); const now = new Date().getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) { if (this.lastActivity != null && now - this.lastActivity < 250) {
return; return;
} }
@ -457,17 +507,19 @@ export class AppComponent implements OnInit {
} }
private showToast(msg: any) { private showToast(msg: any) {
let message = ''; let message = "";
const options: Partial<IndividualConfig> = {}; const options: Partial<IndividualConfig> = {};
if (typeof (msg.text) === 'string') { if (typeof msg.text === "string") {
message = msg.text; message = msg.text;
} else if (msg.text.length === 1) { } else if (msg.text.length === 1) {
message = msg.text[0]; message = msg.text[0];
} else { } else {
msg.text.forEach((t: string) => msg.text.forEach(
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>')); (t: string) =>
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
);
options.enableHtml = true; options.enableHtml = true;
} }
if (msg.options != null) { if (msg.options != null) {
@ -479,12 +531,12 @@ export class AppComponent implements OnInit {
} }
} }
this.toastrService.show(message, msg.title, options, 'toast-' + msg.type); this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
} }
private routeToVault(action: string, cipherType: CipherType) { private routeToVault(action: string, cipherType: CipherType) {
if (!this.router.url.includes('vault')) { if (!this.router.url.includes("vault")) {
this.router.navigate(['/vault'], { this.router.navigate(["/vault"], {
queryParams: { queryParams: {
action: action, action: action,
addType: cipherType, addType: cipherType,

View File

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

View File

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

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({ @Component({
templateUrl: 'password-reprompt.component.html', templateUrl: "password-reprompt.component.html",
}) })
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {} export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

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

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

View File

@ -1,17 +1,12 @@
import { import { animate, style, transition, trigger } from "@angular/animations";
animate, import { Component } from "@angular/core";
style, import { NG_VALUE_ACCESSOR } from "@angular/forms";
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({ @Component({
selector: 'app-verify-master-password', selector: "app-verify-master-password",
templateUrl: 'verify-master-password.component.html', templateUrl: "verify-master-password.component.html",
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -20,11 +15,8 @@ import { VerifyMasterPasswordComponent as BaseComponent } from 'jslib-angular/co
}, },
], ],
animations: [ animations: [
trigger('sent', [ trigger("sent", [
transition(':enter', [ transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
]), ]),
], ],
}) })

View File

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

View File

@ -1,33 +1,36 @@
import { import { animate, state, style, transition, trigger } from "@angular/animations";
animate, import { Component, OnInit } from "@angular/core";
state, import { Router } from "@angular/router";
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 { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.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({ @Component({
selector: 'app-account-switcher', selector: "app-account-switcher",
templateUrl: 'account-switcher.component.html', templateUrl: "account-switcher.component.html",
animations: [ animations: [
trigger('transformPanel', [ trigger("transformPanel", [
state('void', style({ state(
"void",
style({
opacity: 0, opacity: 0,
})), })
transition('void => open', animate('100ms linear', style({ ),
transition(
"void => open",
animate(
"100ms linear",
style({
opacity: 1, opacity: 1,
}))), })
transition('* => void', animate('100ms linear', style({opacity: 0}))), )
),
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
]), ]),
], ],
}) })
@ -40,18 +43,24 @@ export class AccountSwitcherComponent implements OnInit {
return this.accounts != null && Object.keys(this.accounts).length > 0; return this.accounts != null && Object.keys(this.accounts).length > 0;
} }
constructor(private stateService: StateService, private vaultTimeoutService: VaultTimeoutService, constructor(
private messagingService: MessagingService, private router: Router) {} private stateService: StateService,
private vaultTimeoutService: VaultTimeoutService,
private messagingService: MessagingService,
private router: Router
) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.stateService.accounts.subscribe(async accounts => { this.stateService.accounts.subscribe(async (accounts) => {
for (const userId in accounts) { for (const userId in accounts) {
if (userId === await this.stateService.getUserId()) { if (userId === (await this.stateService.getUserId())) {
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active; accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
} else { } else {
accounts[userId].profile.authenticationStatus = await this.vaultTimeoutService.isLocked(userId) ? accounts[userId].profile.authenticationStatus = (await this.vaultTimeoutService.isLocked(
AuthenticationStatus.Locked : userId
AuthenticationStatus.Unlocked; ))
? AuthenticationStatus.Locked
: AuthenticationStatus.Unlocked;
} }
} }
@ -68,11 +77,11 @@ export class AccountSwitcherComponent implements OnInit {
await this.stateService.setActiveUser(userId); await this.stateService.setActiveUser(userId);
const locked = await this.vaultTimeoutService.isLocked(userId); const locked = await this.vaultTimeoutService.isLocked(userId);
if (locked) { if (locked) {
this.messagingService.send('locked', { userId: userId }); this.messagingService.send("locked", { userId: userId });
} else { } else {
this.messagingService.send('unlocked'); this.messagingService.send("unlocked");
this.messagingService.send('syncVault'); this.messagingService.send("syncVault");
this.router.navigate(['vault']); this.router.navigate(["vault"]);
} }
} }
} }

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from "@angular/core";
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from "rxjs";
export type SearchBarState = { export type SearchBarState = {
enabled: boolean; enabled: boolean;
@ -8,12 +8,11 @@ export type SearchBarState = {
@Injectable() @Injectable()
export class SearchBarService { export class SearchBarService {
searchText = new BehaviorSubject<string>(null); searchText = new BehaviorSubject<string>(null);
private _state = { private _state = {
enabled: false, enabled: false,
placeholderText: '', placeholderText: "",
}; };
// tslint:disable-next-line:member-ordering // tslint:disable-next-line:member-ordering

View File

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

View File

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

View File

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

View File

@ -3,10 +3,10 @@
<div class="inner-content" *ngIf="send"> <div class="inner-content" *ngIf="send">
<div class="box"> <div class="box">
<app-callout *ngIf="disableSend"> <app-callout *ngIf="disableSend">
<span>{{'sendDisabledWarning' | i18n}}</span> <span>{{ "sendDisabledWarning" | i18n }}</span>
</app-callout> </app-callout>
<app-callout type="info" *ngIf="disableHideEmail && !disableSend"> <app-callout type="info" *ngIf="disableHideEmail && !disableSend">
{{'sendOptionsPolicyInEffect' | i18n}} {{ "sendOptionsPolicyInEffect" | i18n }}
</app-callout> </app-callout>
</div> </div>
<div class="box"> <div class="box">
@ -15,161 +15,282 @@
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label> <label for="name">{{ "name" | i18n }}</label>
<input id="name" type="text" name="Name" [(ngModel)]="send.name" appAutofocus [readOnly]="disableSend"> <input
id="name"
type="text"
name="Name"
[(ngModel)]="send.name"
appAutofocus
[readOnly]="disableSend"
/>
</div> </div>
<div class="box-content-row box-content-row-radio" *ngIf="!editMode"> <div class="box-content-row box-content-row-radio" *ngIf="!editMode">
<label class="radio-header">{{'whatTypeOfSend' | i18n}}</label> <label class="radio-header">{{ "whatTypeOfSend" | i18n }}</label>
<div class="item" *ngFor="let o of typeOptions"> <div class="item" *ngFor="let o of typeOptions">
<input type="radio" class="radio" [(ngModel)]="send.type" name="Type_{{o.value}}" <input
id="type_{{o.value}}" [value]="o.value" (change)="typeChanged(o)" type="radio"
[checked]="send.type === o.value" [disabled]="disableSend"> 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 }}"> <label class="unstyled" for="type_{{ o.value }}">
{{ o.name }} {{ o.name }}
</label> </label>
</div> </div>
</div> </div>
<div class="box-content-row" appBowRow *ngIf="!editMode && send.type === sendType.File"> <div class="box-content-row" appBowRow *ngIf="!editMode && send.type === sendType.File">
<label for="file">{{'file' | i18n}}</label> <label for="file">{{ "file" | i18n }}</label>
<input type="file" id="file" class="form-control-file" name="file" required [disabled]="disableSend"> <input
type="file"
id="file"
class="form-control-file"
name="file"
required
[disabled]="disableSend"
/>
</div> </div>
<div class="box-content-row" appBowRow *ngIf="editMode && send.type === sendType.File"> <div class="box-content-row" appBowRow *ngIf="editMode && send.type === sendType.File">
<label for="file">{{'file' | i18n}}</label> <label for="file">{{ "file" | i18n }}</label>
<div class="row-main">{{ send.file.fileName }} ({{ send.file.sizeName }})</div> <div class="row-main">{{ send.file.fileName }} ({{ send.file.sizeName }})</div>
</div> </div>
<div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text"> <div class="box-content-row" appBoxRow *ngIf="send.type === sendType.Text">
<label for="text">{{'text' | i18n}}</label> <label for="text">{{ "text" | i18n }}</label>
<textarea id="text" name="text" [(ngModel)]="send.text.text" rows="6" [readOnly]="disableSend"></textarea> <textarea
id="text"
name="text"
[(ngModel)]="send.text.text"
rows="6"
[readOnly]="disableSend"
></textarea>
</div> </div>
</div> </div>
<div class="box-footer" *ngIf="!editMode && send.type === sendType.File"> <div class="box-footer" *ngIf="!editMode && send.type === sendType.File">
{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}} {{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}
</div> </div>
<div class="box-footer" *ngIf="send.type === sendType.Text"> <div class="box-footer" *ngIf="send.type === sendType.Text">
{{'sendTextDesc' | i18n}} {{ "sendTextDesc" | i18n }}
</div> </div>
</div> </div>
<div class="box" *ngIf="send.type === sendType.Text"> <div class="box" *ngIf="send.type === sendType.Text">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideText">{{'textHiddenByDefault' | i18n}}</label> <label for="hideText">{{ "textHiddenByDefault" | i18n }}</label>
<input id="hideText" name="hideText" type="checkbox" [(ngModel)]="send.text.hidden" [disabled]="disableSend"> <input
id="hideText"
name="hideText"
type="checkbox"
[(ngModel)]="send.text.hidden"
[disabled]="disableSend"
/>
</div> </div>
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{'options' | i18n}} {{ "options" | i18n }}
<a class="toggle" href="#" appStopClick appBlurClick role="button" (click)="toggleOptions()"> <a
<i class="fa fa-lg" aria-hidden="true" [ngClass]="{'fa-chevron-down': !showOptions, 'fa-chevron-up': showOptions}"></i> 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> </a>
</div> </div>
</div> </div>
<div [hidden]="!showOptions"> <div [hidden]="!showOptions">
<app-send-efflux-dates <app-send-efflux-dates
[initialDeletionDate]="send.deletionDate" [initialExpirationDate]="send.expirationDate" [initialDeletionDate]="send.deletionDate"
[editMode]="editMode" [disabled]="disableSend" (datesChanged)="setDates($event)"> [initialExpirationDate]="send.expirationDate"
[editMode]="editMode"
[disabled]="disableSend"
(datesChanged)="setDates($event)"
>
</app-send-efflux-dates> </app-send-efflux-dates>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="maxAccessCount">{{'maxAccessCount' | i18n}}</label> <label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</label>
<input id="maxAccessCount" type="number" name="maxAccessCount" [(ngModel)]="send.maxAccessCount" [readOnly]="disableSend"> <input
id="maxAccessCount"
type="number"
name="maxAccessCount"
[(ngModel)]="send.maxAccessCount"
[readOnly]="disableSend"
/>
</div> </div>
</div> </div>
<div class="box-footer" *ngIf="!editMode"> <div class="box-footer" *ngIf="!editMode">
{{'maxAccessCountDesc' | i18n}} {{ "maxAccessCountDesc" | i18n }}
</div> </div>
<div class="box-footer" *ngIf="editMode"> <div class="box-footer" *ngIf="editMode">
<p>{{'maxAccessCountDesc' | i18n}}</p> <p>{{ "maxAccessCountDesc" | i18n }}</p>
{{'currentAccessCount' | i18n}}: <strong>{{send.accessCount}}</strong> {{ "currentAccessCount" | i18n }}: <strong>{{ send.accessCount }}</strong>
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow> <div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main"> <div class="row-main">
<label for="password">{{(hasPassword ? 'newPassword' : 'password') | i18n}}</label> <label for="password">{{
<input id="password" name="password" type="{{showPassword ? 'text' : 'password'}}" (hasPassword ? "newPassword" : "password") | i18n
[(ngModel)]="password" [readOnly]="disableSend" appInputVerbatim> }}</label>
<input
id="password"
name="password"
type="{{ showPassword ? 'text' : 'password' }}"
[(ngModel)]="password"
[readOnly]="disableSend"
appInputVerbatim
/>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick role="button" <a
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()" [disabled]="disableSend"> class="row-btn"
<i class="fa fa-lg" aria-hidden="true" href="#"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i> 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> </a>
</div> </div>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'sendPasswordDesc' | i18n}} {{ "sendPasswordDesc" | i18n }}
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{'notes' | i18n}} {{ "notes" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<textarea id="notes" name="notes" [(ngModel)]="send.notes" rows="6" [readOnly]="disableSend"></textarea> <textarea
id="notes"
name="notes"
[(ngModel)]="send.notes"
rows="6"
[readOnly]="disableSend"
></textarea>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'sendNotesDesc' | i18n}} {{ "sendNotesDesc" | i18n }}
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="hideEmail">{{'hideEmail' | i18n}}</label> <label for="hideEmail">{{ "hideEmail" | i18n }}</label>
<input id="hideEmail" type="checkbox" name="HideEmail" [(ngModel)]="send.hideEmail" <input
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"> id="hideEmail"
type="checkbox"
name="HideEmail"
[(ngModel)]="send.hideEmail"
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend"
/>
</div> </div>
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="disabled">{{'disableSend' | i18n}}</label> <label for="disabled">{{ "disableSend" | i18n }}</label>
<input id="disabled" type="checkbox" name="disabled" [(ngModel)]="send.disabled" [disabled]="disableSend"> <input
id="disabled"
type="checkbox"
name="disabled"
[(ngModel)]="send.disabled"
[disabled]="disableSend"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{'share' | i18n}} {{ "share" | i18n }}
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="editMode"> <div class="box-content-row" appBoxRow *ngIf="editMode">
<label for="link">{{'sendLinkLabel' | i18n}}</label> <label for="link">{{ "sendLinkLabel" | i18n }}</label>
<input id="link" name="link" [ngModel]="link" readonly> <input id="link" name="link" [ngModel]="link" readonly />
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="copyLink">{{'copySendLinkOnSave' | i18n}}</label> <label for="copyLink">{{ "copySendLinkOnSave" | i18n }}</label>
<input id="copyLink" name="copyLink" [(ngModel)]="copyLink" type="checkbox" [disabled]="disableSend"> <input
id="copyLink"
name="copyLink"
[(ngModel)]="copyLink"
type="checkbox"
[disabled]="disableSend"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
<button appBlurClick type="submit" class="primary btn-submit" appA11yTitle="{{'save' | i18n}}" [disabled]="form.loading" *ngIf="!disableSend"> <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> <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> <span><i class="fa fa-save fa-lg fa-fw" aria-hidden="true"></i></span>
</button> </button>
<button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading"> <button appBlurClick type="button" (click)="cancel()" [disabled]="form.loading">
{{'cancel' | i18n}} {{ "cancel" | i18n }}
</button> </button>
<div class="right"> <div class="right">
<button appBlurClick type="button" (click)="copyLinkToClipboard(link)" appA11yTitle="{{'copySendLinkToClipboard' | i18n}}" *ngIf="editMode"> <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> <i class="fa fa-copy fa-lg fa-fw" aria-hidden="true"></i>
</button> </button>
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger" <button
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode"> #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-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> <i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button> </button>
</div> </div>
</div> </div>

View File

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

View File

@ -2,34 +2,54 @@
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow *ngIf="!editMode"> <div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="deletionDate">{{'deletionDate' | i18n}}</label> <label for="deletionDate">{{ "deletionDate" | i18n }}</label>
<select id="deletionDate" name="DeletionDateSelect" formControlName="selectedDeletionDatePreset" required> <select
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{o.name}} id="deletionDate"
</option> name="DeletionDateSelect"
formControlName="selectedDeletionDatePreset"
required
>
<option *ngFor="let o of deletionDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select> </select>
<small class="help-block">{{'deletionDateDesc' | i18n}}</small> <small class="help-block">{{ "deletionDateDesc" | i18n }}</small>
</div> </div>
<div class="box-content-row" *ngIf="selectedDeletionDatePreset.value === 0 || editMode"> <div class="box-content-row" *ngIf="selectedDeletionDatePreset.value === 0 || editMode">
<label *ngIf="editMode" for="deletionDateCustom">{{'deletionDate' | i18n}}</label> <label *ngIf="editMode" for="deletionDateCustom">{{ "deletionDate" | i18n }}</label>
<input id="deletionDateCustom" type="datetime-local" name="deletionDate" <input
formControlName="defaultDeletionDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM"> id="deletionDateCustom"
<small class="help-block" *ngIf="editMode">{{'deletionDateDesc' | i18n}}</small> 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>
<div class="box-content-row" appBoxRow *ngIf="!editMode"> <div class="box-content-row" appBoxRow *ngIf="!editMode">
<label for="expirationDate">{{'expirationDate' | i18n}}</label> <label for="expirationDate">{{ "expirationDate" | i18n }}</label>
<select id="expirationDate" name="expirationDateSelect" formControlName="selectedExpirationDatePreset" required> <select
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{o.name}} id="expirationDate"
</option> name="expirationDateSelect"
formControlName="selectedExpirationDatePreset"
required
>
<option *ngFor="let o of expirationDatePresets" [ngValue]="o.value">{{ o.name }}</option>
</select> </select>
<small class="help-block">{{'expirationDateDesc' | i18n}}</small> <small class="help-block">{{ "expirationDateDesc" | i18n }}</small>
</div> </div>
<div class="box-content-row" *ngIf="selectedExpirationDatePreset.value === 0 || editMode"> <div class="box-content-row" *ngIf="selectedExpirationDatePreset.value === 0 || editMode">
<label *ngIf="editMode" for="expirationDateCustom">{{'expirationDate' | i18n}}</label> <label *ngIf="editMode" for="expirationDateCustom">{{ "expirationDate" | i18n }}</label>
<input id="expirationDateCustom" type="datetime-local" name="expirationDate" <input
formControlName="defaultExpirationDateTime" required placeholder="MM/DD/YYYY HH:MM AM/PM" [readOnly]="disableSend"> id="expirationDateCustom"
<small *ngIf="editMode" class="help-block">{{'expirationDateDesc' | i18n}}</small> 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> </div>
</div> </div>
</ng-container> </ng-container>

View File

@ -1,25 +1,25 @@
import { DatePipe } from '@angular/common'; import { DatePipe } from "@angular/common";
import { import { Component, OnChanges } from "@angular/core";
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 { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-send-efflux-dates', selector: "app-send-efflux-dates",
templateUrl: 'efflux-dates.component.html', templateUrl: "efflux-dates.component.html",
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
}) })
export class EffluxDatesComponent extends BaseEffluxDatesComponent implements OnChanges { export class EffluxDatesComponent extends BaseEffluxDatesComponent implements OnChanges {
constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, constructor(
protected datePipe: DatePipe) { protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected datePipe: DatePipe
) {
super(i18nService, platformUtilsService, datePipe); super(i18nService, platformUtilsService, datePipe);
} }
@ -27,10 +27,13 @@ export class EffluxDatesComponent extends BaseEffluxDatesComponent implements On
ngOnChanges() { ngOnChanges() {
this.selectedExpirationDatePreset.setValue(0); this.selectedExpirationDatePreset.setValue(0);
this.selectedDeletionDatePreset.setValue(0); this.selectedDeletionDatePreset.setValue(0);
this.defaultDeletionDateTime.setValue(this.datePipe.transform(new Date(this.initialDeletionDate), 'yyyy-MM-ddTHH:mm')); this.defaultDeletionDateTime.setValue(
this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm")
);
if (this.initialExpirationDate) { if (this.initialExpirationDate) {
this.defaultExpirationDateTime.setValue( this.defaultExpirationDateTime.setValue(
this.datePipe.transform(new Date(this.initialExpirationDate), 'yyyy-MM-ddTHH:mm')); this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm")
);
} else { } else {
this.defaultExpirationDateTime.setValue(null); this.defaultExpirationDateTime.setValue(null);
} }

View File

@ -3,24 +3,26 @@
<div class="mac-bar"></div> <div class="mac-bar"></div>
<div class="content"> <div class="content">
<div class="inner-content"> <div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2> <h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul> <ul>
<li [ngClass]="{ active: selectedAll }"> <li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick appBlurClick (click)="selectAll()"> <a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allSends' | i18n}} <i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{ "allSends" | i18n }}
</a> </a>
</li> </li>
</ul> </ul>
<h2>{{'types' | i18n}}</h2> <h2>{{ "types" | i18n }}</h2>
<ul> <ul>
<li [ngClass]="{ active: selectedType === sendType.Text }"> <li [ngClass]="{ active: selectedType === sendType.Text }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>&nbsp;{{
"sendTypeText" | i18n
}}
</a> </a>
</li> </li>
<li [ngClass]="{ active: selectedType === sendType.File }"> <li [ngClass]="{ active: selectedType === sendType.File }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-file-o" aria-hidden="true"></i>&nbsp;{{ "sendTypeFile" | i18n }}
</a> </a>
</li> </li>
</ul> </ul>
@ -33,9 +35,15 @@
<div id="items" class="items"> <div id="items" class="items">
<div class="content"> <div class="content">
<div class="list" *ngIf="filteredSends.length"> <div class="list" *ngIf="filteredSends.length">
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)" <a
title="{{'viewItem' | i18n}}" (contextmenu)="viewSendMenu(s)" *ngFor="let s of filteredSends"
[ngClass]="{'active': s.id === sendId}" class="flex-list-item"> 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"> <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> <i class="fa fa-fw fa-lg" [ngClass]="s.type == 0 ? 'fa-file-text-o' : 'fa-file-o'"></i>
</div> </div>
@ -44,26 +52,49 @@
{{ s.name }} {{ s.name }}
<span class="title-badges"> <span class="title-badges">
<ng-container *ngIf="s.disabled"> <ng-container *ngIf="s.disabled">
<i class="fa fa-warning" appStopProp title="{{'disabled' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'disabled' | i18n}}</span> class="fa fa-warning"
appStopProp
title="{{ 'disabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "disabled" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="s.password"> <ng-container *ngIf="s.password">
<i class="fa fa-key" appStopProp title="{{'password' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'password' | i18n}}</span> class="fa fa-key"
appStopProp
title="{{ 'password' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "password" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="s.maxAccessCountReached"> <ng-container *ngIf="s.maxAccessCountReached">
<i class="fa fa-ban" appStopProp title="{{'maxAccessCountReached' | i18n}}" <i
aria-hidden="true"></i> class="fa fa-ban"
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span> appStopProp
title="{{ 'maxAccessCountReached' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="s.expired"> <ng-container *ngIf="s.expired">
<i class="fa fa-clock-o" appStopProp title="{{'expired' | i18n}}" aria-hidden="true"></i> <i
<span class="sr-only">{{'expired' | i18n}}</span> class="fa fa-clock-o"
appStopProp
title="{{ 'expired' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "expired" | i18n }}</span>
</ng-container> </ng-container>
<ng-container *ngIf="s.pendingDelete"> <ng-container *ngIf="s.pendingDelete">
<i class="fa fa-trash" appStopProp title="{{'pendingDeletion' | i18n}}" <i
aria-hidden="true"></i> class="fa fa-trash"
<span class="sr-only">{{'pendingDeletion' | i18n}}</span> appStopProp
title="{{ 'pendingDeletion' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container> </ng-container>
</span> </span>
</div> </div>
@ -75,18 +106,31 @@
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i> <i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
<p>{{'noItemsInList' | i18n}}</p> <p>{{ "noItemsInList" | i18n }}</p>
</ng-container> </ng-container>
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
<button appBlurClick (click)="addSend()" class="block primary" appA11yTitle="{{'addItem' | i18n}}"> <button
appBlurClick
(click)="addSend()"
class="block primary"
appA11yTitle="{{ 'addItem' | i18n }}"
>
<i class="fa fa-plus fa-lg" aria-hidden="true"></i> <i class="fa fa-plus fa-lg" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
<app-send-add-edit id="addEdit" class="details" *ngIf="action == 'add' || action == 'edit'" [sendId]="sendId" [type]="selectedSendType" <app-send-add-edit
(onSavedSend)="savedSend($event)" (onCancelled)="cancel($event)" (onDeletedSend)="deletedSend($event)"></app-send-add-edit> id="addEdit"
class="details"
*ngIf="action == 'add' || action == 'edit'"
[sendId]="sendId"
[type]="selectedSendType"
(onSavedSend)="savedSend($event)"
(onCancelled)="cancel($event)"
(onDeletedSend)="deletedSend($event)"
></app-send-add-edit>
<div class="logo" *ngIf="!action"> <div class="logo" *ngIf="!action">
<div class="content"> <div class="content">
<div class="inner-content"> <div class="inner-content">

View File

@ -1,40 +1,34 @@
import { import { Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
Component,
NgZone,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from 'jslib-common/abstractions/send.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 { SearchBarService } from "../layout/search/search-bar.service";
import { AddEditComponent } from './add-edit.component'; import { AddEditComponent } from "./add-edit.component";
enum Action { enum Action {
None = '', None = "",
Add = 'add', Add = "add",
Edit = 'edit', Edit = "edit",
} }
const BroadcasterSubscriptionId = 'SendComponent'; const BroadcasterSubscriptionId = "SendComponent";
@Component({ @Component({
selector: 'app-send', selector: "app-send",
templateUrl: 'send.component.html', templateUrl: "send.component.html",
}) })
export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy { export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy {
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent; @ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
@ -42,15 +36,29 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
sendId: string; sendId: string;
action: Action = Action.None; action: Action = Action.None;
constructor(sendService: SendService, i18nService: I18nService, constructor(
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, sendService: SendService,
private broadcasterService: BroadcasterService, ngZone: NgZone, i18nService: I18nService,
searchService: SearchService, policyService: PolicyService, platformUtilsService: PlatformUtilsService,
private searchBarService: SearchBarService, logService: LogService) { environmentService: EnvironmentService,
super(sendService, i18nService, platformUtilsService, private broadcasterService: BroadcasterService,
environmentService, ngZone, searchService, ngZone: NgZone,
policyService, logService); searchService: SearchService,
this.searchBarService.searchText.subscribe(searchText => { 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.searchText = searchText;
this.searchTextChanged(); this.searchTextChanged();
}); });
@ -58,13 +66,13 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
async ngOnInit() { async ngOnInit() {
this.searchBarService.setEnabled(true); this.searchBarService.setEnabled(true);
this.searchBarService.setPlaceholderText(this.i18nService.t('searchSends')); this.searchBarService.setPlaceholderText(this.i18nService.t("searchSends"));
super.ngOnInit(); super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => { this.ngZone.run(async () => {
switch (message.command) { switch (message.command) {
case 'syncCompleted': case "syncCompleted":
await this.load(); await this.load();
break; break;
} }
@ -116,17 +124,17 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
} }
get selectedSendType() { get selectedSendType() {
return this.sends.find(s => s.id === this.sendId)?.type; return this.sends.find((s) => s.id === this.sendId)?.type;
} }
viewSendMenu(send: SendView) { viewSendMenu(send: SendView) {
const menu: RendererMenuItem[] = []; const menu: RendererMenuItem[] = [];
menu.push({ menu.push({
label: this.i18nService.t('copyLink'), label: this.i18nService.t("copyLink"),
click: () => this.copy(send), click: () => this.copy(send),
}); });
menu.push({ menu.push({
label: this.i18nService.t('delete'), label: this.i18nService.t("delete"),
click: async () => { click: async () => {
await this.delete(send); await this.delete(send);
await this.deletedSend(send); await this.deletedSend(send);

View File

@ -1,57 +1,60 @@
import { import { APP_INITIALIZER, NgModule } from "@angular/core";
APP_INITIALIZER,
NgModule,
} from '@angular/core';
import { ElectronLogService } from 'jslib-electron/services/electronLog.service'; import { ElectronLogService } from "jslib-electron/services/electronLog.service";
import { ElectronPlatformUtilsService } from 'jslib-electron/services/electronPlatformUtils.service'; import { ElectronPlatformUtilsService } from "jslib-electron/services/electronPlatformUtils.service";
import { ElectronRendererMessagingService } from 'jslib-electron/services/electronRendererMessaging.service'; import { ElectronRendererMessagingService } from "jslib-electron/services/electronRendererMessaging.service";
import { ElectronRendererSecureStorageService } from 'jslib-electron/services/electronRendererSecureStorage.service'; import { ElectronRendererSecureStorageService } from "jslib-electron/services/electronRendererSecureStorage.service";
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service'; import { ElectronRendererStorageService } from "jslib-electron/services/electronRendererStorage.service";
import { I18nService } from '../services/i18n.service'; import { I18nService } from "../services/i18n.service";
import { LoginGuardService } from '../services/loginGuard.service'; import { LoginGuardService } from "../services/loginGuard.service";
import { NativeMessagingService } from '../services/nativeMessaging.service'; import { NativeMessagingService } from "../services/nativeMessaging.service";
import { PasswordRepromptService } from '../services/passwordReprompt.service'; import { PasswordRepromptService } from "../services/passwordReprompt.service";
import { SearchBarService } from './layout/search/search-bar.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 { AuthService } from "jslib-common/services/auth.service";
import { ContainerService } from 'jslib-common/services/container.service'; import { ContainerService } from "jslib-common/services/container.service";
import { EventService } from 'jslib-common/services/event.service'; import { EventService } from "jslib-common/services/event.service";
import { SystemService } from 'jslib-common/services/system.service'; import { SystemService } from "jslib-common/services/system.service";
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.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 { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service'; import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service'; import { EventService as EventServiceAbstraction } from "jslib-common/abstractions/event.service";
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service'; import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service'; import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service'; import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service'; import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service'; import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service'; import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service'; import { SyncService as SyncServiceAbstraction } from "jslib-common/abstractions/sync.service";
import { SystemService as SystemServiceAbstraction } from 'jslib-common/abstractions/system.service'; import { SystemService as SystemServiceAbstraction } from "jslib-common/abstractions/system.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.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 () => { return async () => {
await stateService.init(); await stateService.init();
await environmentService.setUrlsFromStorage(); await environmentService.setUrlsFromStorage();
@ -63,15 +66,15 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
authService.init(); authService.init();
setTimeout(() => notificationsService.init(), 3000); setTimeout(() => notificationsService.init(), 3000);
const htmlEl = window.document.documentElement; const htmlEl = window.document.documentElement;
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString()); htmlEl.classList.add("os_" + platformUtilsService.getDeviceString());
htmlEl.classList.add('locale_' + i18nService.translationLocale); htmlEl.classList.add("locale_" + i18nService.translationLocale);
const theme = await platformUtilsService.getEffectiveTheme(); const theme = await platformUtilsService.getEffectiveTheme();
htmlEl.classList.add('theme_' + theme); htmlEl.classList.add("theme_" + theme);
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => { platformUtilsService.onDefaultSystemThemeChange(async (sysTheme) => {
const bwTheme = await stateService.getTheme(); const bwTheme = await stateService.getTheme();
if (bwTheme == null || bwTheme === ThemeType.System) { if (bwTheme == null || bwTheme === ThemeType.System) {
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark); htmlEl.classList.remove("theme_" + ThemeType.Light, "theme_" + ThemeType.Dark);
htmlEl.classList.add('theme_' + sysTheme); htmlEl.classList.add("theme_" + sysTheme);
} }
}); });
@ -79,9 +82,9 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
const installedVersion = await stateService.getInstalledVersion(); const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion(); const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) { if (installedVersion == null) {
installAction = 'install'; installAction = "install";
} else if (installedVersion !== currentVersion) { } else if (installedVersion !== currentVersion) {
installAction = 'update'; installAction = "update";
} }
if (installAction != null) { if (installAction != null) {
@ -94,16 +97,14 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
} }
@NgModule({ @NgModule({
imports: [ imports: [JslibServicesModule],
JslibServicesModule,
],
declarations: [], declarations: [],
providers: [ providers: [
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
useFactory: initFactory, useFactory: initFactory,
deps: [ deps: [
'WINDOW', "WINDOW",
EnvironmentServiceAbstraction, EnvironmentServiceAbstraction,
SyncServiceAbstraction, SyncServiceAbstraction,
VaultTimeoutServiceAbstraction, VaultTimeoutServiceAbstraction,
@ -120,19 +121,17 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] }, { provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
{ {
provide: PlatformUtilsServiceAbstraction, provide: PlatformUtilsServiceAbstraction,
useFactory: (i18nService: I18nServiceAbstraction, messagingService: MessagingServiceAbstraction, useFactory: (
stateService: StateServiceAbstraction) => new ElectronPlatformUtilsService(i18nService, i18nService: I18nServiceAbstraction,
messagingService, true, stateService), messagingService: MessagingServiceAbstraction,
deps: [ stateService: StateServiceAbstraction
I18nServiceAbstraction, ) => new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService),
MessagingServiceAbstraction, deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StateServiceAbstraction],
StateServiceAbstraction,
],
}, },
{ {
provide: I18nServiceAbstraction, provide: I18nServiceAbstraction,
useFactory: (window: Window) => new I18nService(window.navigator.language, './locales'), useFactory: (window: Window) => new I18nService(window.navigator.language, "./locales"),
deps: [ 'WINDOW' ], deps: ["WINDOW"],
}, },
{ {
provide: MessagingServiceAbstraction, provide: MessagingServiceAbstraction,
@ -140,7 +139,7 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
deps: [BroadcasterServiceAbstraction], deps: [BroadcasterServiceAbstraction],
}, },
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService }, { provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
{ provide: 'SECURE_STORAGE', useClass: ElectronRendererSecureStorageService }, { provide: "SECURE_STORAGE", useClass: ElectronRendererSecureStorageService },
{ {
provide: CryptoServiceAbstraction, provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService, useClass: ElectronCryptoService,
@ -167,13 +166,8 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
{ {
provide: LoginGuardService, provide: LoginGuardService,
useClass: LoginGuardService, useClass: LoginGuardService,
deps: [ deps: [StateServiceAbstraction, PlatformUtilsServiceAbstraction, I18nServiceAbstraction],
StateServiceAbstraction,
PlatformUtilsServiceAbstraction,
I18nServiceAbstraction,
],
}, },
], ],
}) })
export class ServicesModule { export class ServicesModule {}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@
<div class="modal-body"> <div class="modal-body">
<div class="box" *ngIf="cipher && cipher.hasAttachments"> <div class="box" *ngIf="cipher && cipher.hasAttachments">
<div class="box-header" id="attachmentsTitle"> <div class="box-header" id="attachmentsTitle">
{{'attachments' | i18n}} {{ "attachments" | i18n }}
</div> </div>
<div class="box-content no-hover"> <div class="box-content no-hover">
<div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments"> <div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments">
@ -13,13 +13,27 @@
</div> </div>
<small class="row-sub-label">{{ a.sizeName }}</small> <small class="row-sub-label">{{ a.sizeName }}</small>
<div class="action-buttons no-pad"> <div class="action-buttons no-pad">
<button class="row-btn btn" type="button" appStopClick appBlurClick <button
appA11yTitle="{{'delete' | i18n}}" (click)="delete(a)" #deleteBtn class="row-btn btn"
[appApiAction]="deletePromises[a.id]" [disabled]="deleteBtn.loading"> type="button"
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" appStopClick
aria-hidden="true"></i> appBlurClick
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" appA11yTitle="{{ 'delete' | i18n }}"
aria-hidden="true"></i> (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>
</div> </div>
</div> </div>
@ -27,26 +41,35 @@
</div> </div>
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
{{'newAttachment' | i18n}} {{ "newAttachment" | i18n }}
</div> </div>
<div class="box-content no-hover"> <div class="box-content no-hover">
<div class="box-content-row"> <div class="box-content-row">
<label for="file">{{'file' | i18n}}</label> <label for="file">{{ "file" | i18n }}</label>
<input type="file" id="file" name="file" required> <input type="file" id="file" name="file" required />
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'maxFileSize' | i18n}} {{ "maxFileSize" | i18n }}
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" <button
[disabled]="form.loading"> 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-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> <i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button> </button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div> </div>
</form> </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 { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.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({ @Component({
selector: 'app-vault-attachments', selector: "app-vault-attachments",
templateUrl: 'attachments.component.html', templateUrl: "attachments.component.html",
}) })
export class AttachmentsComponent extends BaseAttachmentsComponent { export class AttachmentsComponent extends BaseAttachmentsComponent {
constructor(cipherService: CipherService, i18nService: I18nService, constructor(
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService, cipherService: CipherService,
apiService: ApiService, logService: LogService, i18nService: I18nService,
stateService: StateService) { cryptoService: CryptoService,
super(cipherService, i18nService, cryptoService, platformUtilsService, platformUtilsService: PlatformUtilsService,
apiService, window, logService, stateService); apiService: ApiService,
logService: LogService,
stateService: StateService
) {
super(
cipherService,
i18nService,
cryptoService,
platformUtilsService,
apiService,
window,
logService,
stateService
);
} }
} }

View File

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

View File

@ -1,22 +1,21 @@
import { Component } from '@angular/core'; import { Component } from "@angular/core";
import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component'; import { CiphersComponent as BaseCiphersComponent } from "jslib-angular/components/ciphers.component";
import { SearchService } from 'jslib-common/abstractions/search.service'; 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({ @Component({
selector: 'app-vault-ciphers', selector: "app-vault-ciphers",
templateUrl: 'ciphers.component.html', templateUrl: "ciphers.component.html",
}) })
export class CiphersComponent extends BaseCiphersComponent { export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, searchBarService: SearchBarService) { constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService); super(searchService);
searchBarService.searchText.subscribe(searchText => { searchBarService.searchText.subscribe((searchText) => {
this.searchText = searchText; this.searchText = searchText;
this.search(200); this.search(200);
}); });

View File

@ -4,28 +4,44 @@
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="collectionsTitle"> <div class="box-header" id="collectionsTitle">
{{'collections' | i18n}} {{ "collections" | i18n }}
</div> </div>
<div class="box-content" *ngIf="!collections || !collections.length"> <div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}} {{ "noCollectionsInList" | i18n }}
</div> </div>
<div class="box-content" *ngIf="collections && collections.length"> <div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox" <div
*ngFor="let c of collections; let i = index" appBoxRow> class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index"
appBoxRow
>
<label for="collection_{{ i }}">{{ c.name }}</label> <label for="collection_{{ i }}">{{ c.name }}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked" <input
name="Collection[{{i}}].Checked"> id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" <button
[disabled]="form.loading"> 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-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> <i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button> </button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
</div> </div>
</form> </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 { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-vault-collections', selector: "app-vault-collections",
templateUrl: 'collections.component.html', templateUrl: "collections.component.html",
}) })
export class CollectionsComponent extends BaseCollectionsComponent { export class CollectionsComponent extends BaseCollectionsComponent {
constructor(cipherService: CipherService, i18nService: I18nService, constructor(
collectionService: CollectionService, platformUtilsService: PlatformUtilsService, cipherService: CipherService,
logService: LogService) { i18nService: I18nService,
collectionService: CollectionService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService); super(collectionService, platformUtilsService, i18nService, cipherService, logService);
} }
} }

View File

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

View File

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

View File

@ -8,27 +8,56 @@
</div> </div>
<div class="box-content"> <div class="box-content">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label> <label for="name">{{ "name" | i18n }}</label>
<input id="name" type="text" name="Name" [(ngModel)]="folder.name" <input
[appAutofocus]="!editMode"> id="name"
type="text"
name="Name"
[(ngModel)]="folder.name"
[appAutofocus]="!editMode"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" <button
[disabled]="form.loading"> 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-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> <i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button> </button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
<div class="right"> <div class="right">
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger" <button
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading" #deleteBtn
[appApiAction]="deletePromise"> appBlurClick
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i> type="button"
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" (click)="delete()"
aria-hidden="true"></i> class="danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[appApiAction]="deletePromise"
>
<i
class="fa fa-trash-o fa-lg fa-fw"
[hidden]="deleteBtn.loading"
aria-hidden="true"
></i>
<i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!deleteBtn.loading"
aria-hidden="true"
></i>
</button> </button>
</div> </div>
</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 { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { import { FolderAddEditComponent as BaseFolderAddEditComponent } from "jslib-angular/components/folder-add-edit.component";
FolderAddEditComponent as BaseFolderAddEditComponent,
} from 'jslib-angular/components/folder-add-edit.component';
@Component({ @Component({
selector: 'app-folder-add-edit', selector: "app-folder-add-edit",
templateUrl: 'folder-add-edit.component.html', templateUrl: "folder-add-edit.component.html",
}) })
export class FolderAddEditComponent extends BaseFolderAddEditComponent { export class FolderAddEditComponent extends BaseFolderAddEditComponent {
constructor(folderService: FolderService, i18nService: I18nService, constructor(
platformUtilsService: PlatformUtilsService, logService: LogService) { folderService: FolderService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(folderService, i18nService, platformUtilsService, logService); super(folderService, i18nService, platformUtilsService, logService);
} }
} }

View File

@ -1,98 +1,140 @@
<div class="mac-bar"></div> <div class="mac-bar"></div>
<div class="content"> <div class="content">
<div class="inner-content"> <div class="inner-content">
<h2 class="sr-only">{{'filters' | i18n}}</h2> <h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul> <ul>
<li [ngClass]="{ active: selectedAll }"> <li [ngClass]="{ active: selectedAll }">
<a href="#" appStopClick appBlurClick (click)="selectAll()"> <a href="#" appStopClick appBlurClick (click)="selectAll()">
<i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{'allItems' | i18n}} <i class="fa fa-fw fa-th" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{ active: selectedFavorites }"> <li [ngClass]="{ active: selectedFavorites }">
<a href="#" appStopClick appBlurClick (click)="selectFavorites()"> <a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{'favorites' | i18n}} <i class="fa fa-fw fa-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{ active: selectedTrash }"> <li [ngClass]="{ active: selectedTrash }">
<a href="#" appStopClick appBlurClick (click)="selectTrash()"> <a href="#" appStopClick appBlurClick (click)="selectTrash()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{'trash' | i18n}} <i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</a> </a>
</li> </li>
</ul> </ul>
<h2>{{'types' | i18n}}</h2> <h2>{{ "types" | i18n }}</h2>
<ul> <ul>
<li [ngClass]="{ active: selectedType === cipherType.Login }"> <li [ngClass]="{ active: selectedType === cipherType.Login }">
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)"> <a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
<i class="fa fa-fw fa-globe" aria-hidden="true"></i>&nbsp;{{'typeLogin' | i18n}} <i class="fa fa-fw fa-globe" aria-hidden="true"></i>&nbsp;{{ "typeLogin" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{ active: selectedType === cipherType.Card }"> <li [ngClass]="{ active: selectedType === cipherType.Card }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-credit-card" aria-hidden="true"></i>&nbsp;{{ "typeCard" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{ active: selectedType === cipherType.Identity }"> <li [ngClass]="{ active: selectedType === cipherType.Identity }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-id-card-o" aria-hidden="true"></i>&nbsp;{{ "typeIdentity" | i18n }}
</a> </a>
</li> </li>
<li [ngClass]="{ active: selectedType === cipherType.SecureNote }"> <li [ngClass]="{ active: selectedType === cipherType.SecureNote }">
<a href="#" appStopClick appBlurClick (click)="selectType(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}} <i class="fa fa-fw fa-sticky-note-o" aria-hidden="true"></i>&nbsp;{{
"typeSecureNote" | i18n
}}
</a> </a>
</li> </li>
</ul> </ul>
<p *ngIf="!loaded" class="text-muted">{{'loading' | i18n}}</p> <p *ngIf="!loaded" class="text-muted">{{ "loading" | i18n }}</p>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<div class="heading"> <div class="heading">
<h2>{{'folders' | i18n}}</h2> <h2>{{ "folders" | i18n }}</h2>
<button appBlurClick (click)="addFolder()" appA11yTitle="{{ 'addFolder' | i18n }}"> <button appBlurClick (click)="addFolder()" appA11yTitle="{{ 'addFolder' | i18n }}">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> <i class="fa fa-plus fa-fw" aria-hidden="true"></i>
</button> </button>
</div> </div>
<ul> <ul>
<ng-template #recursiveFolders let-folders> <ng-template #recursiveFolders let-folders>
<li *ngFor="let f of folders" <li
[ngClass]="{active: selectedFolder && f.node.id === selectedFolderId}"> *ngFor="let f of folders"
[ngClass]="{ active: selectedFolder && f.node.id === selectedFolderId }"
>
<a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)"> <a href="#" appStopClick appBlurClick (click)="selectFolder(f.node)">
<i *ngIf="f.children.length" class="fa-fw fa" title="{{'toggleCollapse' | i18n}}" aria-hidden="true" <i
[ngClass]="{'fa-caret-right': isCollapsed(f.node), 'fa-caret-down': !isCollapsed(f.node)}" *ngIf="f.children.length"
(click)="collapse(f.node)" appStopProp></i> class="fa-fw fa"
<i *ngIf="f.children.length === 0" class="fa-fw fa fa-folder-o" aria-hidden="true"></i> 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 }} &nbsp;{{ f.node.name }}
<span appStopProp appStopClick (click)="editFolder(f.node)" role="button" <span
appA11yTitle="{{'editFolder' | i18n}}" *ngIf="f.node.id"> 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> <i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
</span> </span>
</a> </a>
<ul class="fa-ul" *ngIf="f.children.length && !isCollapsed(f.node)"> <ul class="fa-ul" *ngIf="f.children.length && !isCollapsed(f.node)">
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: f.children }"> <ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }"
>
</ng-container> </ng-container>
</ul> </ul>
</li> </li>
</ng-template> </ng-template>
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: nestedFolders }"></ng-container> <ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
></ng-container>
</ul> </ul>
<div *ngIf="collections && collections.length"> <div *ngIf="collections && collections.length">
<h2>{{'collections' | i18n}}</h2> <h2>{{ "collections" | i18n }}</h2>
<ul> <ul>
<ng-template #recursiveCollections let-collections> <ng-template #recursiveCollections let-collections>
<li *ngFor="let c of collections" [ngClass]="{active: c.node.id === selectedCollectionId}"> <li
*ngFor="let c of collections"
[ngClass]="{ active: c.node.id === selectedCollectionId }"
>
<a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)"> <a href="#" appStopClick appBlurClick (click)="selectCollection(c.node)">
<i *ngIf="c.children.length" class="fa-fw fa" title="{{'toggleCollapse' | i18n}}" aria-hidden="true" <i
[ngClass]="{'fa-caret-right': isCollapsed(c.node), 'fa-caret-down': !isCollapsed(c.node)}" *ngIf="c.children.length"
(click)="collapse(c.node)" appStopProp></i> 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> <i *ngIf="c.children.length === 0" class="fa-fw fa fa-cube" aria-hidden="true"></i>
&nbsp;{{ c.node.name }} &nbsp;{{ c.node.name }}
</a> </a>
<ul class="fa-ul" *ngIf="c.children.length && !isCollapsed(c.node)"> <ul class="fa-ul" *ngIf="c.children.length && !isCollapsed(c.node)">
<ng-container <ng-container
*ngTemplateOutlet="recursiveCollections; context:{ $implicit: c.children }"> *ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
>
</ng-container> </ng-container>
</ul> </ul>
</li> </li>
</ng-template> </ng-template>
<ng-container *ngTemplateOutlet="recursiveCollections; context:{ $implicit: nestedCollections }"> <ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
>
</ng-container> </ng-container>
</ul> </ul>
</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 { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { StateService } from 'jslib-common/abstractions/state.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({ @Component({
selector: 'app-vault-groupings', selector: "app-vault-groupings",
templateUrl: 'groupings.component.html', templateUrl: "groupings.component.html",
}) })
export class GroupingsComponent extends BaseGroupingsComponent { export class GroupingsComponent extends BaseGroupingsComponent {
constructor(collectionService: CollectionService, folderService: FolderService, constructor(
stateService: StateService) { collectionService: CollectionService,
folderService: FolderService,
stateService: StateService
) {
super(collectionService, folderService, stateService); super(collectionService, folderService, stateService);
} }
} }

View File

@ -4,33 +4,47 @@
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="passwordGenHistoryTitle"> <div class="box-header" id="passwordGenHistoryTitle">
{{'passwordHistory' | i18n}} {{ "passwordHistory" | i18n }}
</div> </div>
<div class="box-content condensed"> <div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history"> <div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main"> <div class="row-main">
<div class="password-wrapper monospaced" appSelectCopy <div
[innerHTML]="h.password | colorPassword"></div> class="password-wrapper monospaced"
<span class="detail">{{h.date | date:'medium'}}</span> appSelectCopy
[innerHTML]="h.password | colorPassword"
></div>
<span class="detail">{{ h.date | date: "medium" }}</span>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}" <a
(click)="copy(h.password)" role="button"> 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> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a> </a>
</div> </div>
</div> </div>
<div class="box-content-row" *ngIf="!history.length"> <div class="box-content-row" *ngIf="!history.length">
{{'noPasswordsInList' | i18n}} {{ "noPasswordsInList" | i18n }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right"> <div class="right">
<button appBlurClick type="button" (click)="clear()" class="danger" <button
appA11yTitle="{{'clear' | i18n}}"> appBlurClick
type="button"
(click)="clear()"
class="danger"
appA11yTitle="{{ 'clear' | i18n }}"
>
<i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i> <i class="fa fa-trash-o fa-lg fa-fw" aria-hidden="true"></i>
</button> </button>
</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 { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component";
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
} from 'jslib-angular/components/password-generator-history.component';
@Component({ @Component({
selector: 'app-password-generator-history', selector: "app-password-generator-history",
templateUrl: 'password-generator-history.component.html', templateUrl: "password-generator-history.component.html",
}) })
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService) { passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(passwordGenerationService, platformUtilsService, i18nService, window); super(passwordGenerationService, platformUtilsService, i18nService, window);
} }
} }

View File

@ -1,9 +1,14 @@
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{'generatePassword' | i18n}}"> <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-dialog modal-sm" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"> <div class="modal-body">
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()"> <app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{'passwordGeneratorPolicyInEffect' | i18n}} {{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout> </app-callout>
<div class="password-block"> <div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div> <div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
@ -11,29 +16,45 @@
<div class="box"> <div class="box">
<div class="box-content condensed"> <div class="box-content condensed">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()"> <a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()">
<i class="fa fa-fw fa-refresh" aria-hidden="true"></i> {{'regeneratePassword' | i18n}} <i class="fa fa-fw fa-refresh" aria-hidden="true"></i>
{{ "regeneratePassword" | i18n }}
</a> </a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="copy()"> <a class="box-content-row" href="#" appStopClick appBlurClick (click)="copy()">
<i class="fa fa-fw fa-clone" aria-hidden="true"></i> {{'copyPassword' | i18n}} <i class="fa fa-fw fa-clone" aria-hidden="true"></i> {{ "copyPassword" | i18n }}
</a> </a>
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
<button type="button" (click)="toggleOptions()" appA11yTitle="{{'toggleVisibility' | i18n}}"> <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-plus-square-o" aria-hidden="true" [hidden]="showOptions"></i>
<i class="fa fa-minus-square-o" aria-hidden="true" [hidden]="!showOptions"></i> <i class="fa fa-minus-square-o" aria-hidden="true" [hidden]="!showOptions"></i>
{{'options' | i18n}} {{ "options" | i18n }}
</button> </button>
</div> </div>
<div class="box-content condensed" [hidden]="!showOptions"> <div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio"> <div class="box-content-row box-content-row-radio">
<label class="sr-only radio-header">{{'type' | i18n}}</label> <label class="sr-only radio-header">{{ "type" | i18n }}</label>
<div class="radio-group text-default" appBoxRow name="PassTypeOptions" <div
*ngFor="let o of passTypeOptions"> class="radio-group text-default"
<input type="radio" class="radio" [(ngModel)]="options.type" name="Type_{{o.value}}" appBoxRow
id="type_{{o.value}}" [value]="o.value" (change)="saveOptions()" name="PassTypeOptions"
[checked]="options.type === o.value"> *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 }}"> <label class="unstyled" for="type_{{ o.value }}">
{{ o.name }} {{ o.name }}
</label> </label>
@ -44,24 +65,45 @@
<div class="box" [hidden]="!showOptions" *ngIf="options.type === 'passphrase'"> <div class="box" [hidden]="!showOptions" *ngIf="options.type === 'passphrase'">
<div class="box-content condensed"> <div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow> <div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{'numWords' | i18n}}</label> <label for="num-words">{{ "numWords" | i18n }}</label>
<input id="num-words" type="number" min="3" max="20" (blur)="saveOptions()" <input
[(ngModel)]="options.numWords"> id="num-words"
type="number"
min="3"
max="20"
(blur)="saveOptions()"
[(ngModel)]="options.numWords"
/>
</div> </div>
<div class="box-content-row box-content-row-input" appBoxRow> <div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{'wordSeparator' | i18n}}</label> <label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input id="word-separator" type="text" maxlength="1" (input)="saveOptions()" <input
[(ngModel)]="options.wordSeparator"> id="word-separator"
type="text"
maxlength="1"
(input)="saveOptions()"
[(ngModel)]="options.wordSeparator"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{'capitalize' | i18n}}</label> <label for="capitalize">{{ "capitalize" | i18n }}</label>
<input id="capitalize" type="checkbox" (change)="saveOptions()" <input
[(ngModel)]="options.capitalize" [disabled]="enforcedPolicyOptions?.capitalize"> id="capitalize"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.capitalize"
[disabled]="enforcedPolicyOptions?.capitalize"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{'includeNumber' | i18n}}</label> <label for="include-number">{{ "includeNumber" | i18n }}</label>
<input id="include-number" type="checkbox" (change)="saveOptions()" <input
[(ngModel)]="options.includeNumber" [disabled]="enforcedPolicyOptions?.includeNumber"> id="include-number"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.includeNumber"
[disabled]="enforcedPolicyOptions?.includeNumber"
/>
</div> </div>
</div> </div>
</div> </div>
@ -69,61 +111,119 @@
<div class="box" [hidden]="!showOptions"> <div class="box" [hidden]="!showOptions">
<div class="box-content condensed"> <div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow> <div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{'length' | i18n}}</label> <label for="length">{{ "length" | i18n }}</label>
<input id="length" type="number" min="5" max="128" [(ngModel)]="options.length" <input
(blur)="saveOptions()"> id="length"
<input id="lengthRange" type="range" min="5" max="128" step="1" type="number"
[(ngModel)]="options.length" (change)="sliderChanged()" (input)="sliderInput()"> 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>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label> <label for="uppercase">A-Z</label>
<input id="uppercase" type="checkbox" (change)="saveOptions()" <input
[disabled]="enforcedPolicyOptions?.useUppercase" [(ngModel)]="options.uppercase"> id="uppercase"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useUppercase"
[(ngModel)]="options.uppercase"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label> <label for="lowercase">a-z</label>
<input id="lowercase" type="checkbox" (change)="saveOptions()" <input
[disabled]="enforcedPolicyOptions?.useLowercase" [(ngModel)]="options.lowercase"> id="lowercase"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useLowercase"
[(ngModel)]="options.lowercase"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label> <label for="numbers">0-9</label>
<input id="numbers" type="checkbox" (change)="saveOptions()" <input
[disabled]="enforcedPolicyOptions?.useNumbers" [(ngModel)]="options.number"> id="numbers"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useNumbers"
[(ngModel)]="options.number"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label> <label for="special">!@#$%^&*</label>
<input id="special" type="checkbox" (change)="saveOptions()" <input
[disabled]="enforcedPolicyOptions?.useSpecial" [(ngModel)]="options.special"> id="special"
type="checkbox"
(change)="saveOptions()"
[disabled]="enforcedPolicyOptions?.useSpecial"
[(ngModel)]="options.special"
/>
</div> </div>
</div> </div>
</div> </div>
<div class="box" [hidden]="!showOptions"> <div class="box" [hidden]="!showOptions">
<div class="box-content condensed"> <div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow> <div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{'minNumbers' | i18n}}</label> <label for="min-number">{{ "minNumbers" | i18n }}</label>
<input id="min-number" type="number" min="0" max="9" (blur)="saveOptions()" <input
[(ngModel)]="options.minNumber"> id="min-number"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minNumber"
/>
</div> </div>
<div class="box-content-row box-content-row-input" appBoxRow> <div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{'minSpecial' | i18n}}</label> <label for="min-special">{{ "minSpecial" | i18n }}</label>
<input id="min-special" type="number" min="0" max="9" (blur)="saveOptions()" <input
[(ngModel)]="options.minSpecial"> id="min-special"
type="number"
min="0"
max="9"
(blur)="saveOptions()"
[(ngModel)]="options.minSpecial"
/>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{'ambiguous' | i18n}}</label> <label for="ambiguous">{{ "ambiguous" | i18n }}</label>
<input id="ambiguous" type="checkbox" (change)="saveOptions()" <input
[(ngModel)]="avoidAmbiguous"> id="ambiguous"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="avoidAmbiguous"
/>
</div> </div>
</div> </div>
</div> </div>
</ng-container> </ng-container>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="primary" appBlurClick *ngIf="showSelect" (click)="select()" <button
appA11yTitle="{{'select' | i18n}}"> 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> <i class="fa fa-lg fa-fw fa-check" aria-hidden="true"></i>
</button> </button>
<button type="button" data-dismiss="modal">{{(showSelect ? 'cancel' : 'close') | i18n}}</button> <button type="button" data-dismiss="modal">
{{ (showSelect ? "cancel" : "close") | i18n }}
</button>
</div> </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 { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
PasswordGeneratorComponent as BasePasswordGeneratorComponent,
} from 'jslib-angular/components/password-generator.component';
@Component({ @Component({
selector: 'app-password-generator', selector: "app-password-generator",
templateUrl: 'password-generator.component.html', templateUrl: "password-generator.component.html",
}) })
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent { export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService) { passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(passwordGenerationService, platformUtilsService, i18nService, window); super(passwordGenerationService, platformUtilsService, i18nService, window);
} }
} }

View File

@ -4,7 +4,7 @@
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="passwordHistoryTitle"> <div class="box-header" id="passwordHistoryTitle">
{{'passwordHistory' | i18n}} {{ "passwordHistory" | i18n }}
</div> </div>
<div class="box-content condensed"> <div class="box-content condensed">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history"> <div class="box-content-row box-content-row-flex" *ngFor="let h of history">
@ -12,23 +12,29 @@
<span class="text monospaced"> <span class="text monospaced">
{{ h.password }} {{ h.password }}
</span> </span>
<span class="detail">{{h.lastUsedDate | date:'medium'}}</span> <span class="detail">{{ h.lastUsedDate | date: "medium" }}</span>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}" <a
(click)="copy(h.password)" role="button"> 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> <i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</a> </a>
</div> </div>
</div> </div>
<div class="box-content-row" *ngIf="!history.length"> <div class="box-content-row" *ngIf="!history.length">
{{'noPasswordsInList' | i18n}} {{ "noPasswordsInList" | i18n }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
</div> </div>
</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 { CipherService } from "jslib-common/abstractions/cipher.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "jslib-angular/components/password-history.component";
PasswordHistoryComponent as BasePasswordHistoryComponent,
} from 'jslib-angular/components/password-history.component';
@Component({ @Component({
selector: 'app-password-history', selector: "app-password-history",
templateUrl: 'password-history.component.html', templateUrl: "password-history.component.html",
}) })
export class PasswordHistoryComponent extends BasePasswordHistoryComponent { export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
constructor(cipherService: CipherService, platformUtilsService: PlatformUtilsService, constructor(
i18nService: I18nService) { cipherService: CipherService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService
) {
super(cipherService, platformUtilsService, i18nService, window); super(cipherService, platformUtilsService, i18nService, window);
} }
} }

View File

@ -4,50 +4,71 @@
<div class="modal-body"> <div class="modal-body">
<div class="box"> <div class="box">
<div class="box-header" id="moveToOrgTitle"> <div class="box-header" id="moveToOrgTitle">
{{'moveToOrganization' | i18n}} {{ "moveToOrganization" | i18n }}
</div> </div>
<div class="box-content" *ngIf="!organizations || !organizations.length"> <div class="box-content" *ngIf="!organizations || !organizations.length">
<div class="box-content-row"> <div class="box-content-row">
{{'noOrganizationsList' | i18n}} {{ "noOrganizationsList" | i18n }}
</div> </div>
</div> </div>
<div class="box-content" *ngIf="organizations && organizations.length"> <div class="box-content" *ngIf="organizations && organizations.length">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="organization">{{'organization' | i18n}}</label> <label for="organization">{{ "organization" | i18n }}</label>
<select id="organization" name="OrganizationId" [(ngModel)]="organizationId" <select
(change)="filterCollections()"> id="organization"
name="OrganizationId"
[(ngModel)]="organizationId"
(change)="filterCollections()"
>
<option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option> <option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{{'moveToOrgDesc' | i18n}} {{ "moveToOrgDesc" | i18n }}
</div> </div>
</div> </div>
<div class="box" *ngIf="organizations && organizations.length"> <div class="box" *ngIf="organizations && organizations.length">
<div class="box-header"> <div class="box-header">
{{'collections' | i18n}} {{ "collections" | i18n }}
</div> </div>
<div class="box-content" *ngIf="!collections || !collections.length"> <div class="box-content" *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}} {{ "noCollectionsInList" | i18n }}
</div> </div>
<div class="box-content" *ngIf="collections && collections.length"> <div class="box-content" *ngIf="collections && collections.length">
<div class="box-content-row box-content-row-checkbox" <div
*ngFor="let c of collections; let i = index" appBoxRow> class="box-content-row box-content-row-checkbox"
*ngFor="let c of collections; let i = index"
appBoxRow
>
<label for="collection_{{ i }}">{{ c.name }}</label> <label for="collection_{{ i }}">{{ c.name }}</label>
<input id="collection_{{i}}" type="checkbox" [(ngModel)]="c.checked" <input
name="Collection[{{i}}].Checked"> id="collection_{{ i }}"
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button appBlurClick type="submit" class="primary" appA11yTitle="{{'save' | i18n}}" <button
[disabled]="form.loading || !canSave" *ngIf="organizations && organizations.length"> 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-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> <i
class="fa fa-spinner fa-spin fa-lg fa-fw"
[hidden]="!form.loading"
aria-hidden="true"
></i>
</button> </button>
<button type="button" data-dismiss="modal">{{'cancel' | i18n}}</button> <button type="button" data-dismiss="modal">{{ "cancel" | i18n }}</button>
</div> </div>
</form> </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 { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from 'jslib-common/abstractions/organization.service'; import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({ @Component({
selector: 'app-vault-share', selector: "app-vault-share",
templateUrl: 'share.component.html', templateUrl: "share.component.html",
}) })
export class ShareComponent extends BaseShareComponent { export class ShareComponent extends BaseShareComponent {
constructor(cipherService: CipherService, i18nService: I18nService, constructor(
collectionService: CollectionService, platformUtilsService: PlatformUtilsService, cipherService: CipherService,
logService: LogService, organizationService: OrganizationService) { i18nService: I18nService,
super(collectionService, platformUtilsService, i18nService, cipherService, collectionService: CollectionService,
logService, organizationService); platformUtilsService: PlatformUtilsService,
logService: LogService,
organizationService: OrganizationService
) {
super(
collectionService,
platformUtilsService,
i18nService,
cipherService,
logService,
organizationService
);
} }
} }

View File

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

View File

@ -6,66 +6,68 @@ import {
OnInit, OnInit,
ViewChild, ViewChild,
ViewContainerRef, ViewContainerRef,
} from '@angular/core'; } from "@angular/core";
import { import { ActivatedRoute, Router } from "@angular/router";
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
import { SearchBarService } from '../layout/search/search-bar.service'; import { SearchBarService } from "../layout/search/search-bar.service";
import { AddEditComponent } from './add-edit.component'; import { AddEditComponent } from "./add-edit.component";
import { AttachmentsComponent } from './attachments.component'; import { AttachmentsComponent } from "./attachments.component";
import { CiphersComponent } from './ciphers.component'; import { CiphersComponent } from "./ciphers.component";
import { CollectionsComponent } from './collections.component'; import { CollectionsComponent } from "./collections.component";
import { FolderAddEditComponent } from './folder-add-edit.component'; import { FolderAddEditComponent } from "./folder-add-edit.component";
import { GroupingsComponent } from './groupings.component'; import { GroupingsComponent } from "./groupings.component";
import { PasswordGeneratorComponent } from './password-generator.component'; import { PasswordGeneratorComponent } from "./password-generator.component";
import { PasswordHistoryComponent } from './password-history.component'; import { PasswordHistoryComponent } from "./password-history.component";
import { ShareComponent } from './share.component'; import { ShareComponent } from "./share.component";
import { ViewComponent } from './view.component'; import { ViewComponent } from "./view.component";
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType'; import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
import { CipherType } from 'jslib-common/enums/cipherType'; import { CipherType } from "jslib-common/enums/cipherType";
import { EventType } from 'jslib-common/enums/eventType'; import { EventType } from "jslib-common/enums/eventType";
import { CipherView } from 'jslib-common/models/view/cipherView'; import { CipherView } from "jslib-common/models/view/cipherView";
import { FolderView } from 'jslib-common/models/view/folderView'; import { FolderView } from "jslib-common/models/view/folderView";
import { ModalRef } from 'jslib-angular/components/modal/modal.ref'; import { ModalRef } from "jslib-angular/components/modal/modal.ref";
import { ModalService } from 'jslib-angular/services/modal.service'; import { ModalService } from "jslib-angular/services/modal.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { TotpService } from 'jslib-common/abstractions/totp.service'; import { TotpService } from "jslib-common/abstractions/totp.service";
import { invokeMenu, RendererMenuItem } from 'jslib-electron/utils'; import { invokeMenu, RendererMenuItem } from "jslib-electron/utils";
const BroadcasterSubscriptionId = 'VaultComponent'; const BroadcasterSubscriptionId = "VaultComponent";
@Component({ @Component({
selector: 'app-vault', selector: "app-vault",
templateUrl: 'vault.component.html', templateUrl: "vault.component.html",
}) })
export class VaultComponent implements OnInit, OnDestroy { export class VaultComponent implements OnInit, OnDestroy {
@ViewChild(ViewComponent) viewComponent: ViewComponent; @ViewChild(ViewComponent) viewComponent: ViewComponent;
@ViewChild(AddEditComponent) addEditComponent: AddEditComponent; @ViewChild(AddEditComponent) addEditComponent: AddEditComponent;
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent; @ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent; @ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
@ViewChild('passwordGenerator', { read: ViewContainerRef, static: true }) passwordGeneratorModalRef: ViewContainerRef; @ViewChild("passwordGenerator", { read: ViewContainerRef, static: true })
@ViewChild('attachments', { read: ViewContainerRef, static: true }) attachmentsModalRef: ViewContainerRef; passwordGeneratorModalRef: ViewContainerRef;
@ViewChild('passwordHistory', { read: ViewContainerRef, static: true }) passwordHistoryModalRef: ViewContainerRef; @ViewChild("attachments", { read: ViewContainerRef, static: true })
@ViewChild('share', { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef; attachmentsModalRef: ViewContainerRef;
@ViewChild('collections', { read: ViewContainerRef, static: true }) collectionsModalRef: ViewContainerRef; @ViewChild("passwordHistory", { read: ViewContainerRef, static: true })
@ViewChild('folderAddEdit', { read: ViewContainerRef, static: true }) folderAddEditModalRef: ViewContainerRef; passwordHistoryModalRef: ViewContainerRef;
@ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef;
@ViewChild("collections", { read: ViewContainerRef, static: true })
collectionsModalRef: ViewContainerRef;
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
folderAddEditModalRef: ViewContainerRef;
action: string; action: string;
cipherId: string = null; cipherId: string = null;
@ -82,14 +84,23 @@ export class VaultComponent implements OnInit, OnDestroy {
private modal: ModalRef = null; private modal: ModalRef = null;
constructor(private route: ActivatedRoute, private router: Router, constructor(
private i18nService: I18nService, private modalService: ModalService, private route: ActivatedRoute,
private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef, private router: Router,
private ngZone: NgZone, private syncService: SyncService, private i18nService: I18nService,
private modalService: ModalService,
private broadcasterService: BroadcasterService,
private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone,
private syncService: SyncService,
private messagingService: MessagingService, private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService, private eventService: EventService, private platformUtilsService: PlatformUtilsService,
private totpService: TotpService, private passwordRepromptService: PasswordRepromptService, private eventService: EventService,
private stateService: StateService, private searchBarService: SearchBarService) { } private totpService: TotpService,
private passwordRepromptService: PasswordRepromptService,
private stateService: StateService,
private searchBarService: SearchBarService
) {}
async ngOnInit() { async ngOnInit() {
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium(); this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
@ -98,60 +109,80 @@ export class VaultComponent implements OnInit, OnDestroy {
let detectChanges = true; let detectChanges = true;
switch (message.command) { switch (message.command) {
case 'newLogin': case "newLogin":
await this.addCipher(CipherType.Login); await this.addCipher(CipherType.Login);
break; break;
case 'newCard': case "newCard":
await this.addCipher(CipherType.Card); await this.addCipher(CipherType.Card);
break; break;
case 'newIdentity': case "newIdentity":
await this.addCipher(CipherType.Identity); await this.addCipher(CipherType.Identity);
break; break;
case 'newSecureNote': case "newSecureNote":
await this.addCipher(CipherType.SecureNote); await this.addCipher(CipherType.SecureNote);
break; break;
case 'focusSearch': case "focusSearch":
(document.querySelector('#search') as HTMLInputElement).select(); (document.querySelector("#search") as HTMLInputElement).select();
detectChanges = false; detectChanges = false;
break; break;
case 'openPasswordGenerator': case "openPasswordGenerator":
await this.openPasswordGenerator(false); await this.openPasswordGenerator(false);
break; break;
case 'syncCompleted': case "syncCompleted":
await this.load(); await this.load();
break; break;
case 'refreshCiphers': case "refreshCiphers":
this.ciphersComponent.refresh(); this.ciphersComponent.refresh();
break; break;
case 'modalShown': case "modalShown":
this.showingModal = true; this.showingModal = true;
break; break;
case 'modalClosed': case "modalClosed":
this.showingModal = false; this.showingModal = false;
break; break;
case 'copyUsername': case "copyUsername":
const uComponent = this.addEditComponent == null ? this.viewComponent : this.addEditComponent; const uComponent =
this.addEditComponent == null ? this.viewComponent : this.addEditComponent;
const uCipher = uComponent != null ? uComponent.cipher : null; const uCipher = uComponent != null ? uComponent.cipher : null;
if (this.cipherId != null && uCipher != null && uCipher.id === this.cipherId && if (
uCipher.login != null && uCipher.login.username != null) { this.cipherId != null &&
this.copyValue(uCipher, uCipher.login.username, 'username', 'Username'); uCipher != null &&
uCipher.id === this.cipherId &&
uCipher.login != null &&
uCipher.login.username != null
) {
this.copyValue(uCipher, uCipher.login.username, "username", "Username");
} }
break; break;
case 'copyPassword': case "copyPassword":
const pComponent = this.addEditComponent == null ? this.viewComponent : this.addEditComponent; const pComponent =
this.addEditComponent == null ? this.viewComponent : this.addEditComponent;
const pCipher = pComponent != null ? pComponent.cipher : null; const pCipher = pComponent != null ? pComponent.cipher : null;
if (this.cipherId != null && pCipher != null && pCipher.id === this.cipherId && if (
pCipher.login != null && pCipher.login.password != null && pCipher.viewPassword) { this.cipherId != null &&
this.copyValue(pCipher, pCipher.login.password, 'password', 'Password'); pCipher != null &&
pCipher.id === this.cipherId &&
pCipher.login != null &&
pCipher.login.password != null &&
pCipher.viewPassword
) {
this.copyValue(pCipher, pCipher.login.password, "password", "Password");
} }
break; break;
case 'copyTotp': case "copyTotp":
const tComponent = this.addEditComponent == null ? this.viewComponent : this.addEditComponent; const tComponent =
this.addEditComponent == null ? this.viewComponent : this.addEditComponent;
const tCipher = tComponent != null ? tComponent.cipher : null; const tCipher = tComponent != null ? tComponent.cipher : null;
if (this.cipherId != null && tCipher != null && tCipher.id === this.cipherId && if (
tCipher.login != null && tCipher.login.hasTotp && this.userHasPremiumAccess) { this.cipherId != null &&
tCipher != null &&
tCipher.id === this.cipherId &&
tCipher.login != null &&
tCipher.login.hasTotp &&
this.userHasPremiumAccess
) {
const value = await this.totpService.getCode(tCipher.login.totp); const value = await this.totpService.getCode(tCipher.login.totp);
this.copyValue(tCipher, value, 'verificationCodeTotp', 'TOTP'); this.copyValue(tCipher, value, "verificationCodeTotp", "TOTP");
} }
default: default:
detectChanges = false; detectChanges = false;
@ -167,20 +198,20 @@ export class VaultComponent implements OnInit, OnDestroy {
if (!this.syncService.syncInProgress) { if (!this.syncService.syncInProgress) {
await this.load(); await this.load();
} }
document.body.classList.remove('layout_frontend'); document.body.classList.remove("layout_frontend");
this.searchBarService.setEnabled(true); this.searchBarService.setEnabled(true);
this.searchBarService.setPlaceholderText(this.i18nService.t('searchVault')); this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault"));
} }
ngOnDestroy() { ngOnDestroy() {
this.searchBarService.setEnabled(false); this.searchBarService.setEnabled(false);
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
document.body.classList.add('layout_frontend'); document.body.classList.add("layout_frontend");
} }
async load() { async load() {
this.route.queryParams.pipe(first()).subscribe(async params => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
await this.groupingsComponent.load(); await this.groupingsComponent.load();
if (params == null) { if (params == null) {
@ -190,14 +221,14 @@ export class VaultComponent implements OnInit, OnDestroy {
if (params.cipherId) { if (params.cipherId) {
const cipherView = new CipherView(); const cipherView = new CipherView();
cipherView.id = params.cipherId; cipherView.id = params.cipherId;
if (params.action === 'clone') { if (params.action === "clone") {
await this.cloneCipher(cipherView); await this.cloneCipher(cipherView);
} else if (params.action === 'edit') { } else if (params.action === "edit") {
await this.editCipher(cipherView); await this.editCipher(cipherView);
} else { } else {
await this.viewCipher(cipherView); await this.viewCipher(cipherView);
} }
} else if (params.action === 'add') { } else if (params.action === "add") {
this.addType = Number(params.addType); this.addType = Number(params.addType);
this.addCipher(this.addType); this.addCipher(this.addType);
} }
@ -208,7 +239,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} else if (params.favorites) { } else if (params.favorites) {
this.groupingsComponent.selectedFavorites = true; this.groupingsComponent.selectedFavorites = true;
await this.filterFavorites(); await this.filterFavorites();
} else if (params.type && params.action !== 'add') { } else if (params.type && params.action !== "add") {
const t = parseInt(params.type, null); const t = parseInt(params.type, null);
this.groupingsComponent.selectedType = t; this.groupingsComponent.selectedType = t;
await this.filterCipherType(t); await this.filterCipherType(t);
@ -228,34 +259,37 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async viewCipher(cipher: CipherView) { async viewCipher(cipher: CipherView) {
if (!await this.canNavigateAway('view', cipher)) { if (!(await this.canNavigateAway("view", cipher))) {
return; return;
} }
this.cipherId = cipher.id; this.cipherId = cipher.id;
this.action = 'view'; this.action = "view";
this.go(); this.go();
} }
viewCipherMenu(cipher: CipherView) { viewCipherMenu(cipher: CipherView) {
const menu: RendererMenuItem[] = [ const menu: RendererMenuItem[] = [
{ {
label: this.i18nService.t('view'), label: this.i18nService.t("view"),
click: () => this.functionWithChangeDetection(() => { click: () =>
this.functionWithChangeDetection(() => {
this.viewCipher(cipher); this.viewCipher(cipher);
}), }),
}, },
]; ];
if (!cipher.isDeleted) { if (!cipher.isDeleted) {
menu.push({ menu.push({
label: this.i18nService.t('edit'), label: this.i18nService.t("edit"),
click: () => this.functionWithChangeDetection(() => { click: () =>
this.functionWithChangeDetection(() => {
this.editCipher(cipher); this.editCipher(cipher);
}), }),
}); });
menu.push({ menu.push({
label: this.i18nService.t('clone'), label: this.i18nService.t("clone"),
click: () => this.functionWithChangeDetection(() => { click: () =>
this.functionWithChangeDetection(() => {
this.cloneCipher(cipher); this.cloneCipher(cipher);
}), }),
}); });
@ -263,55 +297,59 @@ export class VaultComponent implements OnInit, OnDestroy {
switch (cipher.type) { switch (cipher.type) {
case CipherType.Login: case CipherType.Login:
if (cipher.login.canLaunch || cipher.login.username != null || cipher.login.password != null) { if (
menu.push({ type: 'separator' }); cipher.login.canLaunch ||
cipher.login.username != null ||
cipher.login.password != null
) {
menu.push({ type: "separator" });
} }
if (cipher.login.canLaunch) { if (cipher.login.canLaunch) {
menu.push({ menu.push({
label: this.i18nService.t('launch'), label: this.i18nService.t("launch"),
click: () => this.platformUtilsService.launchUri(cipher.login.launchUri), click: () => this.platformUtilsService.launchUri(cipher.login.launchUri),
}); });
} }
if (cipher.login.username != null) { if (cipher.login.username != null) {
menu.push({ menu.push({
label: this.i18nService.t('copyUsername'), label: this.i18nService.t("copyUsername"),
click: () => this.copyValue(cipher, cipher.login.username, 'username', 'Username'), click: () => this.copyValue(cipher, cipher.login.username, "username", "Username"),
}); });
} }
if (cipher.login.password != null && cipher.viewPassword) { if (cipher.login.password != null && cipher.viewPassword) {
menu.push({ menu.push({
label: this.i18nService.t('copyPassword'), label: this.i18nService.t("copyPassword"),
click: () => { click: () => {
this.copyValue(cipher, cipher.login.password, 'password', 'Password'); this.copyValue(cipher, cipher.login.password, "password", "Password");
this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
}, },
}); });
} }
if (cipher.login.hasTotp && (cipher.organizationUseTotp || this.userHasPremiumAccess)) { if (cipher.login.hasTotp && (cipher.organizationUseTotp || this.userHasPremiumAccess)) {
menu.push({ menu.push({
label: this.i18nService.t('copyVerificationCodeTotp'), label: this.i18nService.t("copyVerificationCodeTotp"),
click: async () => { click: async () => {
const value = await this.totpService.getCode(cipher.login.totp); const value = await this.totpService.getCode(cipher.login.totp);
this.copyValue(cipher, value, 'verificationCodeTotp', 'TOTP'); this.copyValue(cipher, value, "verificationCodeTotp", "TOTP");
}, },
}); });
} }
break; break;
case CipherType.Card: case CipherType.Card:
if (cipher.card.number != null || cipher.card.code != null) { if (cipher.card.number != null || cipher.card.code != null) {
menu.push({ type: 'separator' }); menu.push({ type: "separator" });
} }
if (cipher.card.number != null) { if (cipher.card.number != null) {
menu.push({ menu.push({
label: this.i18nService.t('copyNumber'), label: this.i18nService.t("copyNumber"),
click: () => this.copyValue(cipher, cipher.card.number, 'number', 'Card Number'), click: () => this.copyValue(cipher, cipher.card.number, "number", "Card Number"),
}); });
} }
if (cipher.card.code != null) { if (cipher.card.code != null) {
menu.push({ menu.push({
label: this.i18nService.t('copySecurityCode'), label: this.i18nService.t("copySecurityCode"),
click: () => { click: () => {
this.copyValue(cipher, cipher.card.code, 'securityCode', 'Security Code'); this.copyValue(cipher, cipher.card.code, "securityCode", "Security Code");
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id); this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
}, },
}); });
@ -325,9 +363,9 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async editCipher(cipher: CipherView) { async editCipher(cipher: CipherView) {
if (!await this.canNavigateAway('edit', cipher)) { if (!(await this.canNavigateAway("edit", cipher))) {
return; return;
} else if (!await this.passwordReprompt(cipher)) { } else if (!(await this.passwordReprompt(cipher))) {
return; return;
} }
@ -335,19 +373,19 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async editCipherWithoutPasswordPrompt(cipher: CipherView) { async editCipherWithoutPasswordPrompt(cipher: CipherView) {
if (!await this.canNavigateAway('edit', cipher)) { if (!(await this.canNavigateAway("edit", cipher))) {
return; return;
} }
this.cipherId = cipher.id; this.cipherId = cipher.id;
this.action = 'edit'; this.action = "edit";
this.go(); this.go();
} }
async cloneCipher(cipher: CipherView) { async cloneCipher(cipher: CipherView) {
if (!await this.canNavigateAway('clone', cipher)) { if (!(await this.canNavigateAway("clone", cipher))) {
return; return;
} else if (!await this.passwordReprompt(cipher)) { } else if (!(await this.passwordReprompt(cipher))) {
return; return;
} }
@ -355,22 +393,22 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async cloneCipherWithoutPasswordPrompt(cipher: CipherView) { async cloneCipherWithoutPasswordPrompt(cipher: CipherView) {
if (!await this.canNavigateAway('edit', cipher)) { if (!(await this.canNavigateAway("edit", cipher))) {
return; return;
} }
this.cipherId = cipher.id; this.cipherId = cipher.id;
this.action = 'clone'; this.action = "clone";
this.go(); this.go();
} }
async addCipher(type: CipherType = null) { async addCipher(type: CipherType = null) {
if (!await this.canNavigateAway('add', null)) { if (!(await this.canNavigateAway("add", null))) {
return; return;
} }
this.addType = type; this.addType = type;
this.action = 'add'; this.action = "add";
this.cipherId = null; this.cipherId = null;
this.updateCollectionProperties(); this.updateCollectionProperties();
this.go(); this.go();
@ -379,22 +417,21 @@ export class VaultComponent implements OnInit, OnDestroy {
addCipherOptions() { addCipherOptions() {
const menu: RendererMenuItem[] = [ const menu: RendererMenuItem[] = [
{ {
label: this.i18nService.t('typeLogin'), label: this.i18nService.t("typeLogin"),
click: () => this.addCipherWithChangeDetection(CipherType.Login), click: () => this.addCipherWithChangeDetection(CipherType.Login),
}, },
{ {
label: this.i18nService.t('typeCard'), label: this.i18nService.t("typeCard"),
click: () => this.addCipherWithChangeDetection(CipherType.Card), click: () => this.addCipherWithChangeDetection(CipherType.Card),
}, },
{ {
label: this.i18nService.t('typeIdentity'), label: this.i18nService.t("typeIdentity"),
click: () => this.addCipherWithChangeDetection(CipherType.Identity), click: () => this.addCipherWithChangeDetection(CipherType.Identity),
}, },
{ {
label: this.i18nService.t('typeSecureNote'), label: this.i18nService.t("typeSecureNote"),
click: () => this.addCipherWithChangeDetection(CipherType.SecureNote), click: () => this.addCipherWithChangeDetection(CipherType.SecureNote),
}, },
]; ];
invokeMenu(menu); invokeMenu(menu);
@ -402,7 +439,7 @@ export class VaultComponent implements OnInit, OnDestroy {
async savedCipher(cipher: CipherView) { async savedCipher(cipher: CipherView) {
this.cipherId = cipher.id; this.cipherId = cipher.id;
this.action = 'view'; this.action = "view";
this.go(); this.go();
await this.ciphersComponent.refresh(); await this.ciphersComponent.refresh();
} }
@ -426,13 +463,16 @@ export class VaultComponent implements OnInit, OnDestroy {
this.modal.close(); this.modal.close();
} }
const [modal, childComponent] = await this.modalService.openViewRef(AttachmentsComponent, this.attachmentsModalRef, const [modal, childComponent] = await this.modalService.openViewRef(
comp => comp.cipherId = cipher.id); AttachmentsComponent,
this.attachmentsModalRef,
(comp) => (comp.cipherId = cipher.id)
);
this.modal = modal; this.modal = modal;
let madeAttachmentChanges = false; let madeAttachmentChanges = false;
childComponent.onUploadedAttachment.subscribe(() => madeAttachmentChanges = true); childComponent.onUploadedAttachment.subscribe(() => (madeAttachmentChanges = true));
childComponent.onDeletedAttachment.subscribe(() => madeAttachmentChanges = true); childComponent.onDeletedAttachment.subscribe(() => (madeAttachmentChanges = true));
this.modal.onClosed.subscribe(async () => { this.modal.onClosed.subscribe(async () => {
this.modal = null; this.modal = null;
@ -448,8 +488,11 @@ export class VaultComponent implements OnInit, OnDestroy {
this.modal.close(); this.modal.close();
} }
const [modal, childComponent] = await this.modalService.openViewRef(ShareComponent, this.shareModalRef, const [modal, childComponent] = await this.modalService.openViewRef(
comp => comp.cipherId = cipher.id); ShareComponent,
this.shareModalRef,
(comp) => (comp.cipherId = cipher.id)
);
this.modal = modal; this.modal = modal;
childComponent.onSharedCipher.subscribe(async () => { childComponent.onSharedCipher.subscribe(async () => {
@ -467,8 +510,11 @@ export class VaultComponent implements OnInit, OnDestroy {
this.modal.close(); this.modal.close();
} }
const [modal, childComponent] = await this.modalService.openViewRef(CollectionsComponent, this.collectionsModalRef, const [modal, childComponent] = await this.modalService.openViewRef(
comp => comp.cipherId = cipher.id); CollectionsComponent,
this.collectionsModalRef,
(comp) => (comp.cipherId = cipher.id)
);
this.modal = modal; this.modal = modal;
childComponent.onSavedCollections.subscribe(() => { childComponent.onSavedCollections.subscribe(() => {
@ -485,8 +531,11 @@ export class VaultComponent implements OnInit, OnDestroy {
this.modal.close(); this.modal.close();
} }
[this.modal] = await this.modalService.openViewRef(PasswordHistoryComponent, this.passwordHistoryModalRef, [this.modal] = await this.modalService.openViewRef(
comp => comp.cipherId = cipher.id); PasswordHistoryComponent,
this.passwordHistoryModalRef,
(comp) => (comp.cipherId = cipher.id)
);
this.modal.onClosed.subscribe(async () => { this.modal.onClosed.subscribe(async () => {
this.modal = null; this.modal = null;
@ -495,27 +544,27 @@ export class VaultComponent implements OnInit, OnDestroy {
cancelledAddEdit(cipher: CipherView) { cancelledAddEdit(cipher: CipherView) {
this.cipherId = cipher.id; this.cipherId = cipher.id;
this.action = this.cipherId != null ? 'view' : null; this.action = this.cipherId != null ? "view" : null;
this.go(); this.go();
} }
async clearGroupingFilters() { async clearGroupingFilters() {
this.searchBarService.setPlaceholderText(this.i18nService.t('searchVault')); this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault"));
await this.ciphersComponent.reload(); await this.ciphersComponent.reload();
this.clearFilters(); this.clearFilters();
this.go(); this.go();
} }
async filterFavorites() { async filterFavorites() {
this.searchBarService.setPlaceholderText(this.i18nService.t('searchFavorites')); this.searchBarService.setPlaceholderText(this.i18nService.t("searchFavorites"));
await this.ciphersComponent.reload(c => c.favorite); await this.ciphersComponent.reload((c) => c.favorite);
this.clearFilters(); this.clearFilters();
this.favorites = true; this.favorites = true;
this.go(); this.go();
} }
async filterDeleted() { async filterDeleted() {
this.searchBarService.setPlaceholderText(this.i18nService.t('searchTrash')); this.searchBarService.setPlaceholderText(this.i18nService.t("searchTrash"));
this.ciphersComponent.deleted = true; this.ciphersComponent.deleted = true;
await this.ciphersComponent.reload(null, true); await this.ciphersComponent.reload(null, true);
this.clearFilters(); this.clearFilters();
@ -524,26 +573,27 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async filterCipherType(type: CipherType) { async filterCipherType(type: CipherType) {
this.searchBarService.setPlaceholderText(this.i18nService.t('searchType')); this.searchBarService.setPlaceholderText(this.i18nService.t("searchType"));
await this.ciphersComponent.reload(c => c.type === type); await this.ciphersComponent.reload((c) => c.type === type);
this.clearFilters(); this.clearFilters();
this.type = type; this.type = type;
this.go(); this.go();
} }
async filterFolder(folderId: string) { async filterFolder(folderId: string) {
folderId = folderId === 'none' ? null : folderId; folderId = folderId === "none" ? null : folderId;
this.searchBarService.setPlaceholderText(this.i18nService.t('searchFolder')); this.searchBarService.setPlaceholderText(this.i18nService.t("searchFolder"));
await this.ciphersComponent.reload(c => c.folderId === folderId); await this.ciphersComponent.reload((c) => c.folderId === folderId);
this.clearFilters(); this.clearFilters();
this.folderId = folderId == null ? 'none' : folderId; this.folderId = folderId == null ? "none" : folderId;
this.go(); this.go();
} }
async filterCollection(collectionId: string) { async filterCollection(collectionId: string) {
this.searchBarService.setPlaceholderText(this.i18nService.t('searchCollection')); this.searchBarService.setPlaceholderText(this.i18nService.t("searchCollection"));
await this.ciphersComponent.reload(c => c.collectionIds != null && await this.ciphersComponent.reload(
c.collectionIds.indexOf(collectionId) > -1); (c) => c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1
);
this.clearFilters(); this.clearFilters();
this.collectionId = collectionId; this.collectionId = collectionId;
this.updateCollectionProperties(); this.updateCollectionProperties();
@ -555,14 +605,21 @@ export class VaultComponent implements OnInit, OnDestroy {
this.modal.close(); this.modal.close();
} }
const [modal, childComponent] = await this.modalService.openViewRef(PasswordGeneratorComponent, this.passwordGeneratorModalRef, const [modal, childComponent] = await this.modalService.openViewRef(
comp => comp.showSelect = showSelect); PasswordGeneratorComponent,
this.passwordGeneratorModalRef,
(comp) => (comp.showSelect = showSelect)
);
this.modal = modal; this.modal = modal;
childComponent.onSelected.subscribe((password: string) => { childComponent.onSelected.subscribe((password: string) => {
this.modal.close(); this.modal.close();
if (this.addEditComponent != null && this.addEditComponent.cipher != null && if (
this.addEditComponent.cipher.type === CipherType.Login && this.addEditComponent.cipher.login != null) { this.addEditComponent != null &&
this.addEditComponent.cipher != null &&
this.addEditComponent.cipher.type === CipherType.Login &&
this.addEditComponent.cipher.login != null
) {
this.addEditComponent.markPasswordAsDirty(); this.addEditComponent.markPasswordAsDirty();
this.addEditComponent.cipher.login.password = password; this.addEditComponent.cipher.login.password = password;
} }
@ -574,7 +631,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async addFolder() { async addFolder() {
this.messagingService.send('newFolder'); this.messagingService.send("newFolder");
} }
async editFolder(folderId: string) { async editFolder(folderId: string) {
@ -582,8 +639,11 @@ export class VaultComponent implements OnInit, OnDestroy {
this.modal.close(); this.modal.close();
} }
const [modal, childComponent] = await this.modalService.openViewRef(FolderAddEditComponent, this.folderAddEditModalRef, const [modal, childComponent] = await this.modalService.openViewRef(
comp => comp.folderId = folderId); FolderAddEditComponent,
this.folderAddEditModalRef,
(comp) => (comp.folderId = folderId)
);
this.modal = modal; this.modal = modal;
childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { childComponent.onSavedFolder.subscribe(async (folder: FolderView) => {
@ -601,14 +661,20 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
private dirtyInput(): boolean { private dirtyInput(): boolean {
return (this.action === 'add' || this.action === 'edit' || this.action === 'clone') && return (
document.querySelectorAll('app-vault-add-edit .ng-dirty').length > 0; (this.action === "add" || this.action === "edit" || this.action === "clone") &&
document.querySelectorAll("app-vault-add-edit .ng-dirty").length > 0
);
} }
private async wantsToSaveChanges(): Promise<boolean> { private async wantsToSaveChanges(): Promise<boolean> {
const confirmed = await this.platformUtilsService.showDialog( const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('unsavedChangesConfirmation'), this.i18nService.t('unsavedChangesTitle'), this.i18nService.t("unsavedChangesConfirmation"),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); this.i18nService.t("unsavedChangesTitle"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
return !confirmed; return !confirmed;
} }
@ -649,16 +715,22 @@ export class VaultComponent implements OnInit, OnDestroy {
private copyValue(cipher: CipherView, value: string, labelI18nKey: string, aType: string) { private copyValue(cipher: CipherView, value: string, labelI18nKey: string, aType: string) {
this.functionWithChangeDetection(async () => { this.functionWithChangeDetection(async () => {
if (cipher.reprompt !== CipherRepromptType.None && this.passwordRepromptService.protectedFields().includes(aType) && if (
!await this.passwordRepromptService.showPasswordPrompt()) { cipher.reprompt !== CipherRepromptType.None &&
this.passwordRepromptService.protectedFields().includes(aType) &&
!(await this.passwordRepromptService.showPasswordPrompt())
) {
return; return;
} }
this.platformUtilsService.copyToClipboard(value); this.platformUtilsService.copyToClipboard(value);
this.platformUtilsService.showToast('info', null, this.platformUtilsService.showToast(
this.i18nService.t('valueCopied', this.i18nService.t(labelI18nKey))); "info",
if (this.action === 'view') { null,
this.messagingService.send('minimizeOnCopy'); this.i18nService.t("valueCopied", this.i18nService.t(labelI18nKey))
);
if (this.action === "view") {
this.messagingService.send("minimizeOnCopy");
} }
}); });
} }
@ -672,7 +744,9 @@ export class VaultComponent implements OnInit, OnDestroy {
private updateCollectionProperties() { private updateCollectionProperties() {
if (this.collectionId != null) { if (this.collectionId != null) {
const collection = this.groupingsComponent.collections.filter(c => c.id === this.collectionId); const collection = this.groupingsComponent.collections.filter(
(c) => c.id === this.collectionId
);
if (collection.length > 0) { if (collection.length > 0) {
this.addOrganizationId = collection[0].organizationId; this.addOrganizationId = collection[0].organizationId;
this.addCollectionIds = [this.collectionId]; this.addCollectionIds = [this.collectionId];
@ -687,7 +761,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// Don't navigate to same route // Don't navigate to same route
if (this.action === action && (cipher == null || this.cipherId === cipher.id)) { if (this.action === action && (cipher == null || this.cipherId === cipher.id)) {
return false; return false;
} else if (this.dirtyInput() && await this.wantsToSaveChanges()) { } else if (this.dirtyInput() && (await this.wantsToSaveChanges())) {
return false; return false;
} }
@ -695,6 +769,9 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
private async passwordReprompt(cipher: CipherView) { private async passwordReprompt(cipher: CipherView) {
return cipher.reprompt === CipherRepromptType.None || await this.passwordRepromptService.showPasswordPrompt(); return (
cipher.reprompt === CipherRepromptType.None ||
(await this.passwordRepromptService.showPasswordPrompt())
);
} }
} }

View File

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

View File

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

View File

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

View File

@ -5,54 +5,79 @@ import {
NgZone, NgZone,
OnChanges, OnChanges,
Output, Output,
} from '@angular/core'; } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuditService } from 'jslib-common/abstractions/audit.service'; import { AuditService } from "jslib-common/abstractions/audit.service";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from 'jslib-common/abstractions/event.service'; import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from 'jslib-common/abstractions/state.service'; import { StateService } from "jslib-common/abstractions/state.service";
import { TokenService } from 'jslib-common/abstractions/token.service'; import { TokenService } from "jslib-common/abstractions/token.service";
import { TotpService } from 'jslib-common/abstractions/totp.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({ @Component({
selector: 'app-vault-view', selector: "app-vault-view",
templateUrl: 'view.component.html', templateUrl: "view.component.html",
}) })
export class ViewComponent extends BaseViewComponent implements OnChanges { export class ViewComponent extends BaseViewComponent implements OnChanges {
@Output() onViewCipherPasswordHistory = new EventEmitter<CipherView>(); @Output() onViewCipherPasswordHistory = new EventEmitter<CipherView>();
constructor(cipherService: CipherService, totpService: TotpService, constructor(
tokenService: TokenService, i18nService: I18nService, cipherService: CipherService,
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService, totpService: TotpService,
auditService: AuditService, broadcasterService: BroadcasterService, tokenService: TokenService,
ngZone: NgZone, changeDetectorRef: ChangeDetectorRef, i18nService: I18nService,
eventService: EventService, apiService: ApiService, cryptoService: CryptoService,
private messagingService: MessagingService, passwordRepromptService: PasswordRepromptService, platformUtilsService: PlatformUtilsService,
logService: LogService, stateService: StateService) { auditService: AuditService,
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService, broadcasterService: BroadcasterService,
auditService, window, broadcasterService, ngZone, changeDetectorRef, eventService, ngZone: NgZone,
apiService, passwordRepromptService, logService, stateService); 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() { ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(() => { this.ngZone.run(() => {
switch (message.command) { switch (message.command) {
case 'windowHidden': case "windowHidden":
this.onWindowHidden(); this.onWindowHidden();
break; break;
default: default:
@ -76,7 +101,7 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
async copy(value: string, typeI18nKey: string, aType: string) { async copy(value: string, typeI18nKey: string, aType: string) {
super.copy(value, typeI18nKey, aType); super.copy(value, typeI18nKey, aType);
this.messagingService.send('minimizeOnCopy'); this.messagingService.send("minimizeOnCopy");
} }
onWindowHidden() { onWindowHidden() {
@ -84,7 +109,7 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
this.showCardNumber = false; this.showCardNumber = false;
this.showCardCode = false; this.showCardCode = false;
if (this.cipher !== null && this.cipher.hasFields) { if (this.cipher !== null && this.cipher.hasFields) {
this.cipher.fields.forEach(field => { this.cipher.fields.forEach((field) => {
field.showValue = false; 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 // We need to import the other dependencies using `require` since `import` will
// generate `Error: Cannot find module 'electron'`. The cause of this error is // generate `Error: Cannot find module 'electron'`. The cause of this error is
@ -6,18 +6,20 @@ import { NativeMessagingProxy } from './proxy/native-messaging-proxy';
// which removes the electron module. This flag is needed for stdin/out to work // which removes the electron module. This flag is needed for stdin/out to work
// properly on Windows. // properly on Windows.
if (process.argv.some(arg => arg.indexOf('chrome-extension://') !== -1 || arg.indexOf('{') !== -1)) { if (
if (process.platform === 'darwin') { process.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1)
) {
if (process.platform === "darwin") {
// tslint:disable-next-line // tslint:disable-next-line
const app = require('electron').app; const app = require("electron").app;
app.on('ready', () => { app.on("ready", () => {
app.dock.hide(); app.dock.hide();
}); });
} }
process.stdout.on('error', e => { process.stdout.on("error", (e) => {
if (e.code === 'EPIPE') { if (e.code === "EPIPE") {
process.exit(0); process.exit(0);
} }
}); });
@ -26,7 +28,7 @@ if (process.argv.some(arg => arg.indexOf('chrome-extension://') !== -1 || arg.in
proxy.run(); proxy.run();
} else { } else {
// tslint:disable-next-line // tslint:disable-next-line
const Main = require('./main').Main; const Main = require("./main").Main;
const main = new Main(); const main = new Main();
main.bootstrap(); main.bootstrap();

2
src/global.d.ts vendored
View File

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

View File

@ -1,12 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; <meta
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"> http-equiv="Content-Security-Policy"
<meta name="viewport" content="width=device-width, initial-scale=1"> 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> <title>Bitwarden</title>
<base href=""> <base href="" />
</head> </head>
<body class="layout_frontend"> <body class="layout_frontend">
<app-root> <app-root>

View File

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

View File

@ -1,34 +1,25 @@
import { import { BrowserWindow, clipboard, dialog, MenuItemConstructorOptions } from "electron";
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 { UpdaterMain } from "jslib-electron/updater.main";
import { isMac, isSnapStore, isWindowsStore } from 'jslib-electron/utils'; import { isMac, isSnapStore, isWindowsStore } from "jslib-electron/utils";
import { IMenubarMenu } from './menubar'; import { IMenubarMenu } from "./menubar";
export class AboutMenu implements IMenubarMenu { export class AboutMenu implements IMenubarMenu {
readonly id: string = 'about'; readonly id: string = "about";
get visible(): boolean { get visible(): boolean {
return !isMac(); return !isMac();
} }
get label(): string { get label(): string {
return this.localize('about'); return this.localize("about");
} }
get items(): MenuItemConstructorOptions[] { get items(): MenuItemConstructorOptions[] {
return [ return [this.separator, this.checkForUpdates, this.aboutBitwarden];
this.separator,
this.checkForUpdates,
this.aboutBitwarden,
];
} }
private readonly _i18nService: I18nService; private readonly _i18nService: I18nService;
@ -40,7 +31,7 @@ export class AboutMenu implements IMenubarMenu {
i18nService: I18nService, i18nService: I18nService,
version: string, version: string,
window: BrowserWindow, window: BrowserWindow,
updater: UpdaterMain, updater: UpdaterMain
) { ) {
this._i18nService = i18nService; this._i18nService = i18nService;
this._updater = updater; this._updater = updater;
@ -49,13 +40,13 @@ export class AboutMenu implements IMenubarMenu {
} }
private get separator(): MenuItemConstructorOptions { private get separator(): MenuItemConstructorOptions {
return { type: 'separator' }; return { type: "separator" };
} }
private get checkForUpdates(): MenuItemConstructorOptions { private get checkForUpdates(): MenuItemConstructorOptions {
return { return {
id: 'checkForUpdates', id: "checkForUpdates",
label: this.localize('checkForUpdates'), label: this.localize("checkForUpdates"),
visible: !isWindowsStore() && !isSnapStore(), visible: !isWindowsStore() && !isSnapStore(),
click: () => this.checkForUpdate(), click: () => this.checkForUpdate(),
}; };
@ -63,21 +54,26 @@ export class AboutMenu implements IMenubarMenu {
private get aboutBitwarden(): MenuItemConstructorOptions { private get aboutBitwarden(): MenuItemConstructorOptions {
return { return {
id: 'aboutBitwarden', id: "aboutBitwarden",
label: this.localize('aboutBitwarden'), label: this.localize("aboutBitwarden"),
click: async () => { click: async () => {
const aboutInformation = this.localize('version', this._version) + const aboutInformation =
'\nShell ' + process.versions.electron + this.localize("version", this._version) +
'\nRenderer ' + process.versions.chrome + "\nShell " +
'\nNode ' + process.versions.node + process.versions.electron +
'\nArchitecture ' + process.arch; "\nRenderer " +
process.versions.chrome +
"\nNode " +
process.versions.node +
"\nArchitecture " +
process.arch;
const result = await dialog.showMessageBox(this._window, { const result = await dialog.showMessageBox(this._window, {
title: 'Bitwarden', title: "Bitwarden",
message: 'Bitwarden', message: "Bitwarden",
detail: aboutInformation, detail: aboutInformation,
type: 'info', type: "info",
noLink: true, noLink: true,
buttons: [this.localize('ok'), this.localize('copy')], buttons: [this.localize("ok"), this.localize("copy")],
}); });
if (result.response === 1) { if (result.response === 1) {
clipboard.writeText(aboutInformation); clipboard.writeText(aboutInformation);

View File

@ -1,22 +1,17 @@
import { import { BrowserWindow, dialog, MenuItemConstructorOptions, shell } from "electron";
BrowserWindow,
dialog,
MenuItemConstructorOptions,
shell,
} from 'electron';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.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 { export class AccountMenu implements IMenubarMenu {
readonly id: string = 'accountMenu'; readonly id: string = "accountMenu";
get label(): string { get label(): string {
return this.localize('account'); return this.localize("account");
} }
get items(): MenuItemConstructorOptions[] { get items(): MenuItemConstructorOptions[] {
@ -39,7 +34,7 @@ export class AccountMenu implements IMenubarMenu {
messagingService: MessagingService, messagingService: MessagingService,
webVaultUrl: string, webVaultUrl: string,
window: BrowserWindow, window: BrowserWindow,
isAuthenticated: boolean, isAuthenticated: boolean
) { ) {
this._i18nService = i18nService; this._i18nService = i18nService;
this._messagingService = messagingService; this._messagingService = messagingService;
@ -50,9 +45,9 @@ export class AccountMenu implements IMenubarMenu {
private get premiumMembership(): MenuItemConstructorOptions { private get premiumMembership(): MenuItemConstructorOptions {
return { return {
label: this.localize('premiumMembership'), label: this.localize("premiumMembership"),
click: () => this.sendMessage('openPremium'), click: () => this.sendMessage("openPremium"),
id: 'premiumMembership', id: "premiumMembership",
visible: !isWindowsStore() && !isMacAppStore(), visible: !isWindowsStore() && !isMacAppStore(),
enabled: this._isAuthenticated, enabled: this._isAuthenticated,
}; };
@ -60,14 +55,14 @@ export class AccountMenu implements IMenubarMenu {
private get changeMasterPassword(): MenuItemConstructorOptions { private get changeMasterPassword(): MenuItemConstructorOptions {
return { return {
label: this.localize('changeMasterPass'), label: this.localize("changeMasterPass"),
id: 'changeMasterPass', id: "changeMasterPass",
click: async () => { click: async () => {
const result = await dialog.showMessageBox(this._window, { const result = await dialog.showMessageBox(this._window, {
title: this.localize('changeMasterPass'), title: this.localize("changeMasterPass"),
message: this.localize('changeMasterPass'), message: this.localize("changeMasterPass"),
detail: this.localize('changeMasterPasswordConfirmation'), detail: this.localize("changeMasterPasswordConfirmation"),
buttons: [this.localize('yes'), this.localize('no')], buttons: [this.localize("yes"), this.localize("no")],
cancelId: 1, cancelId: 1,
defaultId: 0, defaultId: 0,
noLink: true, noLink: true,
@ -82,14 +77,14 @@ export class AccountMenu implements IMenubarMenu {
private get twoStepLogin(): MenuItemConstructorOptions { private get twoStepLogin(): MenuItemConstructorOptions {
return { return {
label: this.localize('twoStepLogin'), label: this.localize("twoStepLogin"),
id: 'twoStepLogin', id: "twoStepLogin",
click: async () => { click: async () => {
const result = await dialog.showMessageBox(this._window, { const result = await dialog.showMessageBox(this._window, {
title: this.localize('twoStepLogin'), title: this.localize("twoStepLogin"),
message: this.localize('twoStepLogin'), message: this.localize("twoStepLogin"),
detail: this.localize('twoStepLoginConfirmation'), detail: this.localize("twoStepLoginConfirmation"),
buttons: [this.localize('yes'), this.localize('no')], buttons: [this.localize("yes"), this.localize("no")],
cancelId: 1, cancelId: 1,
defaultId: 0, defaultId: 0,
noLink: true, noLink: true,
@ -104,9 +99,9 @@ export class AccountMenu implements IMenubarMenu {
private get fingerprintPhrase(): MenuItemConstructorOptions { private get fingerprintPhrase(): MenuItemConstructorOptions {
return { return {
label: this.localize('fingerprintPhrase'), label: this.localize("fingerprintPhrase"),
id: 'fingerprintPhrase', id: "fingerprintPhrase",
click: () => this.sendMessage('showFingerprintPhrase'), click: () => this.sendMessage("showFingerprintPhrase"),
enabled: this._isAuthenticated, enabled: this._isAuthenticated,
}; };
} }

View File

@ -1,24 +1,19 @@
import { import { BrowserWindow, dialog, MenuItem, MenuItemConstructorOptions } from "electron";
BrowserWindow,
dialog,
MenuItem,
MenuItemConstructorOptions,
} from 'electron';
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { UpdaterMain } from 'jslib-electron/updater.main'; import { UpdaterMain } from "jslib-electron/updater.main";
import { isMacAppStore, isSnapStore, isWindowsStore } from 'jslib-electron/utils'; 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 // AKA: "FirstMenu" or "MacMenu" - the first menu that shows on all macOs apps
export class BitwardenMenu implements IMenubarMenu { export class BitwardenMenu implements IMenubarMenu {
readonly id: string = 'bitwarden'; readonly id: string = "bitwarden";
readonly label: string = 'Bitwarden'; readonly label: string = "Bitwarden";
get items(): MenuItemConstructorOptions[] { get items(): MenuItemConstructorOptions[] {
return [ return [
@ -50,7 +45,7 @@ export class BitwardenMenu implements IMenubarMenu {
messagingService: MessagingService, messagingService: MessagingService,
updater: UpdaterMain, updater: UpdaterMain,
window: BrowserWindow, window: BrowserWindow,
accounts: { [userId: string]: MenuAccount }, accounts: { [userId: string]: MenuAccount }
) { ) {
this._i18nService = i18nService; this._i18nService = i18nService;
this._updater = updater; this._updater = updater;
@ -65,44 +60,41 @@ export class BitwardenMenu implements IMenubarMenu {
private get aboutBitwarden(): MenuItemConstructorOptions { private get aboutBitwarden(): MenuItemConstructorOptions {
return { return {
id: 'aboutBitwarden', id: "aboutBitwarden",
label: this.localize('aboutBitwarden'), label: this.localize("aboutBitwarden"),
role: 'about', role: "about",
visible: isMacAppStore(), visible: isMacAppStore(),
}; };
} }
private get checkForUpdates(): MenuItemConstructorOptions { private get checkForUpdates(): MenuItemConstructorOptions {
return { return {
id: 'checkForUpdates', id: "checkForUpdates",
label: this.localize('checkForUpdates'), label: this.localize("checkForUpdates"),
click: menuItem => this.checkForUpdate(menuItem), click: (menuItem) => this.checkForUpdate(menuItem),
visible: !isMacAppStore() && !isWindowsStore() && !isSnapStore(), visible: !isMacAppStore() && !isWindowsStore() && !isSnapStore(),
}; };
} }
private get separator(): MenuItemConstructorOptions { private get separator(): MenuItemConstructorOptions {
return { return {
type: 'separator', type: "separator",
}; };
} }
private get settings(): MenuItemConstructorOptions { private get settings(): MenuItemConstructorOptions {
return { return {
id: 'settings', id: "settings",
label: this.localize(process.platform === 'darwin' ? label: this.localize(process.platform === "darwin" ? "preferences" : "settings"),
'preferences' : click: () => this.sendMessage("openSettings"),
'settings' accelerator: "CmdOrCtrl+,",
),
click: () => this.sendMessage('openSettings'),
accelerator: 'CmdOrCtrl+,',
}; };
} }
private get lock(): MenuItemConstructorOptions { private get lock(): MenuItemConstructorOptions {
return { return {
id: 'lock', id: "lock",
label: this.localize('lockVault'), label: this.localize("lockVault"),
submenu: this.lockSubmenu, submenu: this.lockSubmenu,
enabled: this.hasAccounts, enabled: this.hasAccounts,
}; };
@ -118,7 +110,7 @@ export class BitwardenMenu implements IMenubarMenu {
value.push({ value.push({
label: this._accounts[userId].email, label: this._accounts[userId].email,
id: `lockNow_${this._accounts[userId].userId}`, id: `lockNow_${this._accounts[userId].userId}`,
click: () => this.sendMessage('lockVault', { userId: this._accounts[userId].userId }), click: () => this.sendMessage("lockVault", { userId: this._accounts[userId].userId }),
enabled: !this._accounts[userId].isLocked, enabled: !this._accounts[userId].isLocked,
visible: this._accounts[userId].isAuthenticated, visible: this._accounts[userId].isAuthenticated,
}); });
@ -128,18 +120,18 @@ export class BitwardenMenu implements IMenubarMenu {
private get lockAll(): MenuItemConstructorOptions { private get lockAll(): MenuItemConstructorOptions {
return { return {
id: 'lockAllNow', id: "lockAllNow",
label: this.localize('lockAllVaults'), label: this.localize("lockAllVaults"),
click: () => this.sendMessage('lockAllVaults'), click: () => this.sendMessage("lockAllVaults"),
accelerator: 'CmdOrCtrl+L', accelerator: "CmdOrCtrl+L",
enabled: this.hasAccounts, enabled: this.hasAccounts,
}; };
} }
private get logOut(): MenuItemConstructorOptions { private get logOut(): MenuItemConstructorOptions {
return { return {
id: 'logOut', id: "logOut",
label: this.localize('logOut'), label: this.localize("logOut"),
submenu: this.logOutSubmenu, submenu: this.logOutSubmenu,
enabled: this.hasAccounts, enabled: this.hasAccounts,
}; };
@ -157,16 +149,16 @@ export class BitwardenMenu implements IMenubarMenu {
id: `logOut_${this._accounts[userId].userId}`, id: `logOut_${this._accounts[userId].userId}`,
click: async () => { click: async () => {
const result = await dialog.showMessageBox(this._window, { const result = await dialog.showMessageBox(this._window, {
title: this.localize('logOut'), title: this.localize("logOut"),
message: this.localize('logOut'), message: this.localize("logOut"),
detail: this.localize('logOutConfirmation'), detail: this.localize("logOutConfirmation"),
buttons: [this.localize('logOut'), this.localize('cancel')], buttons: [this.localize("logOut"), this.localize("cancel")],
cancelId: 1, cancelId: 1,
defaultId: 0, defaultId: 0,
noLink: true, noLink: true,
}); });
if (result.response === 0) { if (result.response === 0) {
this.sendMessage('logout', { userId: this._accounts[userId].userId }); this.sendMessage("logout", { userId: this._accounts[userId].userId });
} }
}, },
visible: this._accounts[userId].isAuthenticated, visible: this._accounts[userId].isAuthenticated,
@ -177,9 +169,9 @@ export class BitwardenMenu implements IMenubarMenu {
private get services(): MenuItemConstructorOptions { private get services(): MenuItemConstructorOptions {
return { return {
id: 'services', id: "services",
label: this.localize('services'), label: this.localize("services"),
role: 'services', role: "services",
submenu: [], submenu: [],
visible: isMacAppStore(), visible: isMacAppStore(),
}; };
@ -187,36 +179,36 @@ export class BitwardenMenu implements IMenubarMenu {
private get hideBitwarden(): MenuItemConstructorOptions { private get hideBitwarden(): MenuItemConstructorOptions {
return { return {
id: 'hideBitwarden', id: "hideBitwarden",
label: this.localize('hideBitwarden'), label: this.localize("hideBitwarden"),
role: 'hide', role: "hide",
visible: isMacAppStore(), visible: isMacAppStore(),
}; };
} }
private get hideOthers(): MenuItemConstructorOptions { private get hideOthers(): MenuItemConstructorOptions {
return { return {
id: 'hideOthers', id: "hideOthers",
label: this.localize('hideOthers'), label: this.localize("hideOthers"),
role: 'hideOthers', role: "hideOthers",
visible: isMacAppStore(), visible: isMacAppStore(),
}; };
} }
private get showAll(): MenuItemConstructorOptions { private get showAll(): MenuItemConstructorOptions {
return { return {
id: 'showAll', id: "showAll",
label: this.localize('showAll'), label: this.localize("showAll"),
role: 'unhide', role: "unhide",
visible: isMacAppStore(), visible: isMacAppStore(),
}; };
} }
private get quitBitwarden(): MenuItemConstructorOptions { private get quitBitwarden(): MenuItemConstructorOptions {
return { return {
id: 'quitBitwarden', id: "quitBitwarden",
label: this.localize('quitBitwarden'), label: this.localize("quitBitwarden"),
role: 'quit', role: "quit",
visible: isMacAppStore(), visible: isMacAppStore(),
}; };
} }

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