mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 04:33:38 +00:00
update vault usage to surface bit-item-action to be in same template as bit-item
This commit is contained in:
@@ -1,218 +0,0 @@
|
||||
<ng-container *ngIf="cipher.type === CipherType.Login">
|
||||
<ng-container *ngIf="showQuickCopyActions$ | async; else loginCopyMenu">
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-user"
|
||||
size="small"
|
||||
appCopyField="username"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copyUsername' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button
|
||||
*ngIf="cipher.viewPassword"
|
||||
type="button"
|
||||
bitIconButton="bwi-key"
|
||||
size="small"
|
||||
appCopyField="password"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copyPassword' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clock"
|
||||
size="small"
|
||||
appCopyField="totp"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copyVerificationCode' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #loginCopyMenu>
|
||||
<bit-item-action>
|
||||
<button
|
||||
*ngIf="singleCopiableLogin"
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
'copyFieldValue' | i18n: singleCopiableLogin.key : singleCopiableLogin.value
|
||||
"
|
||||
[appCopyClick]="singleCopiableLogin.value"
|
||||
[valueLabel]="singleCopiableLogin.key"
|
||||
showToast
|
||||
></button>
|
||||
<ng-container *ngIf="!singleCopiableLogin">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
hasLoginValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!hasLoginValues"
|
||||
[bitMenuTriggerFor]="loginOptions"
|
||||
></button>
|
||||
<bit-menu #loginOptions>
|
||||
<button type="button" bitMenuItem appCopyField="username" [cipher]="cipher">
|
||||
{{ "copyUsername" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="cipher.viewPassword"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
appCopyField="password"
|
||||
[cipher]="cipher"
|
||||
>
|
||||
{{ "copyPassword" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="totp" [cipher]="cipher">
|
||||
{{ "copyVerificationCode" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</bit-item-action>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="cipher.type === CipherType.Card">
|
||||
<ng-container *ngIf="showQuickCopyActions$ | async; else cardCopyMenu">
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-hashtag"
|
||||
size="small"
|
||||
appCopyField="cardNumber"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copyNumber' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-key"
|
||||
size="small"
|
||||
appCopyField="securityCode"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copySecurityCode' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
<ng-template #cardCopyMenu>
|
||||
<bit-item-action>
|
||||
<button
|
||||
*ngIf="singleCopiableCard"
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="'copyFieldValue' | i18n: singleCopiableCard.key : singleCopiableCard.value"
|
||||
[appCopyClick]="singleCopiableCard.value"
|
||||
[valueLabel]="singleCopiableCard.key"
|
||||
showToast
|
||||
></button>
|
||||
<ng-container *ngIf="!singleCopiableCard">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
hasCardValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!hasCardValues"
|
||||
[bitMenuTriggerFor]="cardOptions"
|
||||
></button>
|
||||
<bit-menu #cardOptions>
|
||||
<button type="button" bitMenuItem appCopyField="cardNumber" [cipher]="cipher">
|
||||
{{ "copyNumber" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="securityCode" [cipher]="cipher">
|
||||
{{ "copySecurityCode" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</bit-item-action>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<bit-item-action *ngIf="cipher.type === CipherType.Identity">
|
||||
<button
|
||||
*ngIf="singleCopiableIdentity"
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
'copyFieldValue' | i18n: singleCopiableIdentity.key : singleCopiableIdentity.value
|
||||
"
|
||||
[appCopyClick]="singleCopiableIdentity.value"
|
||||
[valueLabel]="singleCopiableIdentity.key"
|
||||
showToast
|
||||
></button>
|
||||
<ng-container *ngIf="!singleCopiableIdentity">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
hasIdentityValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!hasIdentityValues"
|
||||
[bitMenuTriggerFor]="identityOptions"
|
||||
></button>
|
||||
<bit-menu #identityOptions>
|
||||
<button type="button" bitMenuItem appCopyField="username" [cipher]="cipher">
|
||||
{{ "copyUsername" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="email" [cipher]="cipher">
|
||||
{{ "copyEmail" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="phone" [cipher]="cipher">
|
||||
{{ "copyPhone" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="address" [cipher]="cipher">
|
||||
{{ "copyAddress" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</bit-item-action>
|
||||
|
||||
<bit-item-action *ngIf="cipher.type === CipherType.SecureNote">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
hasSecureNoteValue ? ('copyNoteTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||
"
|
||||
appCopyField="secureNote"
|
||||
[cipher]="cipher"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
|
||||
<bit-item-action *ngIf="cipher.type === CipherType.SshKey">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
hasSshKeyValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!hasSshKeyValues"
|
||||
[bitMenuTriggerFor]="sshKeyOptions"
|
||||
></button>
|
||||
<bit-menu #sshKeyOptions>
|
||||
<button type="button" bitMenuItem appCopyField="privateKey" [cipher]="cipher">
|
||||
{{ "copyPrivateKey" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="publicKey" [cipher]="cipher">
|
||||
{{ "copyPublicKey" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="keyFingerprint" [cipher]="cipher">
|
||||
{{ "copyFingerprint" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</bit-item-action>
|
||||
@@ -1,116 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input, inject } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components";
|
||||
import { CopyCipherFieldDirective } from "@bitwarden/vault";
|
||||
|
||||
import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";
|
||||
|
||||
type CipherItem = {
|
||||
value: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-item-copy-actions",
|
||||
templateUrl: "item-copy-actions.component.html",
|
||||
imports: [
|
||||
ItemModule,
|
||||
IconButtonModule,
|
||||
JslibModule,
|
||||
MenuModule,
|
||||
CommonModule,
|
||||
CopyCipherFieldDirective,
|
||||
],
|
||||
})
|
||||
export class ItemCopyActionsComponent {
|
||||
protected showQuickCopyActions$ = inject(VaultPopupCopyButtonsService).showQuickCopyActions$;
|
||||
|
||||
@Input() cipher: CipherView;
|
||||
|
||||
protected CipherType = CipherType;
|
||||
|
||||
get hasLoginValues() {
|
||||
return (
|
||||
!!this.cipher.login.hasTotp || !!this.cipher.login.password || !!this.cipher.login.username
|
||||
);
|
||||
}
|
||||
|
||||
get singleCopiableLogin() {
|
||||
const loginItems: CipherItem[] = [
|
||||
{ value: this.cipher.login.username, key: "username" },
|
||||
{ value: this.cipher.login.password, key: "password" },
|
||||
{ value: this.cipher.login.totp, key: "totp" },
|
||||
];
|
||||
// If both the password and username are visible but the password is hidden, return the username
|
||||
if (!this.cipher.viewPassword && this.cipher.login.username && this.cipher.login.password) {
|
||||
return { value: this.cipher.login.username, key: this.i18nService.t("username") };
|
||||
}
|
||||
return this.findSingleCopiableItem(loginItems);
|
||||
}
|
||||
|
||||
get singleCopiableCard() {
|
||||
const cardItems: CipherItem[] = [
|
||||
{ value: this.cipher.card.code, key: "code" },
|
||||
{ value: this.cipher.card.number, key: "number" },
|
||||
];
|
||||
return this.findSingleCopiableItem(cardItems);
|
||||
}
|
||||
|
||||
get singleCopiableIdentity() {
|
||||
const identityItems: CipherItem[] = [
|
||||
{ value: this.cipher.identity.fullAddressForCopy, key: "address" },
|
||||
{ value: this.cipher.identity.email, key: "email" },
|
||||
{ value: this.cipher.identity.username, key: "username" },
|
||||
{ value: this.cipher.identity.phone, key: "phone" },
|
||||
];
|
||||
return this.findSingleCopiableItem(identityItems);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a list of CipherItems, if there is only one item with a value,
|
||||
* return it with the translated key. Otherwise return null
|
||||
*/
|
||||
findSingleCopiableItem(items: { value: string; key: string }[]): CipherItem | null {
|
||||
const singleItemWithValue = items.find(
|
||||
(key) => key.value && items.every((f) => f === key || !f.value),
|
||||
);
|
||||
return singleItemWithValue
|
||||
? { value: singleItemWithValue.value, key: this.i18nService.t(singleItemWithValue.key) }
|
||||
: null;
|
||||
}
|
||||
|
||||
get hasCardValues() {
|
||||
return !!this.cipher.card.code || !!this.cipher.card.number;
|
||||
}
|
||||
|
||||
get hasIdentityValues() {
|
||||
return (
|
||||
!!this.cipher.identity.fullAddressForCopy ||
|
||||
!!this.cipher.identity.email ||
|
||||
!!this.cipher.identity.username ||
|
||||
!!this.cipher.identity.phone
|
||||
);
|
||||
}
|
||||
|
||||
get hasSecureNoteValue() {
|
||||
return !!this.cipher.notes;
|
||||
}
|
||||
|
||||
get hasSshKeyValues() {
|
||||
return (
|
||||
!!this.cipher.sshKey.privateKey ||
|
||||
!!this.cipher.sshKey.publicKey ||
|
||||
!!this.cipher.sshKey.keyFingerprint
|
||||
);
|
||||
}
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
}
|
||||
@@ -1,39 +1,37 @@
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
[attr.aria-label]="'moreOptionsLabel' | i18n: cipher.name"
|
||||
[title]="'moreOptionsTitle' | i18n: cipher.name"
|
||||
[disabled]="cipher.decryptionFailure"
|
||||
[bitMenuTriggerFor]="moreOptions"
|
||||
></button>
|
||||
<bit-menu #moreOptions>
|
||||
<ng-container *ngIf="canAutofill && !hideAutofillOptions">
|
||||
<ng-container *ngIf="autofillAllowed$ | async">
|
||||
<button type="button" bitMenuItem (click)="doAutofill()">
|
||||
{{ "autofill" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem *ngIf="canEdit && isLogin" (click)="doAutofillAndSave()">
|
||||
{{ "fillAndSave" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showViewOption">
|
||||
<button type="button" bitMenuItem (click)="onView()">
|
||||
{{ "view" | i18n }}
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
size="small"
|
||||
[attr.aria-label]="'moreOptionsLabel' | i18n: cipher.name"
|
||||
[title]="'moreOptionsTitle' | i18n: cipher.name"
|
||||
[disabled]="cipher.decryptionFailure"
|
||||
[bitMenuTriggerFor]="moreOptions"
|
||||
></button>
|
||||
<bit-menu #moreOptions>
|
||||
<ng-container *ngIf="canAutofill && !hideAutofillOptions">
|
||||
<ng-container *ngIf="autofillAllowed$ | async">
|
||||
<button type="button" bitMenuItem (click)="doAutofill()">
|
||||
{{ "autofill" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem *ngIf="canEdit && isLogin" (click)="doAutofillAndSave()">
|
||||
{{ "fillAndSave" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<button type="button" bitMenuItem (click)="toggleFavorite()">
|
||||
{{ favoriteText | i18n }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showViewOption">
|
||||
<button type="button" bitMenuItem (click)="onView()">
|
||||
{{ "view" | i18n }}
|
||||
</button>
|
||||
<ng-container *ngIf="canEdit && canViewPassword">
|
||||
<a bitMenuItem (click)="clone()" *ngIf="canClone$ | async">
|
||||
{{ "clone" | i18n }}
|
||||
</a>
|
||||
<a bitMenuItem *ngIf="hasOrganizations" (click)="conditionallyNavigateToAssignCollections()">
|
||||
{{ "assignToCollections" | i18n }}
|
||||
</a>
|
||||
</ng-container>
|
||||
</bit-menu>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
<button type="button" bitMenuItem (click)="toggleFavorite()">
|
||||
{{ favoriteText | i18n }}
|
||||
</button>
|
||||
<ng-container *ngIf="canEdit && canViewPassword">
|
||||
<a bitMenuItem (click)="clone()" *ngIf="canClone$ | async">
|
||||
{{ "clone" | i18n }}
|
||||
</a>
|
||||
<a bitMenuItem *ngIf="hasOrganizations" (click)="conditionallyNavigateToAssignCollections()">
|
||||
{{ "assignToCollections" | i18n }}
|
||||
</a>
|
||||
</ng-container>
|
||||
</bit-menu>
|
||||
|
||||
@@ -15,13 +15,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
||||
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import {
|
||||
DialogService,
|
||||
IconButtonModule,
|
||||
ItemModule,
|
||||
MenuModule,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { DialogService, IconButtonModule, MenuModule, ToastService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
|
||||
@@ -31,7 +25,7 @@ import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
|
||||
standalone: true,
|
||||
selector: "app-item-more-options",
|
||||
templateUrl: "./item-more-options.component.html",
|
||||
imports: [ItemModule, IconButtonModule, MenuModule, CommonModule, JslibModule, RouterModule],
|
||||
imports: [IconButtonModule, MenuModule, CommonModule, JslibModule, RouterModule],
|
||||
})
|
||||
export class ItemMoreOptionsComponent implements OnInit {
|
||||
private _cipher$ = new BehaviorSubject<CipherView>(undefined);
|
||||
|
||||
@@ -81,18 +81,18 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template #itemGroup>
|
||||
<bit-item-group>
|
||||
<ng-container *ngFor="let group of cipherGroups$()">
|
||||
<ng-container *ngIf="group.subHeaderKey">
|
||||
<h3 class="tw-text-muted tw-text-xs tw-font-semibold tw-pl-1 tw-mb-1 bit-compact:tw-m-0">
|
||||
{{ group.subHeaderKey | i18n }}
|
||||
</h3>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let group of cipherGroups$()">
|
||||
<ng-container *ngIf="group.subHeaderKey">
|
||||
<h3 class="tw-text-muted tw-text-xs tw-font-semibold tw-pl-1 tw-mb-1 bit-compact:tw-m-0">
|
||||
{{ group.subHeaderKey | i18n }}
|
||||
</h3>
|
||||
</ng-container>
|
||||
|
||||
<cdk-virtual-scroll-viewport
|
||||
[itemSize]="itemHeight$ | async"
|
||||
class="tw-overflow-visible [&>.cdk-virtual-scroll-content-wrapper]:[contain:layout_style]"
|
||||
>
|
||||
<cdk-virtual-scroll-viewport
|
||||
[itemSize]="itemHeight$ | async"
|
||||
class="tw-overflow-visible [&>.cdk-virtual-scroll-content-wrapper]:[contain:layout_style]"
|
||||
>
|
||||
<bit-item-group>
|
||||
<bit-item *cdkVirtualFor="let cipher of group.ciphers">
|
||||
<button
|
||||
bit-item-content
|
||||
@@ -147,15 +147,262 @@
|
||||
[title]="'launchWebsiteName' | i18n: cipher.name"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
|
||||
<app-item-more-options
|
||||
[cipher]="cipher"
|
||||
[hideAutofillOptions]="hideAutofillOptions$ | async"
|
||||
[showViewOption]="primaryActionAutofill"
|
||||
></app-item-more-options>
|
||||
|
||||
@let cbv = copyButtonsService.toCopyButtonsView(cipher);
|
||||
<ng-container *ngIf="cipher.type === CipherType.Login">
|
||||
<ng-container
|
||||
*ngIf="copyButtonsService.showQuickCopyActions$ | async; else loginCopyMenu"
|
||||
>
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-user"
|
||||
size="small"
|
||||
appCopyField="username"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copyUsername' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button
|
||||
*ngIf="cipher.viewPassword"
|
||||
type="button"
|
||||
bitIconButton="bwi-key"
|
||||
size="small"
|
||||
appCopyField="password"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copyPassword' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clock"
|
||||
size="small"
|
||||
appCopyField="totp"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copyVerificationCode' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #loginCopyMenu>
|
||||
<bit-item-action>
|
||||
<button
|
||||
*ngIf="cbv.singleCopiableLogin"
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
'copyFieldValue'
|
||||
| i18n: cbv.singleCopiableLogin.key : cbv.singleCopiableLogin.value
|
||||
"
|
||||
[appCopyClick]="cbv.singleCopiableLogin.value"
|
||||
[valueLabel]="cbv.singleCopiableLogin.key"
|
||||
showToast
|
||||
></button>
|
||||
<ng-container *ngIf="!cbv.singleCopiableLogin">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
cbv.hasLoginValues
|
||||
? ('copyInfoTitle' | i18n: cipher.name)
|
||||
: ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!cbv.hasLoginValues"
|
||||
[bitMenuTriggerFor]="loginOptions"
|
||||
></button>
|
||||
<bit-menu #loginOptions>
|
||||
<button type="button" bitMenuItem appCopyField="username" [cipher]="cipher">
|
||||
{{ "copyUsername" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="cipher.viewPassword"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
appCopyField="password"
|
||||
[cipher]="cipher"
|
||||
>
|
||||
{{ "copyPassword" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="totp" [cipher]="cipher">
|
||||
{{ "copyVerificationCode" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</bit-item-action>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="cipher.type === CipherType.Card">
|
||||
<ng-container
|
||||
*ngIf="copyButtonsService.showQuickCopyActions$ | async; else cardCopyMenu"
|
||||
>
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-hashtag"
|
||||
size="small"
|
||||
appCopyField="cardNumber"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copyNumber' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-key"
|
||||
size="small"
|
||||
appCopyField="securityCode"
|
||||
[cipher]="cipher"
|
||||
[appA11yTitle]="'copySecurityCode' | i18n"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
<ng-template #cardCopyMenu>
|
||||
<bit-item-action>
|
||||
<button
|
||||
*ngIf="cbv.singleCopiableCard"
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
'copyFieldValue'
|
||||
| i18n: cbv.singleCopiableCard.key : cbv.singleCopiableCard.value
|
||||
"
|
||||
[appCopyClick]="cbv.singleCopiableCard.value"
|
||||
[valueLabel]="cbv.singleCopiableCard.key"
|
||||
showToast
|
||||
></button>
|
||||
<ng-container *ngIf="!cbv.singleCopiableCard">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
cbv.hasCardValues
|
||||
? ('copyInfoTitle' | i18n: cipher.name)
|
||||
: ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!cbv.hasCardValues"
|
||||
[bitMenuTriggerFor]="cardOptions"
|
||||
></button>
|
||||
<bit-menu #cardOptions>
|
||||
<button type="button" bitMenuItem appCopyField="cardNumber" [cipher]="cipher">
|
||||
{{ "copyNumber" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
appCopyField="securityCode"
|
||||
[cipher]="cipher"
|
||||
>
|
||||
{{ "copySecurityCode" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</bit-item-action>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<bit-item-action *ngIf="cipher.type === CipherType.Identity">
|
||||
<button
|
||||
*ngIf="cbv.singleCopiableIdentity"
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
'copyFieldValue'
|
||||
| i18n: cbv.singleCopiableIdentity.key : cbv.singleCopiableIdentity.value
|
||||
"
|
||||
[appCopyClick]="cbv.singleCopiableIdentity.value"
|
||||
[valueLabel]="cbv.singleCopiableIdentity.key"
|
||||
showToast
|
||||
></button>
|
||||
<ng-container *ngIf="!cbv.singleCopiableIdentity">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
cbv.hasIdentityValues
|
||||
? ('copyInfoTitle' | i18n: cipher.name)
|
||||
: ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!cbv.hasIdentityValues"
|
||||
[bitMenuTriggerFor]="identityOptions"
|
||||
></button>
|
||||
<bit-menu #identityOptions>
|
||||
<button type="button" bitMenuItem appCopyField="username" [cipher]="cipher">
|
||||
{{ "copyUsername" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="email" [cipher]="cipher">
|
||||
{{ "copyEmail" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="phone" [cipher]="cipher">
|
||||
{{ "copyPhone" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="address" [cipher]="cipher">
|
||||
{{ "copyAddress" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</bit-item-action>
|
||||
|
||||
<bit-item-action *ngIf="cipher.type === CipherType.SecureNote">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
cbv.hasSecureNoteValue
|
||||
? ('copyNoteTitle' | i18n: cipher.name)
|
||||
: ('noValuesToCopy' | i18n)
|
||||
"
|
||||
appCopyField="secureNote"
|
||||
[cipher]="cipher"
|
||||
></button>
|
||||
</bit-item-action>
|
||||
|
||||
<bit-item-action *ngIf="cipher.type === CipherType.SshKey">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[appA11yTitle]="
|
||||
cbv.hasSshKeyValues
|
||||
? ('copyInfoTitle' | i18n: cipher.name)
|
||||
: ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!cbv.hasSshKeyValues"
|
||||
[bitMenuTriggerFor]="sshKeyOptions"
|
||||
></button>
|
||||
<bit-menu #sshKeyOptions>
|
||||
<button type="button" bitMenuItem appCopyField="privateKey" [cipher]="cipher">
|
||||
{{ "copyPrivateKey" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="publicKey" [cipher]="cipher">
|
||||
{{ "copyPublicKey" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="keyFingerprint" [cipher]="cipher">
|
||||
{{ "copyFingerprint" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</bit-item-action>
|
||||
|
||||
<!-- <app-item-copy-actions [cipher]="cipher"></app-item-copy-actions> -->
|
||||
<bit-item-action>
|
||||
<app-item-more-options
|
||||
[cipher]="cipher"
|
||||
[hideAutofillOptions]="hideAutofillOptions$ | async"
|
||||
[showViewOption]="primaryActionAutofill"
|
||||
></app-item-more-options>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</ng-container>
|
||||
</bit-item-group>
|
||||
</bit-item-group>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
@@ -42,8 +42,10 @@ import {
|
||||
SectionComponent,
|
||||
SectionHeaderComponent,
|
||||
TypographyModule,
|
||||
MenuModule,
|
||||
} from "@bitwarden/components";
|
||||
import {
|
||||
CopyCipherFieldDirective,
|
||||
DecryptionFailureDialogComponent,
|
||||
OrgIconDirective,
|
||||
PasswordRepromptService,
|
||||
@@ -52,9 +54,9 @@ import {
|
||||
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
|
||||
import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";
|
||||
import { VaultPopupSectionService } from "../../../services/vault-popup-section.service";
|
||||
import { PopupCipherView } from "../../../views/popup-cipher.view";
|
||||
import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component";
|
||||
import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component";
|
||||
|
||||
@Component({
|
||||
@@ -68,13 +70,14 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
|
||||
TypographyModule,
|
||||
JslibModule,
|
||||
SectionHeaderComponent,
|
||||
ItemCopyActionsComponent,
|
||||
ItemMoreOptionsComponent,
|
||||
OrgIconDirective,
|
||||
ScrollingModule,
|
||||
DisclosureComponent,
|
||||
DisclosureTriggerForDirective,
|
||||
DecryptionFailureDialogComponent,
|
||||
MenuModule,
|
||||
CopyCipherFieldDirective,
|
||||
],
|
||||
selector: "app-vault-list-items-container",
|
||||
templateUrl: "vault-list-items-container.component.html",
|
||||
@@ -84,6 +87,9 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
|
||||
export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
|
||||
private compactModeService = inject(CompactModeService);
|
||||
private vaultPopupSectionService = inject(VaultPopupSectionService);
|
||||
protected copyButtonsService = inject(VaultPopupCopyButtonsService);
|
||||
|
||||
protected CipherType = CipherType;
|
||||
|
||||
@ViewChild(CdkVirtualScrollViewport, { static: false }) viewPort: CdkVirtualScrollViewport;
|
||||
@ViewChild(DisclosureComponent) disclosure: DisclosureComponent;
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { map, Observable, shareReplay } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import {
|
||||
GlobalStateProvider,
|
||||
KeyDefinition,
|
||||
VAULT_APPEARANCE,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
export type CopyButtonDisplayMode = "combined" | "quick";
|
||||
|
||||
type CipherItem = {
|
||||
value: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
type CopyButtonsView = {
|
||||
hasLoginValues: boolean;
|
||||
hasCardValues: boolean;
|
||||
hasIdentityValues: boolean;
|
||||
hasSecureNoteValue: boolean;
|
||||
hasSshKeyValues: boolean;
|
||||
singleCopiableLogin: CipherItem | null;
|
||||
singleCopiableCard: CipherItem | null;
|
||||
singleCopiableIdentity: CipherItem | null;
|
||||
};
|
||||
|
||||
const COPY_BUTTON = new KeyDefinition<CopyButtonDisplayMode>(VAULT_APPEARANCE, "copyButtons", {
|
||||
deserializer: (s) => s,
|
||||
});
|
||||
@@ -20,6 +38,7 @@ const COPY_BUTTON = new KeyDefinition<CopyButtonDisplayMode>(VAULT_APPEARANCE, "
|
||||
export class VaultPopupCopyButtonsService {
|
||||
private readonly DEFAULT_DISPLAY_MODE = "combined";
|
||||
private state = inject(GlobalStateProvider).get(COPY_BUTTON);
|
||||
private i18nService = inject(I18nService);
|
||||
|
||||
displayMode$: Observable<CopyButtonDisplayMode> = this.state.state$.pipe(
|
||||
map((state) => state ?? this.DEFAULT_DISPLAY_MODE),
|
||||
@@ -37,4 +56,88 @@ export class VaultPopupCopyButtonsService {
|
||||
async setShowQuickCopyActions(value: boolean) {
|
||||
await this.setDisplayMode(value ? "quick" : "combined");
|
||||
}
|
||||
|
||||
toCopyButtonsView(cipher: CipherView): CopyButtonsView {
|
||||
return {
|
||||
hasLoginValues: this.hasLoginValues(cipher),
|
||||
hasCardValues: this.hasCardValues(cipher),
|
||||
hasIdentityValues: this.hasIdentityValues(cipher),
|
||||
hasSecureNoteValue: this.hasSecureNoteValue(cipher),
|
||||
hasSshKeyValues: this.hasSshKeyValues(cipher),
|
||||
singleCopiableLogin: this.singleCopiableLogin(cipher),
|
||||
singleCopiableCard: this.singleCopiableCard(cipher),
|
||||
singleCopiableIdentity: this.singleCopiableIdentity(cipher),
|
||||
};
|
||||
}
|
||||
|
||||
private hasLoginValues(cipher: CipherView) {
|
||||
return !!cipher.login.hasTotp || !!cipher.login.password || !!cipher.login.username;
|
||||
}
|
||||
|
||||
private singleCopiableLogin(cipher: CipherView) {
|
||||
const loginItems: CipherItem[] = [
|
||||
{ value: cipher.login.username, key: "username" },
|
||||
{ value: cipher.login.password, key: "password" },
|
||||
{ value: cipher.login.totp, key: "totp" },
|
||||
];
|
||||
// If both the password and username are visible but the password is hidden, return the username
|
||||
if (!cipher.viewPassword && cipher.login.username && cipher.login.password) {
|
||||
return { value: cipher.login.username, key: this.i18nService.t("username") };
|
||||
}
|
||||
return this.findSingleCopiableItem(loginItems);
|
||||
}
|
||||
|
||||
private singleCopiableCard(cipher: CipherView) {
|
||||
const cardItems: CipherItem[] = [
|
||||
{ value: cipher.card.code, key: "code" },
|
||||
{ value: cipher.card.number, key: "number" },
|
||||
];
|
||||
return this.findSingleCopiableItem(cardItems);
|
||||
}
|
||||
|
||||
private singleCopiableIdentity(cipher: CipherView) {
|
||||
const identityItems: CipherItem[] = [
|
||||
{ value: cipher.identity.fullAddressForCopy, key: "address" },
|
||||
{ value: cipher.identity.email, key: "email" },
|
||||
{ value: cipher.identity.username, key: "username" },
|
||||
{ value: cipher.identity.phone, key: "phone" },
|
||||
];
|
||||
return this.findSingleCopiableItem(identityItems);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a list of CipherItems, if there is only one item with a value,
|
||||
* return it with the translated key. Otherwise return null
|
||||
*/
|
||||
private findSingleCopiableItem(items: { value: string; key: string }[]): CipherItem | null {
|
||||
const singleItemWithValue = items.find(
|
||||
(key) => key.value && items.every((f) => f === key || !f.value),
|
||||
);
|
||||
return singleItemWithValue
|
||||
? { value: singleItemWithValue.value, key: this.i18nService.t(singleItemWithValue.key) }
|
||||
: null;
|
||||
}
|
||||
|
||||
private hasCardValues(cipher: CipherView) {
|
||||
return !!cipher.card.code || !!cipher.card.number;
|
||||
}
|
||||
|
||||
private hasIdentityValues(cipher: CipherView) {
|
||||
return (
|
||||
!!cipher.identity.fullAddressForCopy ||
|
||||
!!cipher.identity.email ||
|
||||
!!cipher.identity.username ||
|
||||
!!cipher.identity.phone
|
||||
);
|
||||
}
|
||||
|
||||
private hasSecureNoteValue(cipher: CipherView) {
|
||||
return !!cipher.notes;
|
||||
}
|
||||
|
||||
private hasSshKeyValues(cipher: CipherView) {
|
||||
return (
|
||||
!!cipher.sshKey.privateKey || !!cipher.sshKey.publicKey || !!cipher.sshKey.keyFingerprint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user