1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 23:33:31 +00:00

[PM-8455] [PM-7683] Dynamic list items - Copy Action (#9410)

* [PM-7683] Add fullAddressForCopy helper to identity.view

* [PM-7683] Introduce CopyCipherFieldService to the Vault library

- A new CopyCipherFieldService that can be used to copy a cipher's field to the user clipboard
- A new appCopyField directive to make it easy to copy a cipher's fields in templates
- Tests for the CopyCipherFieldService

* [PM-7683] Introduce item-copy-actions.component

* [PM-7683] Fix username value in copy cipher directive

* [PM-7683] Add title to View item link

* [PM-7683] Move disabled logic into own method
This commit is contained in:
Shane Melton
2024-06-04 14:09:09 -07:00
committed by GitHub
parent fc2953a126
commit d1a9d6f613
10 changed files with 580 additions and 12 deletions

View File

@@ -185,7 +185,7 @@
"message": "Continue to browser extension store?"
},
"continueToBrowserExtensionStoreDesc": {
"message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now."
"message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now."
},
"changeMasterPasswordOnWebConfirmation": {
"message": "You can change your master password on the Bitwarden web app."
@@ -3281,7 +3281,7 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfo": {
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
@@ -3291,7 +3291,37 @@
}
}
},
"moreOptions": {
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"moreOptionsLabel": {
"message": "More options, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with more options for an item.",
"placeholders": {
@@ -3301,6 +3331,35 @@
}
}
},
"moreOptionsTitle": {
"message": "More options - $ITEMNAME$",
"description": "Title for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"viewItemTitle": {
"message": "View item - $ITEMNAME$",
"description": "Title for a link that opens a view for an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyEmail": {
"message": "Copy email"
},
"copyPhone": {
"message": "Copy phone"
},
"copyAddress": {
"message": "Copy address"
},
"adminConsole": {
"message": "Admin Console"
},

View File

@@ -0,0 +1,77 @@
<bit-item-action *ngIf="cipher.type === CipherType.Login">
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[attr.aria-label]="'copyInfoLabel' | i18n: cipher.name"
[title]="'copyInfoTitle' | i18n: cipher.name"
[bitMenuTriggerFor]="loginOptions"
></button>
<bit-menu #loginOptions>
<button type="button" bitMenuItem appCopyField="username" [cipher]="cipher">
{{ "copyUsername" | i18n }}
</button>
<button type="button" bitMenuItem appCopyField="password" [cipher]="cipher">
{{ "copyPassword" | i18n }}
</button>
<button type="button" bitMenuItem appCopyField="totp" [cipher]="cipher">
{{ "copyVerificationCode" | i18n }}
</button>
</bit-menu>
</bit-item-action>
<bit-item-action *ngIf="cipher.type === CipherType.Card">
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[attr.aria-label]="'copyInfoLabel' | i18n: cipher.name"
[title]="'copyInfoTitle' | i18n: cipher.name"
[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>
</bit-item-action>
<bit-item-action *ngIf="cipher.type === CipherType.Identity">
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[attr.aria-label]="'copyInfoLabel' | i18n: cipher.name"
[title]="'copyInfoTitle' | i18n: cipher.name"
[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>
</bit-item-action>
<bit-item-action *ngIf="cipher.type === CipherType.SecureNote">
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[attr.aria-label]="'copyNoteLabel' | i18n: cipher.name"
[title]="'copyNoteTitle' | i18n: cipher.name"
appCopyField="secureNote"
[cipher]="cipher"
></button>
</bit-item-action>

View File

@@ -0,0 +1,29 @@
import { CommonModule } from "@angular/common";
import { Component, Input } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
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";
@Component({
standalone: true,
selector: "app-item-copy-actions",
templateUrl: "item-copy-actions.component.html",
imports: [
ItemModule,
IconButtonModule,
JslibModule,
MenuModule,
CommonModule,
CopyCipherFieldDirective,
],
})
export class ItemCopyActionsComponent {
@Input() cipher: CipherView;
protected CipherType = CipherType;
constructor() {}
}

View File

@@ -13,7 +13,12 @@
</popup-section-header>
<bit-item-group>
<bit-item *ngFor="let cipher of ciphers">
<a bit-item-content [routerLink]="['/view-cipher']" [queryParams]="{ cipherId: cipher.id }">
<a
bit-item-content
[routerLink]="['/view-cipher']"
[queryParams]="{ cipherId: cipher.id }"
[appA11yTitle]="'viewItemTitle' | i18n: cipher.name"
>
<app-vault-icon slot="start" [cipher]="cipher"></app-vault-icon>
{{ cipher.name }}
<span slot="secondary">{{ cipher.subTitle }}</span>
@@ -22,14 +27,7 @@
<bit-item-action *ngIf="showAutoFill">
<button type="button" bitBadge variant="primary">{{ "autoFill" | i18n }}</button>
</bit-item-action>
<bit-item-action>
<button
type="button"
bitIconButton="bwi-clone"
size="small"
[attr.aria-label]="'copyInfo' | i18n: cipher.name"
></button>
</bit-item-action>
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
<bit-item-action>
<button
type="button"

View File

@@ -14,6 +14,7 @@ import {
} from "@bitwarden/components";
import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component";
import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component";
@Component({
imports: [
@@ -27,6 +28,7 @@ import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup
JslibModule,
PopupSectionHeaderComponent,
RouterLink,
ItemCopyActionsComponent,
],
selector: "app-vault-list-items-container",
templateUrl: "vault-list-items-container.component.html",