1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-22 03:03:43 +00:00

move new app into popup folder

This commit is contained in:
Kyle Spearrin
2018-04-10 21:54:20 -04:00
parent 1fed135b31
commit 67ab9b1d3e
169 changed files with 35 additions and 63 deletions

View File

@@ -0,0 +1,304 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<button type="button" appBlurClick (click)="cancel()">{{'cancel' | i18n}}</button>
</div>
<div class="center">
<span class="title">{{title}}</span>
</div>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'save' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
</button>
</div>
</header>
<content *ngIf="cipher">
<div class="box">
<div class="box-header">
{{'itemInformation' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
<label for="type">{{'type' | i18n}}</label>
<select id="type" name="Type" [(ngModel)]="cipher.type">
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="name">{{'name' | i18n}}</label>
<input id="name" type="text" name="Name" [(ngModel)]="cipher.name">
</div>
<!-- Login -->
<div *ngIf="cipher.type === cipherType.Login">
<div class="box-content-row" appBoxRow>
<label for="loginUsername">{{'username' | i18n}}</label>
<input id="loginUsername" type="text" name="Login.Username"
[(ngModel)]="cipher.login.username">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="loginPassword">{{'password' | i18n}}</label>
<input id="loginPassword" class="monospaced"
type="{{showPassword ? 'text' : 'password'}}" name="Login.Password"
[(ngModel)]="cipher.login.password">
</div>
<div class="action-buttons">
<button type="button" #checkPasswordBtn class="row-btn btn" appBlurClick
title="{{'checkPassword' | i18n}}" (click)="checkPassword()"
[appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading">
<i class="fa fa-lg fa-check-circle" [hidden]="checkPasswordBtn.loading"></i>
<i class="fa fa-lg fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"></i>
</button>
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'generatePassword' | i18n}}" (click)="generatePassword()">
<i class="fa fa-lg fa-refresh"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="loginTotp">{{'authenticatorKeyTotp' | i18n}}</label>
<input id="loginTotp" type="text" name="Login.Totp" class="monospaced"
[(ngModel)]="cipher.login.totp">
</div>
</div>
<!-- Card -->
<div *ngIf="cipher.type === cipherType.Card">
<div class="box-content-row" appBoxRow>
<label for="cardCardholderName">{{'cardholderName' | i18n}}</label>
<input id="cardCardholderName" type="text" name="Card.CardCardholderName"
[(ngModel)]="cipher.card.cardholderName">
</div>
<div class="box-content-row" appBoxRow>
<label for="cardNumber">{{'number' | i18n}}</label>
<input id="cardNumber" type="text" name="Card.Number" [(ngModel)]="cipher.card.number">
</div>
<div class="box-content-row" appBoxRow>
<label for="cardBrand">{{'brand' | i18n}}</label>
<select id="cardBrand" name="Card.Brand" [(ngModel)]="cipher.card.brand">
<option *ngFor="let o of cardBrandOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpMonth">{{'expirationMonth' | i18n}}</label>
<select id="cardExpMonth" name="Card.ExpMonth" [(ngModel)]="cipher.card.expMonth">
<option *ngFor="let o of cardExpMonthOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="cardExpYear">{{'expirationYear' | i18n}}</label>
<input id="cardExpYear" type="text" name="Card.ExpYear" [(ngModel)]="cipher.card.expYear"
placeholder="{{'ex' | i18n}} 2019">
</div>
<div class="box-content-row" appBoxRow>
<label for="cardCode">{{'securityCode' | i18n}}</label>
<input id="cardCode" type="text" name="Card.Code" [(ngModel)]="cipher.card.code">
</div>
</div>
<!-- Identity -->
<div *ngIf="cipher.type === cipherType.Identity">
<div class="box-content-row" appBoxRow>
<label for="idTitle">{{'title' | i18n}}</label>
<select id="idTitle" name="Identity.Title" [(ngModel)]="cipher.identity.title">
<option *ngFor="let o of identityTitleOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-content-row" appBoxRow>
<label for="idFirstName">{{'firstName' | i18n}}</label>
<input id="idFirstName" type="text" name="Identity.FirstName"
[(ngModel)]="cipher.identity.firstName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idMiddleName">{{'middleName' | i18n}}</label>
<input id="idMiddleName" type="text" name="Identity.MiddleName"
[(ngModel)]="cipher.identity.middleName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idLastName">{{'lastName' | i18n}}</label>
<input id="idLastName" type="text" name="Identity.LastName"
[(ngModel)]="cipher.identity.lastName">
</div>
<div class="box-content-row" appBoxRow>
<label for="idUsername">{{'username' | i18n}}</label>
<input id="idUsername" type="text" name="Identity.Username"
[(ngModel)]="cipher.identity.username">
</div>
<div class="box-content-row" appBoxRow>
<label for="idCompany">{{'company' | i18n}}</label>
<input id="idCompany" type="text" name="Identity.Company"
[(ngModel)]="cipher.identity.company">
</div>
<div class="box-content-row" appBoxRow>
<label for="idSsn">{{'ssn' | i18n}}</label>
<input id="idSsn" type="text" name="Identity.SSN" [(ngModel)]="cipher.identity.ssn">
</div>
<div class="box-content-row" appBoxRow>
<label for="idPassportNumber">{{'passportNumber' | i18n}}</label>
<input id="idPassportNumber" type="text" name="Identity.PassportNumber"
[(ngModel)]="cipher.identity.passportNumber">
</div>
<div class="box-content-row" appBoxRow>
<label for="idLicenseNumber">{{'licenseNumber' | i18n}}</label>
<input id="idLicenseNumber" type="text" name="Identity.LicenseNumber"
[(ngModel)]="cipher.identity.licenseNumber">
</div>
<div class="box-content-row" appBoxRow>
<label for="idEmail">{{'email' | i18n}}</label>
<input id="idEmail" type="text" name="Identity.Email" [(ngModel)]="cipher.identity.email">
</div>
<div class="box-content-row" appBoxRow>
<label for="idPhone">{{'phone' | i18n}}</label>
<input id="idPhone" type="text" name="Identity.Phone" [(ngModel)]="cipher.identity.phone">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress1">{{'address1' | i18n}}</label>
<input id="idAddress1" type="text" name="Identity.Address1"
[(ngModel)]="cipher.identity.address1">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress2">{{'address2' | i18n}}</label>
<input id="idAddress2" type="text" name="Identity.Address2"
[(ngModel)]="cipher.identity.address2">
</div>
<div class="box-content-row" appBoxRow>
<label for="idAddress3">{{'address3' | i18n}}</label>
<input id="idAddress3" type="text" name="Identity.Address3"
[(ngModel)]="cipher.identity.address3">
</div>
<div class="box-content-row" appBoxRow>
<label for="idCity">{{'cityTown' | i18n}}</label>
<input id="idCity" type="text" name="Identity.City" [(ngModel)]="cipher.identity.city">
</div>
<div class="box-content-row" appBoxRow>
<label for="idState">{{'stateProvince' | i18n}}</label>
<input id="idState" type="text" name="Identity.State" [(ngModel)]="cipher.identity.state">
</div>
<div class="box-content-row" appBoxRow>
<label for="idPostalCode">{{'zipPostalCode' | i18n}}</label>
<input id="idPostalCode" type="text" name="Identity.PostalCode"
[(ngModel)]="cipher.identity.postalCode">
</div>
<div class="box-content-row" appBoxRow>
<label for="idCountry">{{'country' | i18n}}</label>
<input id="idCountry" type="text" name="Identity.Country"
[(ngModel)]="cipher.identity.country">
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.type === cipherType.Login">
<div class="box-content">
<ng-container *ngIf="cipher.login.hasUris">
<div class="box-content-row box-content-row-multi" appBoxRow
*ngFor="let u of cipher.login.uris; let i = index">
<a href="#" appStopClick (click)="removeUri(u)" title="{{'remove' | i18n}}">
<i class="fa fa-minus-circle fa-lg"></i>
</a>
<div class="row-main">
<label for="loginUri{{i}}">{{'uriPosition' | i18n : (i + 1)}}</label>
<input id="loginUri{{i}}" type="text" name="Login.Uris[{{i}}].Uri" [(ngModel)]="u.uri"
placeholder="{{'ex' | i18n}} https://google.com">
<label for="loginUriMatch{{i}}" class="sr-only">
{{'matchDetection' | i18n}} {{(i + 1)}}
</label>
<select id="loginUriMatch{{i}}" name="Login.Uris[{{i}}].Match" [(ngModel)]="u.match"
[hidden]="u.showOptions === false || (u.showOptions == null && u.match == null)"
(change)="loginUriMatchChanged(u)">
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleOptions' | i18n}}" (click)="toggleUriOptions(u)">
<i class="fa fa-lg fa-cog"></i>
</a>
</div>
</div>
</ng-container>
<a href="#" appStopClick appBlurClick (click)="addUri()"
class="box-content-row box-content-row-newmulti">
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{'newUri' | i18n}}
</a>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="folder">{{'folder' | i18n}}</label>
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
<option *ngFor="let f of folders" [ngValue]="f.id">{{f.name}}</option>
</select>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="favorite">{{'favorite' | i18n}}</label>
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite">
</div>
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
(click)="attachments()" *ngIf="editMode">
<div class="row-main">{{'attachments' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon"></i>
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<label for="notes">{{'notes' | i18n}}</label>
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"></textarea>
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'customFields' | i18n}}
</div>
<div class="box-content">
<ng-container *ngIf="cipher.hasFields">
<div class="box-content-row box-content-row-multi" appBoxRow
*ngFor="let f of cipher.fields; let i = index"
[ngClass]="{'box-content-row-checkbox': f.type === fieldType.Boolean}">
<a href="#" appStopClick (click)="removeField(f)" title="{{'remove' | i18n}}">
<i class="fa fa-minus-circle fa-lg"></i>
</a>
<label for="fieldName{{i}}" class="sr-only">{{'name' | i18n}}</label>
<label for="fieldValue{{i}}" class="sr-only">{{'value' | i18n}}</label>
<div class="row-main">
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name"
class="row-label" placeholder="{{'name' | i18n}}">
<input id="fieldValue{{i}}" type="text" name="Field.Value{{i}}" [(ngModel)]="f.value"
*ngIf="f.type === fieldType.Text" placeholder="{{'value' | i18n}}">
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}"
name="Field.Value{{i}}" [(ngModel)]="f.value" class="monospaced"
*ngIf="f.type === fieldType.Hidden" placeholder="{{'value' | i18n}}">
</div>
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox"
[(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean">
<div class="action-buttons" *ngIf="f.type === fieldType.Hidden">
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}"></i>
</a>
</div>
</div>
</ng-container>
<div class="box-content-row box-content-row-newmulti" appBoxRow>
<a href="#" appStopClick (click)="addField()">
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{'newCustomField' | i18n}}
</a>
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
<select id="addFieldType" name="AddFieldType" [(ngModel)]="addFieldType" class="field-type">
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
</div>
</content>
</form>

View File

@@ -0,0 +1,98 @@
import { Location } from '@angular/common';
import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { CipherType } from 'jslib/enums/cipherType';
import { AuditService } from 'jslib/abstractions/audit.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/add-edit.component';
@Component({
selector: 'app-vault-add-edit',
templateUrl: 'add-edit.component.html',
})
export class AddEditComponent extends BaseAddEditComponent implements OnInit {
constructor(cipherService: CipherService, folderService: FolderService,
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
analytics: Angulartics2, toasterService: ToasterService,
auditService: AuditService, stateService: StateService,
private route: ActivatedRoute, private router: Router,
private location: Location) {
super(cipherService, folderService, i18nService, platformUtilsService, analytics,
toasterService, auditService, stateService);
}
ngOnInit() {
this.route.queryParams.subscribe(async (params) => {
if (params.cipherId) {
this.cipherId = params.cipherId;
}
if (params.folderId) {
this.folderId = params.folderId;
}
if (params.type) {
const type = parseInt(params.type, null);
this.type = type;
}
this.editMode = !params.cipherId;
await this.load();
if (!this.editMode) {
if (params.name) {
this.cipher.name = params.name;
}
if (params.uri) {
this.cipher.login.uris[0].uri = params.uri;
}
}
});
setTimeout(() => {
if (!this.editMode) {
if (this.cipher.name != null && this.cipher.name !== '') {
document.getElementById('loginUsername').focus();
} else {
document.getElementById('name').focus();
}
}
}, 200);
}
async submit(): Promise<boolean> {
if (await super.submit()) {
this.location.back();
return true;
}
return false;
}
cancel() {
super.cancel();
this.location.back();
}
async generatePassword(): Promise<boolean> {
const confirmed = await super.generatePassword();
if (confirmed) {
this.stateService.save('addEditCipher', this.cipher);
this.router.navigate(['generator']);
}
return confirmed;
}
}

View File

@@ -0,0 +1,41 @@
<header>
<div class="left">
<button type="button" appBlurClick (click)="back()">
<span class="header-icon"><i class="fa fa-chevron-left"></i></span>
<span>{{'back' | i18n}}</span>
</button>
</div>
<div class="search">
<input type="search" placeholder="{{searchPlaceholder || ('searchVault' | i18n)}}" id="search"
[(ngModel)]="searchText" appAutofocus>
<i class="fa fa-search"></i>
</div>
<div class="right" *ngIf="showAdd">
<button type="button" appBlurClick (click)="addCipher()" title="{{'addItem' | i18n}}">
<i class="fa fa-plus fa-lg fa-fw"></i>
</button>
</div>
</header>
<content>
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
<div class="no-items" *ngIf="!searchedCiphers.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded"></i>
<ng-container *ngIf="loaded">
<p>{{'noItemsInList' | i18n}}</p>
<button (click)="addCipher()" class="btn block primary link" *ngIf="showAdd">
{{'addItem' | i18n}}
</button>
</ng-container>
</div>
<div class="box list only-list" *ngIf="searchedCiphers.length > 0">
<div class="box-header">
{{groupingTitle}}
<span class="flex-right">{{searchedCiphers.length}}</span>
</div>
<div class="box-content">
<app-ciphers-list [ciphers]="searchedCiphers" title="{{'viewItem' | i18n}}"
(onSelected)="selectCipher($event)"></app-ciphers-list>
</div>
</div>
</ng-container>
</content>

View File

@@ -0,0 +1,150 @@
import { Location } from '@angular/common';
import {
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { FolderService } from 'jslib/abstractions/folder.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { StateService } from 'jslib/abstractions/state.service';
import { CipherType } from 'jslib/enums/cipherType';
import { CipherView } from 'jslib/models/view/cipherView';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { CiphersComponent as BaseCiphersComponent } from 'jslib/angular/components/ciphers.component';
import { PopupUtilsService } from '../services/popup-utils.service';
const ComponentId = 'CiphersComponent';
@Component({
selector: 'app-vault-ciphers',
templateUrl: 'ciphers.component.html',
})
export class CiphersComponent extends BaseCiphersComponent implements OnInit, OnDestroy {
groupingTitle: string;
searchText: string;
state: any;
showAdd = true;
folderId: string = null;
type: CipherType = null;
constructor(cipherService: CipherService, private route: ActivatedRoute,
private router: Router, private location: Location,
private ngZone: NgZone, private broadcasterService: BroadcasterService,
private changeDetectorRef: ChangeDetectorRef, private stateService: StateService,
private popupUtils: PopupUtilsService, private i18nService: I18nService,
private folderService: FolderService, private collectionService: CollectionService) {
super(cipherService);
}
async ngOnInit() {
this.route.queryParams.subscribe(async (params) => {
if (params.type) {
this.searchPlaceholder = this.i18nService.t('searchType');
this.type = parseInt(params.type, null);
switch (this.type) {
case CipherType.Login:
this.groupingTitle = this.i18nService.t('logins');
break;
case CipherType.Card:
this.groupingTitle = this.i18nService.t('cards');
break;
case CipherType.Identity:
this.groupingTitle = this.i18nService.t('identities');
break;
case CipherType.SecureNote:
this.groupingTitle = this.i18nService.t('secureNotes');
break;
default:
break;
}
await super.load((c) => c.type === this.type);
} else if (params.folderId) {
this.folderId = params.folderId === 'none' ? null : params.folderId;
this.searchPlaceholder = this.i18nService.t('searchFolder');
if (this.folderId != null) {
const folder = await this.folderService.get(this.folderId);
if (folder != null) {
this.groupingTitle = (await folder.decrypt()).name;
}
} else {
this.groupingTitle = this.i18nService.t('noneFolder');
}
await super.load((c) => c.folderId === this.folderId);
} else if (params.collectionId) {
this.showAdd = false;
this.searchPlaceholder = this.i18nService.t('searchCollection');
const collection = await this.collectionService.get(params.collectionId);
if (collection != null) {
this.groupingTitle = (await collection.decrypt()).name;
}
await super.load((c) => c.collectionIds != null && c.collectionIds.indexOf(params.collectionId) > -1);
} else {
this.groupingTitle = this.i18nService.t('allItems');
await super.load();
}
this.state = (await this.stateService.get<any>(ComponentId)) || {};
if (this.state.searchText) {
this.searchText = this.state.searchText;
}
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0);
});
this.broadcasterService.subscribe(ComponentId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
window.setTimeout(() => {
this.load();
}, 500);
break;
default:
break;
}
this.changeDetectorRef.detectChanges();
})
});
}
ngOnDestroy() {
this.saveState();
this.broadcasterService.unsubscribe(ComponentId);
}
selectCipher(cipher: CipherView) {
super.selectCipher(cipher);
this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } });
}
addCipher() {
super.addCipher();
this.router.navigate(['/add-cipher'], { queryParams: { folderId: this.folderId, type: this.type } });
}
back() {
this.location.back();
}
private async saveState() {
this.state = {
scrollY: this.popupUtils.getContentScrollY(window),
searchText: this.searchText,
};
await this.stateService.save(ComponentId, this.state);
}
}

View File

@@ -0,0 +1,62 @@
<header>
<div class="left">
<app-pop-out *ngIf="showPopout"></app-pop-out>
<button type="button" appBlurClick (click)="refresh()" title="{{'refresh' | i18n}}" *ngIf="inSidebar">
<i class="fa fa-refresh fa-lg fa-fw"></i>
</button>
</div>
<div class="search">
<input type="search" placeholder="{{'searchVault' | i18n}}" id="search"
[(ngModel)]="searchText" (input)="searchVault()" appAutofocus>
<i class="fa fa-search"></i>
</div>
<div class="right">
<button type="button" appBlurClick (click)="addCipher()" title="{{'addItem' | i18n}}">
<i class="fa fa-plus fa-lg fa-fw"></i>
</button>
</div>
</header>
<content>
<div class="no-items" *ngIf="!loaded">
<i class="fa fa-spinner fa-spin fa-3x"></i>
</div>
<ng-container *ngIf="loaded">
<div class="box list" *ngIf="loginCiphers">
<div class="box-header">
{{'typeLogins' | i18n}}
<span class="flex-right">{{loginCiphers.length}}</span>
</div>
<div class="box-content">
<app-ciphers-list [ciphers]="loginCiphers" title="{{'autoFill' | i18n}}" [showView]="true"
(onSelected)="fillCipher($event)" (onView)="viewCipher($event)"
*ngIf="loginCiphers.length"></app-ciphers-list>
<div class="box-content-row text-center padded no-hover" *ngIf="!loginCiphers.length">
<p>{{'autoFillInfo' | i18n}}</p>
<button type="button" class="btn primary link block" (click)="addCipher()">
{{'addLogin' | i18n}}
</button>
</div>
</div>
</div>
<div class="box list" *ngIf="cardCiphers && cardCiphers.length">
<div class="box-header">
{{'cards' | i18n}}
<span class="flex-right">{{cardCiphers.length}}</span>
</div>
<div class="box-content">
<app-ciphers-list [ciphers]="cardCiphers" title="{{'autoFill' | i18n}}" [showView]="true"
(onSelected)="fillCipher($event)" (onView)="viewCipher($event)"></app-ciphers-list>
</div>
</div>
<div class="box list" *ngIf="identityCiphers && identityCiphers.length">
<div class="box-header">
{{'identities' | i18n}}
<span class="flex-right">{{identityCiphers.length}}</span>
</div>
<div class="box-content">
<app-ciphers-list [ciphers]="identityCiphers" title="{{'autoFill' | i18n}}" [showView]="true"
(onSelected)="fillCipher($event)" (onView)="viewCipher($event)"></app-ciphers-list>
</div>
</div>
</ng-container>
</content>

View File

@@ -0,0 +1,188 @@
import {
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { BrowserApi } from '../../browser/browserApi';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { CipherType } from 'jslib/enums/cipherType';
import { CipherView } from 'jslib/models/view/cipherView';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { AutofillService } from '../../services/abstractions/autofill.service';
import { PopupUtilsService } from '../services/popup-utils.service';
import { setTimeout } from 'timers';
const BroadcasterSubscriptionId = 'CurrentTabComponent';
@Component({
selector: 'app-current-tab',
templateUrl: 'current-tab.component.html',
})
export class CurrentTabComponent implements OnInit, OnDestroy {
pageDetails: any[] = [];
cardCiphers: CipherView[];
identityCiphers: CipherView[];
loginCiphers: CipherView[];
url: string;
domain: string;
searchText: string;
canAutofill = false;
inSidebar = false;
showPopout = true;
disableSearch = false;
loaded = false;
constructor(private platformUtilsService: PlatformUtilsService, private cipherService: CipherService,
private popupUtilsService: PopupUtilsService, private autofillService: AutofillService,
private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private router: Router,
private ngZone: NgZone, private broadcasterService: BroadcasterService,
private changeDetectorRef: ChangeDetectorRef) {
this.inSidebar = popupUtilsService.inSidebar(window);
this.showPopout = !this.inSidebar && !platformUtilsService.isSafari();
this.disableSearch = platformUtilsService.isEdge();
}
ngOnInit() {
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
if (this.loaded) {
setTimeout(() => {
this.load();
}, 500);
}
break;
case 'collectPageDetailsResponse':
if (message.sender === BroadcasterSubscriptionId) {
this.pageDetails.push({
frameId: message.webExtSender.frameId,
tab: message.tab,
details: message.details,
});
}
break;
default:
break;
}
this.changeDetectorRef.detectChanges();
})
});
this.load();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async refresh() {
await this.load();
}
addCipher() {
this.router.navigate(['/add-cipher'], { queryParams: { name: this.domain, uri: this.url } });
}
viewCipher(cipher: CipherView) {
this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } });
}
async fillCipher(cipher: CipherView) {
if (!this.canAutofill) {
this.analytics.eventTrack.next({ action: 'Autofilled Error' });
this.toasterService.popAsync('error', null, this.i18nService.t('autofillError'));
return;
}
try {
const totpCode = await this.autofillService.doAutoFill({
cipher: cipher,
pageDetails: this.pageDetails,
fromBackground: false,
doc: window.document,
});
this.analytics.eventTrack.next({ action: 'Autofilled' });
if (totpCode != null && this.platformUtilsService.isFirefox()) {
this.platformUtilsService.copyToClipboard(totpCode, { doc: window.document });
}
if (this.popupUtilsService.inPopup(window)) {
BrowserApi.closePopup(window);
}
} catch {
this.analytics.eventTrack.next({ action: 'Autofilled Error' });
this.toasterService.popAsync('error', null, this.i18nService.t('autofillError'));
}
}
searchVault() {
this.router.navigate(['/tabs/vault'], { queryParams: { searchText: this.searchText } });
}
private async load() {
const tab = await BrowserApi.getTabFromCurrentWindow();
if (tab) {
this.url = tab.url;
} else {
this.loaded = true;
return;
}
this.domain = this.platformUtilsService.getDomain(this.url);
BrowserApi.tabSendMessage(tab, {
command: 'collectPageDetails',
tab: tab,
sender: BroadcasterSubscriptionId,
}).then(() => {
this.canAutofill = true;
});
const ciphers = await this.cipherService.getAllDecryptedForUrl(this.url, [
CipherType.Card,
CipherType.Identity,
]);
this.loginCiphers = [];
this.cardCiphers = [];
this.identityCiphers = [];
ciphers.forEach((c) => {
switch (c.type) {
case CipherType.Login:
this.loginCiphers.push(c);
break;
case CipherType.Card:
this.cardCiphers.push(c);
break;
case CipherType.Identity:
this.identityCiphers.push(c);
break;
default:
break;
}
});
this.loaded = true;
}
}

View File

@@ -0,0 +1,140 @@
<header>
<div class="left">
<app-pop-out></app-pop-out>
</div>
<div class="search">
<input type="search" placeholder="{{'searchVault' | i18n}}" id="search"
[(ngModel)]="searchText" appAutofocus>
<i class="fa fa-search"></i>
</div>
<div class="right">
<button appBlurClick (click)="addCipher()" title="{{'addItem' | i18n}}">
<i class="fa fa-plus fa-lg fa-fw"></i>
</button>
</div>
</header>
<content>
<div class="no-items" *ngIf="!ciphers || !ciphers.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded"></i>
<ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x"></i>
<p>{{'noItemsInList' | i18n}}</p>
<button (click)="addCipher()" class="btn block primary link">{{'addItem' | i18n}}</button>
</ng-container>
</div>
<ng-container *ngIf="ciphers && ciphers.length && (!searchText || searchText.length < 2)">
<div class="box list" *ngIf="favoriteCiphers">
<div class="box-header">
{{'favorites' | i18n}}
<span class="flex-right">{{favoriteCiphers.length}}</span>
</div>
<div class="box-content">
<app-ciphers-list [ciphers]="favoriteCiphers" title="{{'viewItem' | i18n}}"
(onSelected)="selectCipher($event)"></app-ciphers-list>
</div>
</div>
<div class="box list">
<div class="box-header">
{{'types' | i18n}}
<span class="flex-right">4</span>
</div>
<div class="box-content single-line">
<a href="#" class="box-content-row" appStopClick appBlurClick
(click)="selectType(cipherType.Login)">
<div class="row-main">
<div class="icon"><i class="fa fa-fw fa-lg fa-globe"></i></div>
<span class="text">{{'typeLogin' | i18n}}</span>
</div>
<span class="row-sub-label">{{typeCounts.get(cipherType.Login) || 0}}</span>
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick
(click)="selectType(cipherType.Card)">
<div class="row-main">
<div class="icon"><i class="fa fa-fw fa-lg fa-credit-card"></i></div>
<span class="text">{{'typeCard' | i18n}}</span>
</div>
<span class="row-sub-label">{{typeCounts.get(cipherType.Card) || 0}}</span>
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick
(click)="selectType(cipherType.Identity)">
<div class="row-main">
<div class="icon"><i class="fa fa-fw fa-lg fa-id-card-o"></i></div>
<span class="text">{{'typeIdentity' | i18n}}</span>
</div>
<span class="row-sub-label">{{typeCounts.get(cipherType.Identity) || 0}}</span>
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
</a>
<a href="#" class="box-content-row" appStopClick appBlurClick
(click)="selectType(cipherType.SecureNote)">
<div class="row-main">
<div class="icon"><i class="fa fa-fw fa-lg fa-sticky-note-o"></i></div>
<span class="text">{{'typeSecureNote' | i18n}}</span>
</div>
<span class="row-sub-label">{{typeCounts.get(cipherType.SecureNote) || 0}}</span>
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
</a>
</div>
</div>
<div class="box list">
<div class="box-header">
{{'folders' | i18n}}
<span class="flex-right">{{folderCount}}</span>
</div>
<div class="box-content single-line">
<a *ngFor="let f of folders" href="#" class="box-content-row"
appStopClick appBlurClick (click)="selectFolder(f)">
<div class="row-main">
<div class="icon">
<i class="fa fa-fw fa-lg"
[ngClass]="{'fa-folder-open': f.id, 'fa-folder-open-o': !f.id}"></i>
</div>
<span class="text">{{f.name}}</span>
</div>
<span class="row-sub-label">{{folderCounts.get(f.id) || 0}}</span>
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
</a>
</div>
</div>
<div class="box list">
<div class="box-header">
{{'collections' | i18n}}
<span class="flex-right">{{collections.length}}</span>
</div>
<div class="box-content single-line">
<a *ngFor="let c of collections" href="#" class="box-content-row"
appStopClick appBlurClick (click)="selectCollection(c)">
<div class="row-main">
<div class="icon"><i class="fa fa-fw fa-lg fa-cube"></i></div>
<span class="text">{{c.name}}</span>
</div>
<span class="row-sub-label">{{collectionCounts.get(c.id) || 0}}</span>
<span><i class="fa fa-chevron-right fa-lg row-sub-icon"></i></span>
</a>
</div>
</div>
<div class="box list" *ngIf="showNoFolderCiphers">
<div class="box-header">
{{'noneFolder' | i18n}}
<div class="flex-right">{{noFolderCiphers.length}}</div>
</div>
<div class="box-content">
<app-ciphers-list [ciphers]="noFolderCiphers" title="{{'viewItem' | i18n}}"
(onSelected)="selectCipher($event)"></app-ciphers-list>
</div>
</div>
</ng-container>
<ng-container *ngIf="ciphers && ciphers.length && searchText && searchText.length > 1 &&
(ciphers | searchCiphers: searchText) as searchedCiphers">
<div class="no-items" *ngIf="!searchedCiphers.length">
<p>{{'noItemsInList' | i18n}}</p>
</div>
<div class="box list full-list" *ngIf="searchedCiphers.length > 0">
<div class="box-content">
<app-ciphers-list [ciphers]="searchedCiphers" title="{{'viewItem' | i18n}}"
(onSelected)="selectCipher($event)"></app-ciphers-list>
</div>
</div>
</ng-container>
</content>

View File

@@ -0,0 +1,182 @@
import {
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { CipherType } from 'jslib/enums/cipherType';
import { CollectionView } from 'jslib/models/view/collectionView';
import { CipherView } from 'jslib/models/view/cipherView';
import { FolderView } from 'jslib/models/view/folderView';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { StateService } from 'jslib/abstractions/state.service';
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib/angular/components/groupings.component';
import { PopupUtilsService } from '../services/popup-utils.service';
const ComponentId = 'GroupingsComponent';
@Component({
selector: 'app-vault-groupings',
templateUrl: 'groupings.component.html',
})
export class GroupingsComponent extends BaseGroupingsComponent implements OnInit, OnDestroy {
ciphers: CipherView[];
favoriteCiphers: CipherView[];
noFolderCiphers: CipherView[];
folderCount: number;
folderCounts = new Map<string, number>();
collectionCounts = new Map<string, number>();
typeCounts = new Map<CipherType, number>();
showNoFolderCiphers = false;
searchText: string;
state: any;
constructor(collectionService: CollectionService, folderService: FolderService,
private cipherService: CipherService, private router: Router,
private ngZone: NgZone, private broadcasterService: BroadcasterService,
private changeDetectorRef: ChangeDetectorRef, private route: ActivatedRoute,
private stateService: StateService, private popupUtils: PopupUtilsService) {
super(collectionService, folderService);
}
ngOnInit() {
this.stateService.remove('CiphersComponent');
this.broadcasterService.subscribe(ComponentId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'syncCompleted':
window.setTimeout(() => {
this.load();
}, 500);
break;
default:
break;
}
this.changeDetectorRef.detectChanges();
})
});
this.route.queryParams.subscribe(async (params) => {
this.state = (await this.stateService.get<any>(ComponentId)) || {};
if (this.state.searchText) {
this.searchText = this.state.searchText;
} else if (params.searchText) {
this.searchText = params.searchText;
}
this.load();
window.setTimeout(() => this.popupUtils.setContentScrollY(window, this.state.scrollY), 0);
});
}
ngOnDestroy() {
this.saveState();
this.broadcasterService.unsubscribe(ComponentId);
}
async load() {
await super.load();
super.loaded = false;
await this.loadCiphers();
this.showNoFolderCiphers = this.noFolderCiphers != null && this.noFolderCiphers.length < 100 &&
this.collections.length === 0;
if (this.showNoFolderCiphers) {
// Remove "No Folder" from folder listing
this.folders.pop();
this.folderCount = this.folders.length;
} else {
this.folderCount = this.folders.length - 1;
}
super.loaded = true;
}
async loadCiphers() {
this.ciphers = await this.cipherService.getAllDecrypted();
this.ciphers.forEach((c) => {
if (c.favorite) {
if (this.favoriteCiphers == null) {
this.favoriteCiphers = [];
}
this.favoriteCiphers.push(c);
}
if (c.folderId == null) {
if (this.noFolderCiphers == null) {
this.noFolderCiphers = [];
}
this.noFolderCiphers.push(c);
}
if (this.typeCounts.has(c.type)) {
this.typeCounts.set(c.type, this.typeCounts.get(c.type) + 1);
} else {
this.typeCounts.set(c.type, 1);
}
if (this.folderCounts.has(c.folderId)) {
this.folderCounts.set(c.folderId, this.folderCounts.get(c.folderId) + 1);
} else {
this.folderCounts.set(c.folderId, 1);
}
if (c.collectionIds != null) {
c.collectionIds.forEach((colId) => {
if (this.collectionCounts.has(colId)) {
this.collectionCounts.set(colId, this.collectionCounts.get(colId) + 1);
} else {
this.collectionCounts.set(colId, 1);
}
});
}
});
}
async selectType(type: CipherType) {
super.selectType(type);
this.router.navigate(['/ciphers'], { queryParams: { type: type } });
}
async selectFolder(folder: FolderView) {
super.selectFolder(folder);
this.router.navigate(['/ciphers'], { queryParams: { folderId: folder.id || 'none' } });
}
async selectCollection(collection: CollectionView) {
super.selectCollection(collection);
this.router.navigate(['/ciphers'], { queryParams: { collectionId: collection.id } });
}
async selectCipher(cipher: CipherView) {
this.router.navigate(['/view-cipher'], { queryParams: { cipherId: cipher.id } });
}
async addCipher() {
this.router.navigate(['/add-cipher']);
}
private async saveState() {
this.state = {
scrollY: this.popupUtils.getContentScrollY(window),
searchText: this.searchText,
};
await this.stateService.save(ComponentId, this.state);
}
}

View File

@@ -0,0 +1,252 @@
<header>
<div class="left">
<button type="button" appBlurClick (click)="close()">{{'close' | i18n}}</button>
</div>
<div class="center">
<span class="title">{{'viewItem' | i18n}}</span>
</div>
<div class="right">
<button type="button" appBlurClick (click)="edit()">{{'edit' | i18n}}</button>
</div>
</header>
<content *ngIf="cipher">
<div class="box">
<div class="box-header">
{{'itemInformation' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row">
<span class="row-label">{{'name' | i18n}}</span>
{{cipher.name}}
</div>
<!-- Login -->
<div *ngIf="cipher.login">
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.username">
<div class="row-main">
<span class="row-label">{{'username' | i18n}}</span>
{{cipher.login.username}}
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyUsername' | i18n}}"
(click)="copy(cipher.login.username, 'username', 'Username')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
</div>
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.password">
<div class="row-main">
<span class="row-label">{{'password' | i18n}}</span>
<span [hidden]="showPassword" class="monospaced">{{cipher.login.maskedPassword}}</span>
<span [hidden]="!showPassword" class="monospaced">{{cipher.login.password}}</span>
</div>
<div class="action-buttons">
<button type="button" #checkPasswordBtn class="row-btn btn" appBlurClick
title="{{'checkPassword' | i18n}}" (click)="checkPassword()"
[appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading">
<i class="fa fa-lg fa-check-circle" [hidden]="checkPasswordBtn.loading"></i>
<i class="fa fa-lg fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"></i>
</button>
<a class="row-btn" href="#" appStopClick title="{{'toggleVisibility' | i18n}}"
(click)="togglePassword()">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
<a class="row-btn" href="#" appStopClick title="{{'copyPassword' | i18n}}"
(click)="copy(cipher.login.password, 'password', 'Password')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
</div>
</div>
<div class="box-content-row box-content-row-flex totp" [ngClass]="{'low': totpLow}"
*ngIf="cipher.login.totp && totpCode">
<div class="row-main">
<span class="row-label">{{'verificationCodeTotp' | i18n}}</span>
<span class="totp-code">{{totpCodeFormatted}}</span>
</div>
<span class="totp-countdown">
<span class="totp-sec">{{totpSec}}</span>
<svg>
<g>
<circle class="totp-circle inner" r="12.6" cy="16" cx="16"
[ngStyle]="{'stroke-dashoffset.px': totpDash}"></circle>
<circle class="totp-circle outer" r="14" cy="16" cx="16"></circle>
</g>
</svg>
</span>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
</div>
</div>
</div>
<!-- Card -->
<div *ngIf="cipher.card">
<div class="box-content-row" *ngIf="cipher.card.cardholderName">
<span class="row-label">{{'cardholderName' | i18n}}</span>
{{cipher.card.cardholderName}}
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number">
<div class="row-main">
<span class="row-label">{{'number' | i18n}}</span>
{{cipher.card.number}}
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyNumber' | i18n}}"
(click)="copy(cipher.card.number, 'number', 'Number')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
</div>
</div>
<div class="box-content-row" *ngIf="cipher.card.brand">
<span class="row-label">{{'brand' | i18n}}</span>
{{cipher.card.brand}}
</div>
<div class="box-content-row" *ngIf="cipher.card.expiration">
<span class="row-label">{{'expiration' | i18n}}</span>
{{cipher.card.expiration}}
</div>
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.code">
<div class="row-main">
<span class="row-label">{{'securityCode' | i18n}}</span>
{{cipher.card.code}}
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copySecurityCode' | i18n}}"
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
</div>
</div>
</div>
<!-- Identity -->
<div *ngIf="cipher.identity">
<div class="box-content-row" *ngIf="cipher.identity.fullName">
<span class="row-label">{{'identityName' | i18n}}</span>
{{cipher.identity.fullName}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.username">
<span class="row-label">{{'username' | i18n}}</span>
{{cipher.identity.username}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.company">
<span class="row-label">{{'company' | i18n}}</span>
{{cipher.identity.company}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.ssn">
<span class="row-label">{{'ssn' | i18n}}</span>
{{cipher.identity.ssn}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.passportNumber">
<span class="row-label">{{'passportNumber' | i18n}}</span>
{{cipher.identity.passportNumber}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.licenseNumber">
<span class="row-label">{{'licenseNumber' | i18n}}</span>
{{cipher.identity.licenseNumber}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.email">
<span class="row-label">{{'email' | i18n}}</span>
{{cipher.identity.email}}
</div>
<div class="box-content-row" *ngIf="cipher.identity.phone">
<span class="row-label">{{'phone' | i18n}}</span>
{{cipher.identity.phone}}
</div>
<div class="box-content-row"
*ngIf="cipher.identity.address1 || cipher.identity.city || cipher.identity.country">
<span class="row-label">{{'address' | i18n}}</span>
<div *ngIf="cipher.identity.address1">{{cipher.identity.address1}}</div>
<div *ngIf="cipher.identity.address2">{{cipher.identity.address2}}</div>
<div *ngIf="cipher.identity.address3">{{cipher.identity.address3}}</div>
<div *ngIf="cipher.identity.city || cipher.identity.state || cipher.identity.postalCode">
{{cipher.identity.city || '-'}},
{{cipher.identity.state || '-'}},
{{cipher.identity.postalCode || '-'}}
</div>
<div *ngIf="cipher.identity.country">{{cipher.identity.country}}</div>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.login && cipher.login.hasUris">
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let u of cipher.login.uris; let i = index">
<div class="row-main">
<span class="row-label" *ngIf="!u.isWebsite">{{'uri' | i18n}}</span>
<span class="row-label" *ngIf="u.isWebsite">{{'website' | i18n}}</span>
<span title="{{u.uri}}">{{u.domainOrUri}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'launch' | i18n}}"
*ngIf="u.canLaunch" (click)="launch(u)">
<i class="fa fa-lg fa-share-square-o"></i>
</a>
<a class="row-btn" href="#" appStopClick title="{{'copyUri' | i18n}}"
(click)="copy(u.uri, u.isWebsite ? 'website' : 'uri', 'URI')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.notes">
<div class="box-header">
{{'notes' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row pre">{{cipher.notes}}</div>
</div>
</div>
<div class="box" *ngIf="cipher.hasFields">
<div class="box-header">
{{'customFields' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let field of cipher.fields">
<div class="row-main">
<span class="row-label">{{field.name}}</span>
<div *ngIf="field.type === fieldType.Text">
{{field.value || '&nbsp;'}}
</div>
<div *ngIf="field.type === fieldType.Hidden">
<span [hidden]="!field.showValue" class="monospaced">{{field.value}}</span>
<span [hidden]="field.showValue" class="monospaced">{{field.maskedValue}}</span>
</div>
<div *ngIf="field.type === fieldType.Boolean">
<i class="fa fa-check-square-o" *ngIf="field.value === 'true'"></i>
<i class="fa fa-square-o" *ngIf="field.value !== 'true'"></i>
</div>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'toggleVisibility' | i18n}}"
*ngIf="field.type === fieldType.Hidden" (click)="toggleFieldValue(field)">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !field.showValue, 'fa-eye-slash': field.showValue}"></i>
</a>
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
*ngIf="field.value && field.type !== fieldType.Boolean"
(click)="copy(field.value, 'value', 'Field')">
<i class="fa fa-lg fa-clipboard"></i>
</a>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.hasAttachments && isPremium">
<div class="box-header">
{{'attachments' | i18n}}
</div>
<div class="box-content">
<a class="box-content-row box-content-row-flex text-default"
*ngFor="let attachment of cipher.attachments"
href="#" appStopClick appBlurCLick (click)="downloadAttachment(attachment)">
<span class="row-main">{{attachment.fileName}}</span>
<small class="row-sub-label">{{attachment.sizeName}}</small>
<i class="fa fa-download fa-fw row-sub-icon" *ngIf="!attachment.downloading"></i>
<i class="fa fa-spinner fa-fw fa-spin row-sub-icon" *ngIf="attachment.downloading"></i>
</a>
</div>
</div>
</content>

View File

@@ -0,0 +1,61 @@
import { Location } from '@angular/common';
import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { BrowserApi } from '../../browser/browserApi';
import { AuditService } from 'jslib/abstractions/audit.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { TokenService } from 'jslib/abstractions/token.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { ViewComponent as BaseViewComponent } from 'jslib/angular/components/view.component';
@Component({
selector: 'app-vault-view',
templateUrl: 'view.component.html',
})
export class ViewComponent extends BaseViewComponent implements OnInit {
constructor(cipherService: CipherService, totpService: TotpService,
tokenService: TokenService, toasterService: ToasterService,
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, analytics: Angulartics2,
auditService: AuditService, private route: ActivatedRoute,
private router: Router, private location: Location) {
super(cipherService, totpService, tokenService, toasterService, cryptoService, platformUtilsService,
i18nService, analytics, auditService, window);
}
ngOnInit() {
this.route.queryParams.subscribe(async (params) => {
if (params.cipherId) {
this.cipherId = params.cipherId;
} else {
this.close();
}
await this.load();
});
}
edit() {
super.edit();
this.router.navigate(['/edit-cipher'], { queryParams: { cipherId: this.cipher.id } });
}
close() {
this.location.back();
}
}