mirror of
https://github.com/bitwarden/web
synced 2025-12-16 16:23:31 +00:00
various vault functionality
This commit is contained in:
2
jslib
2
jslib
Submodule jslib updated: c59bca05bb...8211e19db0
@@ -224,6 +224,9 @@
|
|||||||
"favorite": {
|
"favorite": {
|
||||||
"message": "Favorite"
|
"message": "Favorite"
|
||||||
},
|
},
|
||||||
|
"unfavorite": {
|
||||||
|
"message": "Unfavorite"
|
||||||
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
"message": "Edit"
|
"message": "Edit"
|
||||||
},
|
},
|
||||||
@@ -321,5 +324,66 @@
|
|||||||
},
|
},
|
||||||
"other": {
|
"other": {
|
||||||
"message": "Other"
|
"message": "Other"
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"message": "Share"
|
||||||
|
},
|
||||||
|
"valueCopied": {
|
||||||
|
"message": "$VALUE$ copied",
|
||||||
|
"description": "Value has been copied to the clipboard.",
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"copyValue": {
|
||||||
|
"message": "Copy Value",
|
||||||
|
"description": "Copy value to clipboard"
|
||||||
|
},
|
||||||
|
"copyPassword": {
|
||||||
|
"message": "Copy Password",
|
||||||
|
"description": "Copy password to clipboard"
|
||||||
|
},
|
||||||
|
"copyUsername": {
|
||||||
|
"message": "Copy Username",
|
||||||
|
"description": "Copy username to clipboard"
|
||||||
|
},
|
||||||
|
"copyNumber": {
|
||||||
|
"message": "Copy Number",
|
||||||
|
"description": "Copy credit card number"
|
||||||
|
},
|
||||||
|
"copySecurityCode": {
|
||||||
|
"message": "Copy Security Code",
|
||||||
|
"description": "Copy credit card security code (CVV)"
|
||||||
|
},
|
||||||
|
"copyUri": {
|
||||||
|
"message": "Copy URI",
|
||||||
|
"description": "Copy URI to clipboard"
|
||||||
|
},
|
||||||
|
"myVault": {
|
||||||
|
"message": "My Vault"
|
||||||
|
},
|
||||||
|
"shareSelected": {
|
||||||
|
"message": "Share Selected"
|
||||||
|
},
|
||||||
|
"deleteSelected": {
|
||||||
|
"message": "Delete Selected"
|
||||||
|
},
|
||||||
|
"moveSelected": {
|
||||||
|
"message": "Move Selected"
|
||||||
|
},
|
||||||
|
"selectAll": {
|
||||||
|
"message": "Select All"
|
||||||
|
},
|
||||||
|
"unselectAll": {
|
||||||
|
"message": "Unselect All"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"message": "Value"
|
||||||
|
},
|
||||||
|
"launch": {
|
||||||
|
"message": "Launch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<div class="modal fade">
|
<div class="modal fade">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<div class="modal-header">{{title}}</div>
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title">{{title}}</h2>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="modal-body" *ngIf="cipher">
|
<div class="modal-body" *ngIf="cipher">
|
||||||
<div class="row" *ngIf="!editMode">
|
<div class="row" *ngIf="!editMode">
|
||||||
<div class="col-6 form-group">
|
<div class="col-6 form-group">
|
||||||
@@ -24,28 +29,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Login -->
|
<!-- Login -->
|
||||||
<div *ngIf="cipher.type === cipherType.Login">
|
<ng-container *ngIf="cipher.type === cipherType.Login">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 form-group">
|
<div class="col-6 form-group">
|
||||||
<label for="loginUsername">{{'username' | i18n}}</label>
|
<label for="loginUsername">{{'username' | i18n}}</label>
|
||||||
|
<div class="input-group">
|
||||||
<input id="loginUsername" class="form-control" type="text" name="Login.Username" [(ngModel)]="cipher.login.username">
|
<input id="loginUsername" class="form-control" type="text" name="Login.Username" [(ngModel)]="cipher.login.username">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'copyUsername' | i18n}}" (click)="copy(cipher.login.username, 'username', 'Username')"
|
||||||
|
tabindex="-1">
|
||||||
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 form-group">
|
<div class="col-6 form-group">
|
||||||
|
<div class="d-flex">
|
||||||
<label for="loginPassword">{{'password' | i18n}}</label>
|
<label for="loginPassword">{{'password' | i18n}}</label>
|
||||||
|
<div class="ml-auto d-flex">
|
||||||
|
<a href="#" class="d-block mr-2" appStopClick appBlurClick title="{{'generatePassword' | i18n}}" (click)="generatePassword()">
|
||||||
|
<i class="fa fa-lg fa-fw fa-refresh"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="d-block" #checkPasswordBtn appStopClick appBlurClick title="{{'checkPassword' | i18n}}" (click)="checkPassword()"
|
||||||
|
[appApiAction]="checkPasswordPromise">
|
||||||
|
<i class="fa fa-lg fa-fw fa-check-circle" [hidden]="checkPasswordBtn.loading"></i>
|
||||||
|
<i class="fa fa-lg fa-fw fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="loginPassword" class="form-control text-monospace" type="{{showPassword ? 'text' : 'password'}}" name="Login.Password"
|
<input id="loginPassword" class="form-control text-monospace" type="{{showPassword ? 'text' : 'password'}}" name="Login.Password"
|
||||||
[(ngModel)]="cipher.login.password">
|
[(ngModel)]="cipher.login.password">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" #checkPasswordBtn class="btn btn-outline-secondary" appBlurClick title="{{'checkPassword' | i18n}}"
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()"
|
||||||
(click)="checkPassword()" [appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading">
|
tabindex="-1">
|
||||||
<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>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
|
|
||||||
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'generatePassword' | i18n}}" (click)="generatePassword()">
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'copyPassword' | i18n}}" (click)="copy(cipher.login.password, 'password', 'Password')"
|
||||||
<i class="fa fa-lg fa-refresh"></i>
|
tabindex="-1">
|
||||||
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,11 +81,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="cipher.login.hasUris">
|
<ng-container *ngIf="cipher.login.hasUris">
|
||||||
<div class="row" appBoxRow *ngFor="let u of cipher.login.uris; let i = index">
|
<div class="row" appBoxRow *ngFor="let u of cipher.login.uris; let i = index">
|
||||||
<div class="col-7">
|
<div class="col-7 form-group">
|
||||||
<label for="loginUri{{i}}">{{'uriPosition' | i18n : (i + 1)}}</label>
|
<label for="loginUri{{i}}">{{'uriPosition' | i18n : (i + 1)}}</label>
|
||||||
|
<div class="input-group">
|
||||||
<input class="form-control" id="loginUri{{i}}" type="text" name="Login.Uris[{{i}}].Uri" [(ngModel)]="u.uri" placeholder="{{'ex' | i18n}} https://google.com">
|
<input class="form-control" id="loginUri{{i}}" type="text" name="Login.Uris[{{i}}].Uri" [(ngModel)]="u.uri" placeholder="{{'ex' | i18n}} https://google.com">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'launch' | i18n}}" (click)="launch(u)" tabindex="-1">
|
||||||
|
<i class="fa fa-lg fa-share"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'copyUri' | i18n}}" (click)="copy(u.uri, 'uri', 'URI')"
|
||||||
|
tabindex="-1">
|
||||||
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 form-group">
|
||||||
<label for="loginUriMatch{{i}}">
|
<label for="loginUriMatch{{i}}">
|
||||||
{{'matchDetection' | i18n}}
|
{{'matchDetection' | i18n}}
|
||||||
</label>
|
</label>
|
||||||
@@ -72,18 +105,19 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<button class="btn btn-secondary" (click)="removeUri(u)" title="{{'remove' | i18n}}">
|
<label class="invisible"> </label>
|
||||||
|
<button class="btn btn-link text-danger" (click)="removeUri(u)" title="{{'remove' | i18n}}">
|
||||||
<i class="fa fa-minus-circle fa-lg"></i>
|
<i class="fa fa-minus-circle fa-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button type="button" appBlurClick (click)="addUri()" class="btn btn-link">
|
<a href="#" appStopClick appBlurClick (click)="addUri()" class="d-inline-block mb-3">
|
||||||
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{'newUri' | i18n}}
|
<i class="fa fa-plus-circle fa-fw"></i> {{'newUri' | i18n}}
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</ng-container>
|
||||||
<!-- Card -->
|
<!-- Card -->
|
||||||
<div *ngIf="cipher.type === cipherType.Card">
|
<ng-container *ngIf="cipher.type === cipherType.Card">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 form-group">
|
<div class="col-6 form-group">
|
||||||
<label for="cardCardholderName">{{'cardholderName' | i18n}}</label>
|
<label for="cardCardholderName">{{'cardholderName' | i18n}}</label>
|
||||||
@@ -99,7 +133,15 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 form-group">
|
<div class="col-6 form-group">
|
||||||
<label for="cardNumber">{{'number' | i18n}}</label>
|
<label for="cardNumber">{{'number' | i18n}}</label>
|
||||||
|
<div class="input-group">
|
||||||
<input id="cardNumber" class="form-control" type="text" name="Card.Number" [(ngModel)]="cipher.card.number">
|
<input id="cardNumber" class="form-control" type="text" name="Card.Number" [(ngModel)]="cipher.card.number">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'copyNumber' | i18n}}" (click)="copy(cipher.card.number, 'number', 'Number')"
|
||||||
|
tabindex="-1">
|
||||||
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col form-group">
|
<div class="col form-group">
|
||||||
<label for="cardExpMonth">{{'expirationMonth' | i18n}}</label>
|
<label for="cardExpMonth">{{'expirationMonth' | i18n}}</label>
|
||||||
@@ -115,13 +157,20 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 form-group">
|
<div class="col-6 form-group">
|
||||||
<label for="cardCode">{{'securityCode' | i18n}}</label>
|
<label for="cardCode">{{'securityCode' | i18n}}</label>
|
||||||
|
<div class="input-group">
|
||||||
<input id="cardCode" class="form-control" type="text" name="Card.Code" [(ngModel)]="cipher.card.code">
|
<input id="cardCode" class="form-control" type="text" name="Card.Code" [(ngModel)]="cipher.card.code">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'securityCode' | i18n}}" (click)="copy(cipher.card.code, 'securityCode', 'Security Code')"
|
||||||
|
tabindex="-1">
|
||||||
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
<!-- Identity -->
|
<!-- Identity -->
|
||||||
<div *ngIf="cipher.type === cipherType.Identity">
|
<ng-container *ngIf="cipher.type === cipherType.Identity">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-4 form-group">
|
<div class="col-4 form-group">
|
||||||
<label for="idTitle">{{'title' | i18n}}</label>
|
<label for="idTitle">{{'title' | i18n}}</label>
|
||||||
@@ -214,66 +263,79 @@
|
|||||||
<input id="idCountry" class="form-control" type="text" name="Identity.Country" [(ngModel)]="cipher.identity.country">
|
<input id="idCountry" class="form-control" type="text" name="Identity.Country" [(ngModel)]="cipher.identity.country">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ng-container>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="notes">{{'notes' | i18n}}</label>
|
<label for="notes">{{'notes' | i18n}}</label>
|
||||||
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes" class="form-control"></textarea>
|
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes" class="form-control"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<h3 class="mt-4">{{'customFields' | i18n}}</h3>
|
||||||
<div class="form-group">
|
|
||||||
<label for="favorite">{{'favorite' | i18n}}</label>
|
|
||||||
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite">
|
|
||||||
</div>
|
|
||||||
<h4>{{'customFields' | i18n}}</h4>
|
|
||||||
<ng-container *ngIf="cipher.hasFields">
|
<ng-container *ngIf="cipher.hasFields">
|
||||||
<div class="row" appBoxRow *ngFor="let f of cipher.fields; let i = index">
|
<div class="row" appBoxRow *ngFor="let f of cipher.fields; let i = index">
|
||||||
<div class="col-4">
|
<div class="col-4 form-group">
|
||||||
<label for="fieldName{{i}}">{{'name' | i18n}}</label>
|
<label for="fieldName{{i}}">{{'name' | i18n}}</label>
|
||||||
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name" class="form-control">
|
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-6 form-group">
|
||||||
<label for="fieldValue{{i}}">{{'value' | i18n}}</label>
|
<label for="fieldValue{{i}}">{{'value' | i18n}}</label>
|
||||||
<input id="fieldValue{{i}}" class="form-control" type="text" name="Field.Value{{i}}" [(ngModel)]="f.value" *ngIf="f.type === fieldType.Text">
|
<div class="input-group" *ngIf="f.type === fieldType.Text">
|
||||||
|
<input id="fieldValue{{i}}" class="form-control" type="text" name="Field.Value{{i}}" [(ngModel)]="f.value">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'copyValue' | i18n}}" (click)="copy(f.value, 'value', 'Field')"
|
||||||
|
tabindex="-1">
|
||||||
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="input-group" *ngIf="f.type === fieldType.Hidden">
|
<div class="input-group" *ngIf="f.type === fieldType.Hidden">
|
||||||
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}" name="Field.Value{{i}}" [(ngModel)]="f.value" class="form-control text-monospace">
|
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}" name="Field.Value{{i}}" [(ngModel)]="f.value" class="form-control text-monospace">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)">
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)"
|
||||||
|
tabindex="-1">
|
||||||
<i class="fa fa-lg" [ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}"></i>
|
<i class="fa fa-lg" [ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" appBlurClick title="{{'copyValue' | i18n}}" (click)="copy(f.value, 'value', 'Field')"
|
||||||
|
tabindex="-1">
|
||||||
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox" [(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean"
|
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox" [(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean"
|
||||||
appTrueFalseValue trueValue="true" falseValue="false">
|
appTrueFalseValue trueValue="true" falseValue="false">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<button type="button" class="btn btn-secondary" (click)="removeField(f)" title="{{'remove' | i18n}}">
|
<label class="invisible"> </label>
|
||||||
|
<button class="btn btn-link text-danger" (click)="removeField(f)" title="{{'remove' | i18n}}">
|
||||||
<i class="fa fa-minus-circle fa-lg"></i>
|
<i class="fa fa-minus-circle fa-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button type="button" class="btn btn-link" appBlurClick (click)="addField()">
|
<a href="#" appStopClick appBlurClick (click)="addField()" class="d-inline-block mb-2">
|
||||||
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{'newCustomField' | i18n}}
|
<i class="fa fa-plus-circle fa-fw"></i> {{'newCustomField' | i18n}}
|
||||||
</button>
|
</a>
|
||||||
<div>
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
|
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
|
||||||
<select id="addFieldType" class="form-control" name="AddFieldType" [(ngModel)]="addFieldType">
|
<select id="addFieldType" class="form-control" name="AddFieldType" [(ngModel)]="addFieldType">
|
||||||
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
|
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}" [disabled]="form.loading">
|
<button appBlurClick type="submit" class="btn btn-primary" title="{{'save' | i18n}}" [disabled]="form.loading">
|
||||||
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
||||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
||||||
</button>
|
</button>
|
||||||
<button appBlurClick type="button" data-dismiss="modal" title="{{'cancel' | i18n}}">
|
<button appBlurClick type="button" class="btn btn-outline-secondary" data-dismiss="modal" title="{{'cancel' | i18n}}">
|
||||||
{{'cancel' | i18n}}
|
{{'cancel' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
<div class="right">
|
<div class="ml-auto" *ngIf="cipher">
|
||||||
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger" title="{{'delete' | i18n}}" *ngIf="editMode"
|
<button appBlurClick type="button" (click)="toggleFavorite()" class="btn btn-link" title="{{(cipher.favorite ? 'unfavorite' : 'favorite') | i18n}}">
|
||||||
[disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
|
<i class="fa fa-lg" [ngClass]="{'fa-star': cipher.favorite, 'fa-star-o': !cipher.favorite}"></i>
|
||||||
|
</button>
|
||||||
|
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="btn btn-outline-danger" title="{{'delete' | i18n}}"
|
||||||
|
*ngIf="editMode" [disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
|
||||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
|||||||
import { StateService } from 'jslib/abstractions/state.service';
|
import { StateService } from 'jslib/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';
|
||||||
|
import { LoginUriView } from 'jslib/models/view/loginUriView';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault-add-edit',
|
selector: 'app-vault-add-edit',
|
||||||
@@ -31,4 +32,28 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit {
|
|||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await super.load();
|
await super.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleFavorite() {
|
||||||
|
this.cipher.favorite = !this.cipher.favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
launch(uri: LoginUriView) {
|
||||||
|
if (!uri.canLaunch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.analytics.eventTrack.next({ action: 'Launched Login URI' });
|
||||||
|
this.platformUtilsService.launchUri(uri.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(value: string, typeI18nKey: string, aType: string) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.analytics.eventTrack.next({ action: 'Copied ' + aType });
|
||||||
|
this.platformUtilsService.copyToClipboard(value, { doc: window.document });
|
||||||
|
this.toasterService.popAsync('info', null,
|
||||||
|
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
|
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
|
||||||
<table class="table table-hover table-sm" *ngIf="searchedCiphers.length > 0">
|
<table class="table table-hover" *ngIf="searchedCiphers.length > 0">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let c of searchedCiphers">
|
<tr *ngFor="let c of searchedCiphers">
|
||||||
<td>
|
<td (click)="checkCipher(c)">
|
||||||
<input type="checkbox">
|
<input type="checkbox" [(ngModel)]="c.checked">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td (click)="checkCipher(c)">
|
||||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td (click)="checkCipher(c)">
|
||||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
|
<a href="#" appStopClick appStopProp (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
|
||||||
<i class="fa fa-share-alt text-muted" *ngIf="c.organizationId" title="{{'shared' | i18n}}"></i>
|
<i class="fa fa-share-alt text-muted" appStopProp *ngIf="c.organizationId" title="{{'shared' | i18n}}"></i>
|
||||||
<i class="fa fa-paperclip text-muted" *ngIf="c.hasAttachments" title="{{'attachments' | i18n}}"></i>
|
<i class="fa fa-paperclip text-muted" appStopProp *ngIf="c.hasAttachments" title="{{'attachments' | i18n}}"></i>
|
||||||
<small>{{c.subTitle}}</small>
|
<br>
|
||||||
|
<small appStopProp>{{c.subTitle}}</small>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="dropdown" appListDropdown>
|
<div class="dropdown" appListDropdown>
|
||||||
@@ -21,9 +22,26 @@
|
|||||||
<i class="fa fa-cog"></i>
|
<i class="fa fa-cog"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||||
<a class="dropdown-item" href="#">Action</a>
|
<a class="dropdown-item" href="#" appStopClick *ngIf="c.type === cipherType.Login">
|
||||||
<a class="dropdown-item" href="#">Another action</a>
|
<i class="fa fa-fw fa-clipboard"></i>
|
||||||
<a class="dropdown-item" href="#">Something else here</a>
|
{{'copyPassword' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" appStopClick>
|
||||||
|
<i class="fa fa-fw fa-paperclip"></i>
|
||||||
|
{{'attachments' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" appStopClick *ngIf="!c.organizationId">
|
||||||
|
<i class="fa fa-fw fa-share-alt"></i>
|
||||||
|
{{'share' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" appStopClick *ngIf="c.organizationId">
|
||||||
|
<i class="fa fa-fw fa-cubes"></i>
|
||||||
|
{{'collections' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-danger" href="#" appStopClick>
|
||||||
|
<i class="fa fa-fw fa-trash-o"></i>
|
||||||
|
{{'delete' | i18n}}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -4,12 +4,22 @@ import { CipherService } from 'jslib/abstractions/cipher.service';
|
|||||||
|
|
||||||
import { CiphersComponent as BaseCiphersComponent } from 'jslib/angular/components/ciphers.component';
|
import { CiphersComponent as BaseCiphersComponent } from 'jslib/angular/components/ciphers.component';
|
||||||
|
|
||||||
|
import { CipherType } from 'jslib/enums/cipherType';
|
||||||
|
|
||||||
|
import { CipherView } from 'jslib/models/view/cipherView';
|
||||||
|
|
||||||
@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 {
|
||||||
|
cipherType = CipherType;
|
||||||
|
|
||||||
constructor(cipherService: CipherService) {
|
constructor(cipherService: CipherService) {
|
||||||
super(cipherService);
|
super(cipherService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkCipher(c: CipherView) {
|
||||||
|
(c as any).checked = !(c as any).checked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,24 @@
|
|||||||
<div class="modal-dialog modal-sm">
|
<div class="modal-dialog modal-sm">
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
{{title}}
|
<h2 class="modal-title">{{title}}</h2>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<label for="name">{{'name' | i18n}}</label>
|
<label for="name">{{'name' | i18n}}</label>
|
||||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="folder.name">
|
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="folder.name">
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}" [disabled]="form.loading">
|
<button appBlurClick type="submit" class="btn btn-primary" title="{{'save' | i18n}}" [disabled]="form.loading">
|
||||||
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
||||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-dismiss="modal" title="{{'cancel' | i18n}}">{{'cancel' | i18n}}</button>
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal" title="{{'cancel' | i18n}}">{{'cancel' | i18n}}</button>
|
||||||
<div class="right">
|
<div class="ml-auto">
|
||||||
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger" title="{{'delete' | i18n}}" *ngIf="editMode"
|
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="btn btn-outline-danger" title="{{'delete' | i18n}}"
|
||||||
[disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
|
*ngIf="editMode" [disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
|
||||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="card">
|
<div class="card vault-filters">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Filters
|
Filters
|
||||||
</div>
|
</div>
|
||||||
@@ -40,31 +40,31 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p *ngIf="!loaded" class="text-muted"><i class="fa fa-spinner fa-spin"></i></p>
|
<p *ngIf="!loaded" class="text-muted">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
</p>
|
||||||
<ng-container *ngIf="loaded">
|
<ng-container *ngIf="loaded">
|
||||||
<h3>
|
<h3 class="d-flex">
|
||||||
{{'folders' | i18n}}
|
{{'folders' | i18n}}
|
||||||
<button appBlurClick (click)="addFolder()" title="{{'addFolder' | i18n}}">
|
<a href="#" class="text-muted ml-auto" appStopClick appBlurClick (click)="addFolder()" title="{{'addFolder' | i18n}}">
|
||||||
<i class="fa fa-plus fa-fw"></i>
|
<i class="fa fa-plus fa-fw"></i>
|
||||||
</button>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="fa-ul">
|
<ul class="fa-ul carets">
|
||||||
<li *ngFor="let f of folders" [ngClass]="{active: selectedFolder && f.id === selectedFolderId}">
|
<li *ngFor="let f of folders" class="d-flex" [ngClass]="{active: selectedFolder && f.id === selectedFolderId}">
|
||||||
<a href="#" appStopClick appBlurClick (click)="selectFolder(f)">
|
<a href="#" appStopClick appBlurClick (click)="selectFolder(f)">
|
||||||
<i class="fa-li fa fa-caret-right"></i> {{f.name}}
|
<i class="fa-li fa fa-caret-right"></i> {{f.name}}</a>
|
||||||
<span appStopProp appStopClick (click)="editFolder(f)" title="{{'editFolder' | i18n}}" *ngIf="f.id">
|
<a href="#" class="text-muted ml-auto show-active" appStopClick appBlurClick (click)="editFolder(f)" title="{{'editFolder' | i18n}}" *ngIf="f.id">
|
||||||
<i class="fa fa-pencil fa-fw"></i>
|
<i class="fa fa-pencil fa-fw"></i>
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div *ngIf="collections && collections.length">
|
<div *ngIf="collections && collections.length">
|
||||||
<h3>{{'collections' | i18n}}</h3>
|
<h3>{{'collections' | i18n}}</h3>
|
||||||
<ul class="fa-ul">
|
<ul class="fa-ul carets">
|
||||||
<li *ngFor="let c of collections" [ngClass]="{active: c.id === selectedCollectionId}">
|
<li *ngFor="let c of collections" [ngClass]="{active: c.id === selectedCollectionId}">
|
||||||
<a href="#" appStopClick appBlurClick (click)="selectCollection(c)">
|
<a href="#" appStopClick appBlurClick (click)="selectCollection(c)">
|
||||||
<i class="fa-li fa fa-caret-right"></i> {{c.name}}
|
<i class="fa-li fa fa-caret-right"></i> {{c.name}}</a>
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,10 +36,43 @@
|
|||||||
</app-vault-groupings>
|
</app-vault-groupings>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<button type="button" class="btn btn-secondary btn-sm pull-right" (click)="addCipher()">
|
<div class="page-header d-flex">
|
||||||
<i class="fa fa-plus"></i>
|
<h1>{{'myVault' | i18n}}</h1>
|
||||||
|
<div class="ml-auto d-flex">
|
||||||
|
<div class="dropdown mr-2" appListDropdown>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown"
|
||||||
|
aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="fa fa-cog"></i>
|
||||||
</button>
|
</button>
|
||||||
<h1>My Vault</h1>
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
<i class="fa fa-fw fa-share"></i>
|
||||||
|
{{'moveSelected' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
<i class="fa fa-fw fa-share-alt"></i>
|
||||||
|
{{'shareSelected' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-danger" href="#">
|
||||||
|
<i class="fa fa-fw fa-trash-o"></i>
|
||||||
|
{{'deleteSelected' | i18n}}
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
<i class="fa fa-fw fa-check-square-o"></i>
|
||||||
|
{{'selectAll' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
<i class="fa fa-fw fa-minus-square-o"></i>
|
||||||
|
{{'unselectAll' | i18n}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" (click)="addCipher()">
|
||||||
|
<i class="fa fa-plus fa-fw"></i>{{'addItem' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<app-vault-ciphers (onCipherClicked)="editCipher($event)">
|
<app-vault-ciphers (onCipherClicked)="editCipher($event)">
|
||||||
</app-vault-ciphers>
|
</app-vault-ciphers>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,6 +82,14 @@
|
|||||||
Some callout
|
Some callout
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
Organizations
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
Your organizations.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
$primary: #3c8dbc;
|
||||||
|
$primary-accent: #286090;
|
||||||
|
$secondary: #ced4da;
|
||||||
|
$success: #00a65a;
|
||||||
|
$info: #555555;
|
||||||
|
$warning: #bf7e16;
|
||||||
|
$danger: #dd4b39;
|
||||||
|
|
||||||
$theme-colors: (
|
$theme-colors: (
|
||||||
"primary": #3c8dbc,
|
"primary-accent": $primary-accent
|
||||||
"primary-accent": #286090,
|
|
||||||
"danger": #dd4b39,
|
|
||||||
"success": #00a65a,
|
|
||||||
"info": #555555,
|
|
||||||
"warning": #bf7e16
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$body-bg: #ffffff;
|
$body-bg: #ffffff;
|
||||||
@@ -13,7 +16,7 @@ $body-color: #333333;
|
|||||||
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,
|
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,
|
||||||
Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';
|
Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';
|
||||||
|
|
||||||
$h1-font-size: 2rem;
|
$h1-font-size: 1.7rem;
|
||||||
$h2-font-size: 1.3rem;
|
$h2-font-size: 1.3rem;
|
||||||
$h3-font-size: 1rem;
|
$h3-font-size: 1rem;
|
||||||
$h4-font-size: 1rem;
|
$h4-font-size: 1rem;
|
||||||
@@ -29,8 +32,11 @@ $list-group-active-color: $body-color;
|
|||||||
$list-group-active-bg: #ffffff;
|
$list-group-active-bg: #ffffff;
|
||||||
$list-group-active-border-color: rgba(#000000, .125);
|
$list-group-active-border-color: rgba(#000000, .125);
|
||||||
|
|
||||||
$dropdown-link-hover-color: #ffffff;
|
$dropdown-link-color: $body-color;
|
||||||
$dropdown-link-hover-bg: #3c8dbc;
|
$dropdown-link-hover-bg: rgba(#000000, .06);
|
||||||
|
$dropdown-link-active-color: $dropdown-link-color;
|
||||||
|
$dropdown-link-active-bg: rgba(#000000, .1);
|
||||||
|
$dropdown-item-padding-x: 1rem;
|
||||||
|
|
||||||
$navbar-brand-font-size: 35px;
|
$navbar-brand-font-size: 35px;
|
||||||
$navbar-brand-height: 35px;
|
$navbar-brand-height: 35px;
|
||||||
@@ -38,6 +44,12 @@ $navbar-brand-padding-y: 0;
|
|||||||
$navbar-dark-color: rgba(#ffffff, .7);
|
$navbar-dark-color: rgba(#ffffff, .7);
|
||||||
$navbar-dark-hover-color: rgba(#ffffff, .9);
|
$navbar-dark-hover-color: rgba(#ffffff, .9);
|
||||||
|
|
||||||
|
$input-bg: #fafafa;
|
||||||
|
$input-focus-bg: #ffffff;
|
||||||
|
$input-disabled-bg: #e0e0e0;
|
||||||
|
|
||||||
|
$table-hover-bg: rgba(#000000, .03);
|
||||||
|
|
||||||
@import "../../node_modules/bootstrap/scss/bootstrap";
|
@import "../../node_modules/bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@@ -48,6 +60,16 @@ body {
|
|||||||
min-width: 1010px;
|
min-width: 1010px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
padding-bottom: 0.6rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
h1, h2, h3, h4 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
@@ -81,10 +103,51 @@ body {
|
|||||||
padding-left: calc(#{$list-group-item-padding-x} - 3px);
|
padding-left: calc(#{$list-group-item-padding-x} - 3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-header, .modal-header {
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-weight: normal;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
justify-content: initial;
|
||||||
|
background-color: $input-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
form label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn[class*="btn-outline-"]:not(:hover) {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-secondary {
|
||||||
|
color: $text-muted;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $body-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app-vault-icon .fa {
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
app-vault {
|
app-vault {
|
||||||
.table {
|
.table {
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
tr:first-child {
|
tr:first-child {
|
||||||
td {
|
td {
|
||||||
border: none;
|
border: none;
|
||||||
@@ -101,17 +164,16 @@ app-vault {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
small {
|
small {
|
||||||
display: block;
|
|
||||||
color: $text-muted;
|
color: $text-muted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td:first-child {
|
td:first-child {
|
||||||
width: 25px;
|
width: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:nth-child(2) {
|
td:nth-child(2) {
|
||||||
width: 25px;
|
width: 45px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -120,13 +182,82 @@ app-vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td:nth-child(3) {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
td:last-child {
|
td:last-child {
|
||||||
width: 65px;
|
width: 72px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown:not(.show) button {
|
.dropdown:not(.show) button {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app-vault-groupings {
|
||||||
|
.card {
|
||||||
|
#search {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $body-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&.text-muted {
|
||||||
|
color: $body-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.fa-ul {
|
||||||
|
margin-left: 1.9em;
|
||||||
|
|
||||||
|
.fa-li {
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.carets {
|
||||||
|
margin-left: 1.1em;
|
||||||
|
|
||||||
|
.fa-li {
|
||||||
|
left: -24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-active {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active {
|
||||||
|
.show-active {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active {
|
||||||
|
a:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
color: theme-color("primary");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,72 +4,49 @@ import { MessagingService } from 'jslib/abstractions/messaging.service';
|
|||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
|
||||||
import { AnalyticsIds } from 'jslib/misc/analytics';
|
import { AnalyticsIds } from 'jslib/misc/analytics';
|
||||||
|
import { Utils } from 'jslib/misc/utils';
|
||||||
const DialogPromiseExpiration = 600000; // 10 minutes
|
|
||||||
|
|
||||||
export class WebPlatformUtilsService implements PlatformUtilsService {
|
export class WebPlatformUtilsService implements PlatformUtilsService {
|
||||||
identityClientId: string = 'web';
|
identityClientId: string = 'web';
|
||||||
|
|
||||||
private showDialogResolves = new Map<number, { resolve: (value: boolean) => void, date: Date }>();
|
private browserCache: string = null;
|
||||||
private deviceCache: DeviceType = null;
|
|
||||||
private analyticsIdCache: string = null;
|
|
||||||
|
|
||||||
constructor(private messagingService: MessagingService) { }
|
constructor(private messagingService: MessagingService) { }
|
||||||
|
|
||||||
getDevice(): DeviceType {
|
getDevice(): DeviceType {
|
||||||
if (this.deviceCache) {
|
return DeviceType.Web;
|
||||||
return this.deviceCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (navigator.userAgent.indexOf(' Firefox/') !== -1 || navigator.userAgent.indexOf(' Gecko/') !== -1) {
|
|
||||||
this.deviceCache = DeviceType.Firefox;
|
|
||||||
} else if (navigator.userAgent.indexOf(' OPR/') >= 0) {
|
|
||||||
this.deviceCache = DeviceType.Opera;
|
|
||||||
} else if (navigator.userAgent.indexOf(' Edge/') !== -1) {
|
|
||||||
this.deviceCache = DeviceType.Edge;
|
|
||||||
} else if (navigator.userAgent.indexOf(' Vivaldi/') !== -1) {
|
|
||||||
this.deviceCache = DeviceType.Vivaldi;
|
|
||||||
} else if (navigator.userAgent.indexOf(' Safari/') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) {
|
|
||||||
this.deviceCache = DeviceType.Safari;
|
|
||||||
} else if ((window as any).chrome && navigator.userAgent.indexOf(' Chrome/') !== -1) {
|
|
||||||
this.deviceCache = DeviceType.Chrome;
|
|
||||||
} else if (navigator.userAgent.indexOf(' Trident/') !== -1) {
|
|
||||||
this.deviceCache = DeviceType.IE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.deviceCache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceString(): string {
|
getDeviceString(): string {
|
||||||
return DeviceType[this.getDevice()].toLowerCase();
|
return 'Web';
|
||||||
}
|
}
|
||||||
|
|
||||||
isFirefox(): boolean {
|
isFirefox(): boolean {
|
||||||
return this.getDevice() === DeviceType.Firefox;
|
return this.getBrowserType() === 'firefox';
|
||||||
}
|
}
|
||||||
|
|
||||||
isChrome(): boolean {
|
isChrome(): boolean {
|
||||||
return this.getDevice() === DeviceType.Chrome;
|
return this.getBrowserType() === 'chrome';
|
||||||
}
|
}
|
||||||
|
|
||||||
isEdge(): boolean {
|
isEdge(): boolean {
|
||||||
return this.getDevice() === DeviceType.Edge;
|
return this.getBrowserType() === 'edge';
|
||||||
}
|
|
||||||
|
|
||||||
isIE(): boolean {
|
|
||||||
return this.getDevice() === DeviceType.IE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isOpera(): boolean {
|
isOpera(): boolean {
|
||||||
return this.getDevice() === DeviceType.Opera;
|
return this.getBrowserType() === 'opera';
|
||||||
}
|
}
|
||||||
|
|
||||||
isVivaldi(): boolean {
|
isVivaldi(): boolean {
|
||||||
return this.getDevice() === DeviceType.Vivaldi;
|
return this.getBrowserType() === 'vivaldi';
|
||||||
}
|
}
|
||||||
|
|
||||||
isSafari(): boolean {
|
isSafari(): boolean {
|
||||||
return this.getDevice() === DeviceType.Safari;
|
return this.getBrowserType() === 'safari';
|
||||||
|
}
|
||||||
|
|
||||||
|
isIE(): boolean {
|
||||||
|
return this.getBrowserType() === 'ie';
|
||||||
}
|
}
|
||||||
|
|
||||||
isMacAppStore(): boolean {
|
isMacAppStore(): boolean {
|
||||||
@@ -77,16 +54,11 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
analyticsId(): string {
|
analyticsId(): string {
|
||||||
if (this.analyticsIdCache) {
|
return 'UA-81915606-3';
|
||||||
return this.analyticsIdCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.analyticsIdCache = (AnalyticsIds as any)[this.getDevice()];
|
|
||||||
return this.analyticsIdCache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDomain(uriString: string): string {
|
getDomain(uriString: string): string {
|
||||||
return uriString;
|
return Utils.getHostname(uriString);
|
||||||
}
|
}
|
||||||
|
|
||||||
isViewOpen(): boolean {
|
isViewOpen(): boolean {
|
||||||
@@ -94,7 +66,11 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
launchUri(uri: string, options?: any): void {
|
launchUri(uri: string, options?: any): void {
|
||||||
//
|
const a = document.createElement('a');
|
||||||
|
a.href = uri;
|
||||||
|
a.target = '_blank';
|
||||||
|
a.rel = 'noreferrer noopener';
|
||||||
|
a.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
|
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
|
||||||
@@ -122,18 +98,7 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showDialog(text: string, title?: string, confirmText?: string, cancelText?: string, type?: string) {
|
showDialog(text: string, title?: string, confirmText?: string, cancelText?: string, type?: string) {
|
||||||
const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
return Promise.resolve(false);
|
||||||
this.messagingService.send('showDialog', {
|
|
||||||
text: text,
|
|
||||||
title: title,
|
|
||||||
confirmText: confirmText,
|
|
||||||
cancelText: cancelText,
|
|
||||||
type: type,
|
|
||||||
dialogId: dialogId,
|
|
||||||
});
|
|
||||||
return new Promise<boolean>((resolve) => {
|
|
||||||
this.showDialogResolves.set(dialogId, { resolve: resolve, date: new Date() });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isDev(): boolean {
|
isDev(): boolean {
|
||||||
@@ -165,23 +130,27 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveDialogPromise(dialogId: number, confirmed: boolean) {
|
private getBrowserType(): string {
|
||||||
if (this.showDialogResolves.has(dialogId)) {
|
if (this.browserCache != null) {
|
||||||
const resolveObj = this.showDialogResolves.get(dialogId);
|
return this.browserCache;
|
||||||
resolveObj.resolve(confirmed);
|
|
||||||
this.showDialogResolves.delete(dialogId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up old promises
|
if (navigator.userAgent.indexOf(' Firefox/') !== -1 || navigator.userAgent.indexOf(' Gecko/') !== -1) {
|
||||||
const deleteIds: number[] = [];
|
this.browserCache = 'firefox';
|
||||||
this.showDialogResolves.forEach((val, key) => {
|
} else if (navigator.userAgent.indexOf(' OPR/') >= 0) {
|
||||||
const age = new Date().getTime() - val.date.getTime();
|
this.browserCache = 'opera';
|
||||||
if (age > DialogPromiseExpiration) {
|
} else if (navigator.userAgent.indexOf(' Edge/') !== -1) {
|
||||||
deleteIds.push(key);
|
this.browserCache = 'edge';
|
||||||
|
} else if (navigator.userAgent.indexOf(' Vivaldi/') !== -1) {
|
||||||
|
this.browserCache = 'vivaldi';
|
||||||
|
} else if (navigator.userAgent.indexOf(' Safari/') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) {
|
||||||
|
this.browserCache = 'safari';
|
||||||
|
} else if ((window as any).chrome && navigator.userAgent.indexOf(' Chrome/') !== -1) {
|
||||||
|
this.browserCache = 'chrome';
|
||||||
|
} else if (navigator.userAgent.indexOf(' Trident/') !== -1) {
|
||||||
|
this.browserCache = 'ie';
|
||||||
}
|
}
|
||||||
});
|
|
||||||
deleteIds.forEach((id) => {
|
return this.browserCache;
|
||||||
this.showDialogResolves.delete(id);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user