mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
* Move share from edit to view. Fix animations Editing and Sharing a cipher simultaneously results in lost edits. Move share button to the view page to resolve this confusion. Previous routing caused the share form to be animated again on submition, resulting in a stuttering page load. This method correctly animates all transitions with the concession that the share page always takes you back to the view page. This is not necessarily the current behavior, but it is the most likely behavior in the current scheme * Update jslib reference
344 lines
19 KiB
HTML
344 lines
19 KiB
HTML
<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" *ngIf="cipher">
|
|
<button type="button" appBlurClick (click)="edit()" *ngIf="!cipher.isDeleted">{{'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>
|
|
<input type="text" [value]="cipher.name" readonly aria-readonly="true" />
|
|
</div>
|
|
<!-- Login -->
|
|
<div *ngIf="cipher.login">
|
|
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.username">
|
|
<div class="row-main">
|
|
<span class="row-label draggable" draggable="true"
|
|
(dragstart)="setTextDataOnDrag($event, cipher.login.username)">{{'username' | i18n}}
|
|
</span>
|
|
<input type="text" [value]="cipher.login.username" readonly aria-readonly="true" />
|
|
</div>
|
|
<div class="action-buttons">
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyUsername' | i18n}}"
|
|
(click)="copy(cipher.login.username, 'username', 'Username')">
|
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="box-content-row box-content-row-flex" *ngIf="cipher.login.password">
|
|
<div class="row-main">
|
|
<span class="row-label draggable" draggable="true"
|
|
(dragstart)="setTextDataOnDrag($event, cipher.login.password)">{{'password' | i18n}}</span>
|
|
<div [hidden]="showPassword" class="monospaced">
|
|
{{cipher.login.maskedPassword}}</div>
|
|
<div [hidden]="!showPassword" class="monospaced password-wrapper" appSelectCopy
|
|
[innerHTML]="cipher.login.password | colorPassword"></div>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<button type="button" #checkPasswordBtn class="row-btn btn" appBlurClick
|
|
appA11yTitle="{{'checkPassword' | i18n}}" (click)="checkPassword()"
|
|
[appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading"
|
|
*ngIf="cipher.viewPassword">
|
|
<i class="fa fa-lg fa-check-circle" [hidden]="checkPasswordBtn.loading"
|
|
aria-hidden="true"></i>
|
|
<i class="fa fa-lg fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"
|
|
aria-hidden="true"></i>
|
|
</button>
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
|
|
(click)="togglePassword()" *ngIf="cipher.viewPassword">
|
|
<i class="fa fa-lg" aria-hidden="true"
|
|
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
|
</a>
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
|
|
(click)="copy(cipher.login.password, 'password', 'Password')" *ngIf="cipher.viewPassword">
|
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="box-content-row box-content-row-flex totp" [ngClass]="{'low': totpLow}"
|
|
*ngIf="cipher.login.totp && totpCode">
|
|
<div class="row-main">
|
|
<span class="row-label draggable" draggable="true"
|
|
(dragstart)="setTextDataOnDrag($event, totpCode)">{{'verificationCodeTotp' | i18n}}</span>
|
|
<span class="totp-code">{{totpCodeFormatted}}</span>
|
|
</div>
|
|
<span class="totp-countdown">
|
|
<span class="totp-sec">{{totpSec}}</span>
|
|
<svg>
|
|
<g>
|
|
<circle class="totp-circle inner" r="12.6" cy="16" cx="16"
|
|
[ngStyle]="{'stroke-dashoffset.px': totpDash}"></circle>
|
|
<circle class="totp-circle outer" r="14" cy="16" cx="16"></circle>
|
|
</g>
|
|
</svg>
|
|
</span>
|
|
<div class="action-buttons">
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyVerificationCode' | i18n}}"
|
|
(click)="copy(totpCode, 'verificationCodeTotp', 'TOTP')">
|
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Card -->
|
|
<div *ngIf="cipher.card">
|
|
<div class="box-content-row" *ngIf="cipher.card.cardholderName">
|
|
<span class="row-label">{{'cardholderName' | i18n}}</span>
|
|
{{cipher.card.cardholderName}}
|
|
</div>
|
|
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number">
|
|
<div class="row-main">
|
|
<span class="row-label">{{'number' | i18n}}</span>
|
|
{{cipher.card.number}}
|
|
</div>
|
|
<div class="action-buttons">
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyNumber' | i18n}}"
|
|
(click)="copy(cipher.card.number, 'number', 'Number')">
|
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.card.brand">
|
|
<span class="row-label">{{'brand' | i18n}}</span>
|
|
{{cipher.card.brand}}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.card.expiration">
|
|
<span class="row-label">{{'expiration' | i18n}}</span>
|
|
{{cipher.card.expiration}}
|
|
</div>
|
|
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.code">
|
|
<div class="row-main">
|
|
<span class="row-label">{{'securityCode' | i18n}}</span>
|
|
<span [hidden]="showCardCode" class="monospaced">{{cipher.card.maskedCode}}</span>
|
|
<span [hidden]="!showCardCode" class="monospaced">{{cipher.card.code}}</span>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
|
|
(click)="toggleCardCode()">
|
|
<i class="fa fa-lg" aria-hidden="true"
|
|
[ngClass]="{'fa-eye': !showCardCode, 'fa-eye-slash': showCardCode}"></i>
|
|
</a>
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copySecurityCode' | i18n}}"
|
|
(click)="copy(cipher.card.code, 'securityCode', 'Security Code')">
|
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Identity -->
|
|
<div *ngIf="cipher.identity">
|
|
<div class="box-content-row" *ngIf="cipher.identity.fullName">
|
|
<span class="row-label">{{'identityName' | i18n}}</span>
|
|
{{cipher.identity.fullName}}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.username">
|
|
<span class="row-label">{{'username' | i18n}}</span>
|
|
{{cipher.identity.username}}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.company">
|
|
<span class="row-label">{{'company' | i18n}}</span>
|
|
{{cipher.identity.company}}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.ssn">
|
|
<span class="row-label">{{'ssn' | i18n}}</span>
|
|
{{cipher.identity.ssn}}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.passportNumber">
|
|
<span class="row-label">{{'passportNumber' | i18n}}</span>
|
|
{{cipher.identity.passportNumber}}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.licenseNumber">
|
|
<span class="row-label">{{'licenseNumber' | i18n}}</span>
|
|
{{cipher.identity.licenseNumber}}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.email">
|
|
<span class="row-label">{{'email' | i18n}}</span>
|
|
{{cipher.identity.email}}
|
|
</div>
|
|
<div class="box-content-row" *ngIf="cipher.identity.phone">
|
|
<span class="row-label">{{'phone' | i18n}}</span>
|
|
{{cipher.identity.phone}}
|
|
</div>
|
|
<div class="box-content-row"
|
|
*ngIf="cipher.identity.address1 || cipher.identity.city || cipher.identity.country">
|
|
<span class="row-label">{{'address' | i18n}}</span>
|
|
<div *ngIf="cipher.identity.address1">{{cipher.identity.address1}}</div>
|
|
<div *ngIf="cipher.identity.address2">{{cipher.identity.address2}}</div>
|
|
<div *ngIf="cipher.identity.address3">{{cipher.identity.address3}}</div>
|
|
<div *ngIf="cipher.identity.fullAddressPart2">{{cipher.identity.fullAddressPart2}}</div>
|
|
<div *ngIf="cipher.identity.country">{{cipher.identity.country}}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<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}}">
|
|
<input type="text" [value]="u.hostOrUri" readonly aria-readonly="true" />
|
|
</span>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'launch' | i18n}}" *ngIf="u.canLaunch"
|
|
(click)="launch(u)">
|
|
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
|
|
</a>
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyUri' | i18n}}"
|
|
(click)="copy(u.uri, u.isWebsite ? 'website' : 'uri', 'URI')">
|
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="box" *ngIf="cipher.notes">
|
|
<div class="box-header">
|
|
{{'notes' | i18n}}
|
|
</div>
|
|
<div class="box-content">
|
|
<div class="box-content-row">
|
|
<textarea [value]="cipher.notes" rows="6" readonly aria-readonly="true"></textarea>
|
|
</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 || ' '}}
|
|
</div>
|
|
<div *ngIf="field.type === fieldType.Hidden">
|
|
<span [hidden]="!field.showValue" class="monospaced show-whitespace">{{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'" aria-hidden="true"></i>
|
|
<i class="fa fa-square-o" *ngIf="field.value !== 'true'" aria-hidden="true"></i>
|
|
<span class="sr-only">{{field.value}}</span>
|
|
</div>
|
|
</div>
|
|
<div class="action-buttons">
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
|
|
*ngIf="field.type === fieldType.Hidden && cipher.viewPassword"
|
|
(click)="toggleFieldValue(field)">
|
|
<i class="fa fa-lg" aria-hidden="true"
|
|
[ngClass]="{'fa-eye': !field.showValue, 'fa-eye-slash': field.showValue}"></i>
|
|
</a>
|
|
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyValue' | i18n}}"
|
|
*ngIf="field.value && field.type !== fieldType.Boolean && !(field.type === fieldType.Hidden && !cipher.viewPassword)"
|
|
(click)="copy(field.value, 'value', field.type === fieldType.Hidden ? 'H_Field' : 'Field')">
|
|
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="box" *ngIf="cipher.hasAttachments && (canAccessPremium || cipher.organizationId) && showAttachments">
|
|
<div class="box-header">
|
|
{{'attachments' | i18n}}
|
|
</div>
|
|
<div class="box-content">
|
|
<a class="box-content-row box-content-row-flex text-default" *ngFor="let attachment of cipher.attachments"
|
|
href="#" appStopClick appBlurCLick (click)="downloadAttachment(attachment)">
|
|
<span class="row-main">{{attachment.fileName}}</span>
|
|
<small class="row-sub-label">{{attachment.sizeName}}</small>
|
|
<i class="fa fa-download fa-fw row-sub-icon" *ngIf="!attachment.downloading" aria-hidden="true"></i>
|
|
<i class="fa fa-spinner fa-fw fa-spin row-sub-icon" *ngIf="attachment.downloading"
|
|
aria-hidden="true"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="box list">
|
|
<div class="box-content single-line">
|
|
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="fillCipher()"
|
|
*ngIf="!cipher.isDeleted && !inPopout">
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="fa fa-pencil-square-o fa-lg fa-fw"></i>
|
|
</div>
|
|
<span>{{'autoFill' | i18n}}</span>
|
|
</div>
|
|
</a>
|
|
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="fillCipherAndSave()"
|
|
*ngIf="!cipher.isDeleted && !inPopout">
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="fa fa-bookmark fa-lg fa-fw"></i>
|
|
</div>
|
|
<span>{{'autoFillAndSave' | i18n}}</span>
|
|
</div>
|
|
</a>
|
|
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="clone()"
|
|
*ngIf="!cipher.organizationId && !cipher.isDeleted">
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="fa fa-files-o fa-lg fa-fw"></i>
|
|
</div>
|
|
<span>{{'cloneItem' | i18n}}</span>
|
|
</div>
|
|
</a>
|
|
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="share()" *ngIf="!cipher.organizationId">
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="fa fa-share-alt fa-lg fa-fw"></i>
|
|
</div>
|
|
<span>{{'shareItem' | i18n}}</span>
|
|
</div>
|
|
</a>
|
|
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="restore()" *ngIf="cipher.isDeleted">
|
|
<div class="row-main text-primary">
|
|
<div class="icon text-primary" aria-hidden="true">
|
|
<i class="fa fa-undo fa-lg fa-fw"></i>
|
|
</div>
|
|
<span>{{'restoreItem' | i18n}}</span>
|
|
</div>
|
|
</a>
|
|
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="delete()">
|
|
<div class="row-main text-danger">
|
|
<div class="icon text-danger" aria-hidden="true">
|
|
<i class="fa fa-trash-o fa-lg fa-fw"></i>
|
|
</div>
|
|
<span>{{(cipher.isDeleted ? 'permanentlyDeleteItem' : 'deleteItem') | i18n}}</span>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="box">
|
|
<div class="box-footer">
|
|
<div>
|
|
<b class="font-weight-semibold">{{'dateUpdated' | i18n}}:</b>
|
|
{{cipher.revisionDate | date:'medium'}}
|
|
</div>
|
|
<div *ngIf="cipher.passwordRevisionDisplayDate">
|
|
<b class="font-weight-semibold">{{'datePasswordUpdated' | i18n}}:</b>
|
|
{{cipher.passwordRevisionDisplayDate | date:'medium'}}
|
|
</div>
|
|
<div *ngIf="cipher.hasPasswordHistory">
|
|
<b class="font-weight-semibold">{{'passwordHistory' | i18n}}:</b>
|
|
<a routerLink="/cipher-password-history" [queryParams]="{cipherId: cipher.id}" appStopClick
|
|
title="{{'passwordHistory' | i18n}}">
|
|
{{cipher.passwordHistory.length}}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</content>
|