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">
|
||||
<div class="list" *ngIf="searchedCiphers.length > 0">
|
||||
<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>
|
||||
<span class="text">
|
||||
{{c.name}}
|
||||
|
@ -32,7 +33,8 @@
|
|||
</ng-container>
|
||||
</div>
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,9 @@ import { CipherView } from 'jslib/models/view/cipherView';
|
|||
export class CiphersComponent {
|
||||
@Input() activeCipherId: string = null;
|
||||
@Output() onCipherClicked = new EventEmitter<CipherView>();
|
||||
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
|
||||
@Output() onAddCipher = new EventEmitter();
|
||||
@Output() onAddCipherOptions = new EventEmitter();
|
||||
|
||||
loaded: boolean = false;
|
||||
ciphers: CipherView[] = [];
|
||||
|
@ -51,7 +53,15 @@ export class CiphersComponent {
|
|||
this.onCipherClicked.emit(cipher);
|
||||
}
|
||||
|
||||
cipherRightClicked(cipher: CipherView) {
|
||||
this.onCipherRightClicked.emit(cipher);
|
||||
}
|
||||
|
||||
addCipher() {
|
||||
this.onAddCipher.emit();
|
||||
}
|
||||
|
||||
addCipherOptions() {
|
||||
this.onAddCipherOptions.emit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
<app-vault-ciphers id="items"
|
||||
[activeCipherId]="cipherId"
|
||||
(onCipherClicked)="viewCipher($event)"
|
||||
(onAddCipher)="addCipher($event)">
|
||||
(onCipherRightClicked)="viewCipherMenu($event)"
|
||||
(onAddCipher)="addCipher($event)"
|
||||
(onAddCipherOptions)="addCipherOptions($event)">
|
||||
</app-vault-ciphers>
|
||||
<app-vault-view id="details"
|
||||
*ngIf="cipherId && action === 'view'"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import * as template from './vault.component.html';
|
||||
|
||||
import { remote } from 'electron';
|
||||
|
||||
import { Location } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
|
@ -38,6 +40,7 @@ import { FolderView } from 'jslib/models/view/folderView';
|
|||
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
|
||||
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 broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef,
|
||||
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() {
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
|
@ -205,6 +208,74 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
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) {
|
||||
if (this.action === 'edit' && this.cipherId === cipher.id) {
|
||||
return;
|
||||
|
@ -226,6 +297,27 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
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) {
|
||||
this.cipherId = cipher.id;
|
||||
this.action = 'view';
|
||||
|
@ -394,4 +486,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
const url = this.router.createUrlTree(['vault'], { queryParams: queryParams }).toString();
|
||||
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}}
|
||||
</div>
|
||||
<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')">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
|
@ -52,7 +52,7 @@
|
|||
<i class="fa fa-lg"
|
||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||
</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')">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
|
@ -94,7 +94,7 @@
|
|||
{{cipher.card.number}}
|
||||
</div>
|
||||
<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')">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
|
@ -114,7 +114,7 @@
|
|||
{{cipher.card.code}}
|
||||
</div>
|
||||
<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')">
|
||||
<i class="fa fa-lg fa-clipboard"></i>
|
||||
</a>
|
||||
|
|
|
@ -78,7 +78,8 @@
|
|||
"message": "Launch"
|
||||
},
|
||||
"copyValue": {
|
||||
"message": "Copy Value"
|
||||
"message": "Copy Value",
|
||||
"description": "Copy value to clipboard"
|
||||
},
|
||||
"toggleVisibility": {
|
||||
"message": "Toggle Visibility"
|
||||
|
@ -802,5 +803,16 @@
|
|||
},
|
||||
"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 {
|
||||
isDev,
|
||||
isAppImage,
|
||||
isDev,
|
||||
} from '../scripts/utils';
|
||||
|
||||
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';
|
||||
|
||||
|
@ -149,4 +153,9 @@ export class DesktopPlatformUtilsService implements PlatformUtilsService {
|
|||
isDev(): boolean {
|
||||
return isDev();
|
||||
}
|
||||
|
||||
copyToClipboard(text: string, options?: any): void {
|
||||
const type = options ? options.type : null;
|
||||
clipboard.writeText(text, type);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue