[PM-37948] Add back missing desktop shortcuts (#20942)

This commit is contained in:
Bryan Cunningham 2026-06-02 09:45:39 -04:00 committed by GitHub
parent 6f4b7c2c36
commit 48c32fffe7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 132 additions and 7 deletions

View File

@ -450,6 +450,9 @@ export class AppComponent implements OnInit, OnDestroy {
case "newSecureNote":
this.routeToVault("add", CipherType.SecureNote);
break;
case "newSshKey":
this.routeToVault("add", CipherType.SshKey);
break;
default:
break;
case "newFolder":

View File

@ -1,4 +1,7 @@
<div class="vault tw-flex tw-flex-col tw-h-full tw-overflow-x-hidden">
<div
class="vault tw-flex tw-flex-col tw-h-full tw-overflow-x-hidden"
attr.aria-hidden="{{ showingModal }}"
>
<div class="tw-pt-6 tw-px-6">
<app-header>
<vault-new-cipher-menu

View File

@ -9,6 +9,7 @@ import {
NgZone,
OnDestroy,
OnInit,
signal,
} from "@angular/core";
import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
import { FormsModule } from "@angular/forms";
@ -49,13 +50,14 @@ import {
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventCollectionService } from "@bitwarden/common/dirt/event-logs";
import { EventCollectionService, EventType } from "@bitwarden/common/dirt/event-logs";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/platform/sync";
import { CipherId, OrganizationId, UserId, CollectionId } from "@bitwarden/common/types/guid";
@ -63,7 +65,8 @@ import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/ciphe
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherType, toCipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -172,6 +175,8 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
private vaultFilterService = inject(VaultFilterService);
private routedVaultFilterService = inject(RoutedVaultFilterService);
private vaultItemTransferService: VaultItemsTransferService = inject(VaultItemsTransferService);
private platformUtilsService = inject(PlatformUtilsService);
private totpService = inject(TotpService);
private destroyRef = inject(DestroyRef);
private cipherFormConfigService = inject(CipherFormConfigService);
@ -179,6 +184,9 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
protected activeFilter: VaultFilter = new VaultFilter();
protected cipherRepromptId: string | null = null;
protected showingModal = false;
protected readonly cipher = signal<CipherView | null>(null);
private activeUserId: UserId | null = null;
private passwordReprompted: boolean = false;
@ -320,6 +328,13 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
// Clear cipher selection on page load/reload to prevent flash of content
const currentParams = await firstValueFrom(this.route.queryParams);
const incomingAddType = toCipherType(currentParams.addType);
if (currentParams.action === "add" && incomingAddType) {
await this.addCipher(incomingAddType).catch(() => {});
}
// Clear cipher selection on page load/reload to prevent flash of content
if (currentParams.itemId || currentParams.cipherId) {
await this.router.navigate([], {
queryParams: { itemId: null, cipherId: null, action: null },
@ -330,11 +345,83 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
void this.ngZone.run(async () => {
if (message.command === "syncCompleted" && message.successfully) {
if (this.activeUserId) {
void this.vaultItemTransferService.enforceOrganizationDataOwnership(this.activeUserId);
let detectChanges = true;
try {
switch (message.command) {
case "newLogin":
await this.addCipher(CipherType.Login);
break;
case "newCard":
await this.addCipher(CipherType.Card);
break;
case "newIdentity":
await this.addCipher(CipherType.Identity);
break;
case "newSecureNote":
await this.addCipher(CipherType.SecureNote);
break;
case "newSshKey":
await this.addCipher(CipherType.SshKey);
break;
case "focusSearch":
(document.querySelector("bit-search input") as HTMLInputElement)?.select();
detectChanges = false;
break;
case "syncCompleted":
if (message.successfully) {
if (this.activeUserId) {
void this.vaultItemTransferService.enforceOrganizationDataOwnership(
this.activeUserId,
);
}
this.refresh();
}
break;
case "modalShown":
this.showingModal = true;
break;
case "modalClosed":
this.showingModal = false;
break;
case "copyUsername": {
const cipher = this.cipher();
if (cipher?.login?.username) {
this.copyValue(cipher, cipher.login.username, "username", "Username");
}
break;
}
case "copyPassword": {
const cipher = this.cipher();
if (cipher?.login?.password && cipher.viewPassword) {
this.copyValue(cipher, cipher.login.password, "password", "Password");
await this.eventCollectionService.collect(
EventType.Cipher_ClientCopiedPassword,
cipher.id,
);
}
break;
}
case "copyTotp": {
const cipher = this.cipher();
if (cipher?.login?.hasTotp && (cipher.organizationUseTotp || this.userHasPremium())) {
const value = await firstValueFrom(
this.totpService.getCode$(cipher.login.totp),
).catch((): any => null);
if (value) {
this.copyValue(cipher, value.code, "verificationCodeTotp", "TOTP");
}
}
break;
}
default:
detectChanges = false;
break;
}
this.refresh();
} catch {
// Ignore errors
}
if (detectChanges) {
this.changeDetectorRef.detectChanges();
}
});
});
@ -560,6 +647,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
if (await this.shouldReprompt(cipher)) {
return;
}
this.cipher.set(cipher);
const formConfig = await this.cipherFormConfigService.buildConfig(
cipher.edit ? "edit" : "partial-edit",
cipher.id as CipherId,
@ -590,6 +678,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
if (await this.shouldReprompt(cipher)) {
return;
}
this.cipher.set(cipher);
const formConfig = await this.cipherFormConfigService.buildConfig(
cipher.edit ? "edit" : "partial-edit",
cipher.id as CipherId,
@ -602,6 +691,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
if (await this.shouldReprompt(cipher)) {
return;
}
this.cipher.set(cipher);
const formConfig = await this.cipherFormConfigService.buildConfig(
"clone",
cipher.id as CipherId,
@ -897,6 +987,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
});
this.activeDrawerRef.closed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((result) => {
this.activeDrawerRef = undefined;
this.cipher.set(null);
void this.router.navigate([], {
queryParams: { action: null, itemId: null },
queryParamsHandling: "merge",
@ -908,6 +999,34 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
});
}
private copyValue(cipher: CipherView, value: string, labelI18nKey: string, aType: string) {
this.functionWithChangeDetection(() => {
(async () => {
if (
cipher.reprompt !== CipherRepromptType.None &&
this.passwordRepromptService.protectedFields().includes(aType) &&
!(await this.passwordReprompt(cipher))
) {
return;
}
this.platformUtilsService.copyToClipboard(value);
this.toastService.showToast({
variant: "info",
title: undefined,
message: this.i18nService.t("valueCopied", this.i18nService.t(labelI18nKey)),
});
this.messagingService.send("minimizeOnCopy");
})().catch(() => {});
});
}
private functionWithChangeDetection(func: () => void) {
this.ngZone.run(() => {
func();
this.changeDetectorRef.detectChanges();
});
}
/** Prompts the user for their master password if the cipher has reprompt enabled. */
private async passwordReprompt(cipher: CipherView) {
if (cipher.reprompt === CipherRepromptType.None) {