mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-30 13:03:53 +01:00
context menu options for cipher listing
This commit is contained in:
parent
6c0148bb96
commit
34b3890647
@ -9,7 +9,8 @@
|
|||||||
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
|
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
|
||||||
<div class="list" *ngIf="searchedCiphers.length > 0">
|
<div class="list" *ngIf="searchedCiphers.length > 0">
|
||||||
<a *ngFor="let c of searchedCiphers" appStopClick (click)="cipherClicked(c)"
|
<a *ngFor="let c of searchedCiphers" appStopClick (click)="cipherClicked(c)"
|
||||||
href="#" title="{{'viewItem' | i18n}}" [ngClass]="{'active': c.id === activeCipherId}">
|
(contextmenu)="cipherRightClicked(c)" href="#" title="{{'viewItem' | i18n}}"
|
||||||
|
[ngClass]="{'active': c.id === activeCipherId}">
|
||||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||||
<span class="text">
|
<span class="text">
|
||||||
{{c.name}}
|
{{c.name}}
|
||||||
@ -32,7 +33,8 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<button appBlurClick (click)="addCipher()" class="block primary" title="{{'addItem' | i18n}}">
|
<button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()"
|
||||||
|
class="block primary" title="{{'addItem' | i18n}}">
|
||||||
<i class="fa fa-plus fa-lg"></i>
|
<i class="fa fa-plus fa-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,9 @@ import { CipherView } from 'jslib/models/view/cipherView';
|
|||||||
export class CiphersComponent {
|
export class CiphersComponent {
|
||||||
@Input() activeCipherId: string = null;
|
@Input() activeCipherId: string = null;
|
||||||
@Output() onCipherClicked = new EventEmitter<CipherView>();
|
@Output() onCipherClicked = new EventEmitter<CipherView>();
|
||||||
|
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
|
||||||
@Output() onAddCipher = new EventEmitter();
|
@Output() onAddCipher = new EventEmitter();
|
||||||
|
@Output() onAddCipherOptions = new EventEmitter();
|
||||||
|
|
||||||
loaded: boolean = false;
|
loaded: boolean = false;
|
||||||
ciphers: CipherView[] = [];
|
ciphers: CipherView[] = [];
|
||||||
@ -51,7 +53,15 @@ export class CiphersComponent {
|
|||||||
this.onCipherClicked.emit(cipher);
|
this.onCipherClicked.emit(cipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cipherRightClicked(cipher: CipherView) {
|
||||||
|
this.onCipherRightClicked.emit(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
addCipher() {
|
addCipher() {
|
||||||
this.onAddCipher.emit();
|
this.onAddCipher.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addCipherOptions() {
|
||||||
|
this.onAddCipherOptions.emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
<app-vault-ciphers id="items"
|
<app-vault-ciphers id="items"
|
||||||
[activeCipherId]="cipherId"
|
[activeCipherId]="cipherId"
|
||||||
(onCipherClicked)="viewCipher($event)"
|
(onCipherClicked)="viewCipher($event)"
|
||||||
(onAddCipher)="addCipher($event)">
|
(onCipherRightClicked)="viewCipherMenu($event)"
|
||||||
|
(onAddCipher)="addCipher($event)"
|
||||||
|
(onAddCipherOptions)="addCipherOptions($event)">
|
||||||
</app-vault-ciphers>
|
</app-vault-ciphers>
|
||||||
<app-vault-view id="details"
|
<app-vault-view id="details"
|
||||||
*ngIf="cipherId && action === 'view'"
|
*ngIf="cipherId && action === 'view'"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import * as template from './vault.component.html';
|
import * as template from './vault.component.html';
|
||||||
|
|
||||||
|
import { remote } from 'electron';
|
||||||
|
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
@ -38,6 +40,7 @@ import { FolderView } from 'jslib/models/view/folderView';
|
|||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||||
|
|
||||||
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
||||||
@ -69,8 +72,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private componentFactoryResolver: ComponentFactoryResolver, private i18nService: I18nService,
|
private componentFactoryResolver: ComponentFactoryResolver, private i18nService: I18nService,
|
||||||
private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef,
|
private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef,
|
||||||
private ngZone: NgZone, private syncService: SyncService, private analytics: Angulartics2,
|
private ngZone: NgZone, private syncService: SyncService, private analytics: Angulartics2,
|
||||||
private toasterService: ToasterService, private messagingService: MessagingService) {
|
private toasterService: ToasterService, private messagingService: MessagingService,
|
||||||
}
|
private platformUtilsService: PlatformUtilsService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||||
@ -205,6 +208,74 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.go();
|
this.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewCipherMenu(cipher: CipherView) {
|
||||||
|
const menu = new remote.Menu();
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('view'),
|
||||||
|
click: () => {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
this.viewCipher(cipher);
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('edit'),
|
||||||
|
click: () => {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
this.editCipher(cipher);
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
switch (cipher.type) {
|
||||||
|
case CipherType.Login:
|
||||||
|
if (cipher.login.canLaunch || cipher.login.username != null || cipher.login.password != null) {
|
||||||
|
menu.append(new remote.MenuItem({ type: 'separator' }));
|
||||||
|
}
|
||||||
|
if (cipher.login.canLaunch) {
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('launch'),
|
||||||
|
click: () => this.platformUtilsService.launchUri(cipher.login.uri),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (cipher.login.username != null) {
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('copyUsername'),
|
||||||
|
click: () => this.platformUtilsService.copyToClipboard(cipher.login.username),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (cipher.login.password != null) {
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('copyPassword'),
|
||||||
|
click: () => this.platformUtilsService.copyToClipboard(cipher.login.password),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CipherType.Card:
|
||||||
|
if (cipher.card.number != null || cipher.card.code != null) {
|
||||||
|
menu.append(new remote.MenuItem({ type: 'separator' }));
|
||||||
|
}
|
||||||
|
if (cipher.card.number != null) {
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('copyNumber'),
|
||||||
|
click: () => this.platformUtilsService.copyToClipboard(cipher.card.number),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (cipher.card.code != null) {
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('copySecurityCode'),
|
||||||
|
click: () => this.platformUtilsService.copyToClipboard(cipher.card.code),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
menu.popup(remote.getCurrentWindow());
|
||||||
|
}
|
||||||
|
|
||||||
editCipher(cipher: CipherView) {
|
editCipher(cipher: CipherView) {
|
||||||
if (this.action === 'edit' && this.cipherId === cipher.id) {
|
if (this.action === 'edit' && this.cipherId === cipher.id) {
|
||||||
return;
|
return;
|
||||||
@ -226,6 +297,27 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.go();
|
this.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addCipherOptions() {
|
||||||
|
const menu = new remote.Menu();
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('typeLogin'),
|
||||||
|
click: () => this.addCipherWithChangeDetection(CipherType.Login),
|
||||||
|
}));
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('typeCard'),
|
||||||
|
click: () => this.addCipherWithChangeDetection(CipherType.Card),
|
||||||
|
}));
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('typeIdentity'),
|
||||||
|
click: () => this.addCipherWithChangeDetection(CipherType.Identity),
|
||||||
|
}));
|
||||||
|
menu.append(new remote.MenuItem({
|
||||||
|
label: this.i18nService.t('typeSecureNote'),
|
||||||
|
click: () => this.addCipherWithChangeDetection(CipherType.SecureNote),
|
||||||
|
}));
|
||||||
|
menu.popup(remote.getCurrentWindow());
|
||||||
|
}
|
||||||
|
|
||||||
async savedCipher(cipher: CipherView) {
|
async savedCipher(cipher: CipherView) {
|
||||||
this.cipherId = cipher.id;
|
this.cipherId = cipher.id;
|
||||||
this.action = 'view';
|
this.action = 'view';
|
||||||
@ -394,4 +486,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
const url = this.router.createUrlTree(['vault'], { queryParams: queryParams }).toString();
|
const url = this.router.createUrlTree(['vault'], { queryParams: queryParams }).toString();
|
||||||
this.location.go(url);
|
this.location.go(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addCipherWithChangeDetection(type: CipherType = null) {
|
||||||
|
this.ngZone.run(async () => {
|
||||||
|
this.addCipher(type);
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
{{cipher.login.username}}
|
{{cipher.login.username}}
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
|
<a class="row-btn" href="#" appStopClick title="{{'copyUsername' | i18n}}"
|
||||||
(click)="copy(cipher.login.username, 'Username')">
|
(click)="copy(cipher.login.username, 'Username')">
|
||||||
<i class="fa fa-lg fa-clipboard"></i>
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -52,7 +52,7 @@
|
|||||||
<i class="fa fa-lg"
|
<i class="fa fa-lg"
|
||||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||||
</a>
|
</a>
|
||||||
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
|
<a class="row-btn" href="#" appStopClick title="{{'copyPassword' | i18n}}"
|
||||||
(click)="copy(cipher.login.password, 'Password')">
|
(click)="copy(cipher.login.password, 'Password')">
|
||||||
<i class="fa fa-lg fa-clipboard"></i>
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -94,7 +94,7 @@
|
|||||||
{{cipher.card.number}}
|
{{cipher.card.number}}
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
|
<a class="row-btn" href="#" appStopClick title="{{'copyNumber' | i18n}}"
|
||||||
(click)="copy(cipher.card.number, 'Number')">
|
(click)="copy(cipher.card.number, 'Number')">
|
||||||
<i class="fa fa-lg fa-clipboard"></i>
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -114,7 +114,7 @@
|
|||||||
{{cipher.card.code}}
|
{{cipher.card.code}}
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<a class="row-btn" href="#" appStopClick title="{{'copyValue' | i18n}}"
|
<a class="row-btn" href="#" appStopClick title="{{'copySecurityCode' | i18n}}"
|
||||||
(click)="copy(cipher.card.code, 'Security Code')">
|
(click)="copy(cipher.card.code, 'Security Code')">
|
||||||
<i class="fa fa-lg fa-clipboard"></i>
|
<i class="fa fa-lg fa-clipboard"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -78,7 +78,8 @@
|
|||||||
"message": "Launch"
|
"message": "Launch"
|
||||||
},
|
},
|
||||||
"copyValue": {
|
"copyValue": {
|
||||||
"message": "Copy Value"
|
"message": "Copy Value",
|
||||||
|
"description": "Copy value to clipboard"
|
||||||
},
|
},
|
||||||
"toggleVisibility": {
|
"toggleVisibility": {
|
||||||
"message": "Toggle Visibility"
|
"message": "Toggle Visibility"
|
||||||
@ -802,5 +803,16 @@
|
|||||||
},
|
},
|
||||||
"unknown": {
|
"unknown": {
|
||||||
"message": "Unknown"
|
"message": "Unknown"
|
||||||
|
},
|
||||||
|
"copyUsername": {
|
||||||
|
"message": "Copy Username"
|
||||||
|
},
|
||||||
|
"copyNumber": {
|
||||||
|
"message": "Copy Number",
|
||||||
|
"description": "Copy credit card number"
|
||||||
|
},
|
||||||
|
"copySecurityCode": {
|
||||||
|
"message": "Copy Security Code",
|
||||||
|
"description": "Copy credit card security code (CVV)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import { autoUpdater } from 'electron-updater';
|
|||||||
|
|
||||||
import { Main } from '../main';
|
import { Main } from '../main';
|
||||||
import {
|
import {
|
||||||
isDev,
|
|
||||||
isAppImage,
|
isAppImage,
|
||||||
|
isDev,
|
||||||
} from '../scripts/utils';
|
} from '../scripts/utils';
|
||||||
|
|
||||||
const UpdaterCheckInitalDelay = 5 * 1000; // 5 seconds
|
const UpdaterCheckInitalDelay = 5 * 1000; // 5 seconds
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import { remote, shell } from 'electron';
|
import {
|
||||||
|
clipboard,
|
||||||
|
remote,
|
||||||
|
shell,
|
||||||
|
} from 'electron';
|
||||||
|
|
||||||
import { isDev } from '../scripts/utils';
|
import { isDev } from '../scripts/utils';
|
||||||
|
|
||||||
@ -149,4 +153,9 @@ export class DesktopPlatformUtilsService implements PlatformUtilsService {
|
|||||||
isDev(): boolean {
|
isDev(): boolean {
|
||||||
return isDev();
|
return isDev();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyToClipboard(text: string, options?: any): void {
|
||||||
|
const type = options ? options.type : null;
|
||||||
|
clipboard.writeText(text, type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user