diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index da45b2756be..aa171074f5f 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2111,25 +2111,49 @@ } } }, - "tryAutofillPageLoad": { - "message": "Try auto-fill on page load?" - }, - "tryAutofill": { + "howToAutofill": { "message": "How to auto-fill" }, - "autofillPageLoadInfo": { - "message": "Login forms will automatically fill in matching credentials if you turn on auto-fill on page load." - }, "autofillSelectInfo": { - "message": "Select an item from this page to auto-fill the active tab's form." + "message": "Select an item from this page or use the shortcut: $COMMAND$. You can also try auto-fill on page load.", + "placeholders": { + "command": { + "content": "$1", + "example": "CTRL+Shift+L" + } + } }, - "autofillTurnedOn": { - "message": "Auto-fill on page load turned on" + "autofillSelectInfoNoCommand": { + "message": "Select an item from this page or set a shortcut in settings. You can also try auto-fill on page load." }, - "turnOn": { - "message": "Turn on" + "gotIt": { + "message": "Got it" }, - "notNow": { - "message": "Not now" + "autofillSettings": { + "message": "Auto-fill settings" + }, + "autofillShortcut": { + "message": "Auto-fill keyboard shortcut" + }, + "autofillShortcutNotSet": { + "message": "The auto-fill shortcut is not set. Change this in the browser's settings." + }, + "autofillShortcutText": { + "message": "The auto-fill shortcut is: $COMMAND$. Change this in the browser's settings.", + "placeholders": { + "command": { + "content": "$1", + "example": "CTRL+Shift+L" + } + } + }, + "autofillShortcutTextSafari": { + "message": "Default auto-fill shortcut: $COMMAND$.", + "placeholders": { + "command": { + "content": "$1", + "example": "CTRL+Shift+L" + } + } } } diff --git a/apps/browser/src/popup/scss/box.scss b/apps/browser/src/popup/scss/box.scss index ef13a7f5455..c026c780889 100644 --- a/apps/browser/src/popup/scss/box.scss +++ b/apps/browser/src/popup/scss/box.scss @@ -354,6 +354,7 @@ &.box-content-row-flex, .box-content-row-flex, &.box-content-row-checkbox, + &.box-content-row-link, &.box-content-row-input, &.box-content-row-slider, &.box-content-row-multi { @@ -398,6 +399,7 @@ } &.box-content-row-checkbox, + &.box-content-row-link, &.box-content-row-input, &.box-content-row-slider { padding-top: 10px; diff --git a/apps/browser/src/popup/scss/buttons.scss b/apps/browser/src/popup/scss/buttons.scss index 4c5141cf8f7..e9af536dc3d 100644 --- a/apps/browser/src/popup/scss/buttons.scss +++ b/apps/browser/src/popup/scss/buttons.scss @@ -28,7 +28,7 @@ &.callout-half { font-weight: bold; - max-width: 45%; + max-width: 50%; } &:hover:not([disabled]) { diff --git a/apps/browser/src/popup/settings/autofill.component.html b/apps/browser/src/popup/settings/autofill.component.html index c3a76e145e7..a3ff1936711 100644 --- a/apps/browser/src/popup/settings/autofill.component.html +++ b/apps/browser/src/popup/settings/autofill.component.html @@ -72,4 +72,19 @@ {{ "defaultUriMatchDetectionDesc" | i18n }} +
+
+ +
+ +
diff --git a/apps/browser/src/popup/settings/autofill.component.ts b/apps/browser/src/popup/settings/autofill.component.ts index 7c949921b87..dfe5925e2e7 100644 --- a/apps/browser/src/popup/settings/autofill.component.ts +++ b/apps/browser/src/popup/settings/autofill.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { UriMatchType } from "@bitwarden/common/enums/uriMatchType"; @@ -16,8 +17,13 @@ export class AutofillComponent implements OnInit { autoFillOnPageLoadOptions: any[]; defaultUriMatch = UriMatchType.Domain; uriMatchOptions: any[]; + autofillKeyboardHelperText: string; - constructor(private stateService: StateService, i18nService: I18nService) { + constructor( + private stateService: StateService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService + ) { this.autoFillOnPageLoadOptions = [ { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, @@ -40,6 +46,9 @@ export class AutofillComponent implements OnInit { const defaultUriMatch = await this.stateService.getDefaultUriMatch(); this.defaultUriMatch = defaultUriMatch == null ? UriMatchType.Domain : defaultUriMatch; + + const command = await this.platformUtilsService.getAutofillKeyboardShortcut(); + await this.setAutofillKeyboardHelperText(command); } async updateAutoFillOnPageLoad() { @@ -57,4 +66,26 @@ export class AutofillComponent implements OnInit { AboutAutofill() { BrowserApi.createNewTab("https://bitwarden.com/help/auto-fill-browser/"); } + + private async setAutofillKeyboardHelperText(command: string) { + if (command) { + this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutText", command); + } else { + this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutNotSet"); + } + } + + async commandSettings() { + if (this.platformUtilsService.isChrome()) { + BrowserApi.createNewTab("chrome://extensions/shortcuts"); + } else if (this.platformUtilsService.isOpera()) { + BrowserApi.createNewTab("opera://extensions/shortcuts"); + } else if (this.platformUtilsService.isEdge()) { + BrowserApi.createNewTab("edge://extensions/shortcuts"); + } else if (this.platformUtilsService.isVivaldi()) { + BrowserApi.createNewTab("vivaldi://extensions/shortcuts"); + } else { + BrowserApi.createNewTab("https://bitwarden.com/help/keyboard-shortcuts"); + } + } } diff --git a/apps/browser/src/services/browserPlatformUtils.service.ts b/apps/browser/src/services/browserPlatformUtils.service.ts index 3d33fa174ec..2f4e2329968 100644 --- a/apps/browser/src/services/browserPlatformUtils.service.ts +++ b/apps/browser/src/services/browserPlatformUtils.service.ts @@ -377,4 +377,31 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService supportsSecureStorage(): boolean { return false; } + + async getAutofillKeyboardShortcut(): Promise { + let autofillCommand: string; + // You can not change the command in Safari or obtain it programmatically + if (this.isSafari()) { + autofillCommand = "Cmd+Shift+L"; + } else if (this.isFirefox()) { + autofillCommand = (await browser.commands.getAll()).find( + (c) => c.name === "autofill_login" + ).shortcut; + // Firefox is returing Ctrl instead of Cmd for the modifier key on macOS if + // the command is the default one set on installation. + if ( + (await browser.runtime.getPlatformInfo()).os === "mac" && + autofillCommand === "Ctrl+Shift+L" + ) { + autofillCommand = "Cmd+Shift+L"; + } + } else { + await new Promise((resolve) => + chrome.commands.getAll((c) => + resolve((autofillCommand = c.find((c) => c.name === "autofill_login").shortcut)) + ) + ); + } + return autofillCommand; + } } diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.html b/apps/browser/src/vault/popup/components/vault/current-tab.component.html index f2cc0305720..bec06919433 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.html +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.html @@ -36,27 +36,20 @@ - -

{{ "autofillPageLoadInfo" | i18n }}

+ +

{{ autofillCalloutText }}

-
- -

{{ "autofillSelectInfo" | i18n }}

-

{{ "typeLogins" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts index 177c3124bbc..1c5c83c0b8c 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts @@ -42,8 +42,8 @@ export class CurrentTabComponent implements OnInit, OnDestroy { loaded = false; isLoading = false; showOrganizations = false; - showTryAutofillOnPageLoad = false; - showSelectAutofillCallout = false; + showHowToAutofill = false; + autofillCalloutText: string; protected search$ = new Subject(); private destroy$ = new Subject(); @@ -103,10 +103,12 @@ export class CurrentTabComponent implements OnInit, OnDestroy { if (!this.syncService.syncInProgress) { await this.load(); + await this.setCallout(); } else { this.loadedTimeout = window.setTimeout(async () => { if (!this.isLoading) { await this.load(); + await this.setCallout(); } }, 5000); } @@ -114,11 +116,6 @@ export class CurrentTabComponent implements OnInit, OnDestroy { this.search$ .pipe(debounceTime(500), takeUntil(this.destroy$)) .subscribe(() => this.searchVault()); - - this.showTryAutofillOnPageLoad = - this.loginCiphers.length > 0 && - !(await this.stateService.getEnableAutoFillOnPageLoad()) && - !(await this.stateService.getDismissedAutofillCallout()); } ngOnDestroy() { @@ -274,17 +271,32 @@ export class CurrentTabComponent implements OnInit, OnDestroy { this.isLoading = this.loaded = true; } - async setAutofillOnPageLoad() { - await this.stateService.setEnableAutoFillOnPageLoad(true); - this.platformUtilsService.showToast("success", null, this.i18nService.t("autofillTurnedOn")); - await this.fillCipher(this.loginCiphers[0], 3000); - await this.stateService.setDismissedAutofillCallout(true); - this.showTryAutofillOnPageLoad = false; + async goToSettings() { + this.router.navigate(["autofill"]); } - async notNow() { + async dismissCallout() { await this.stateService.setDismissedAutofillCallout(true); - this.showTryAutofillOnPageLoad = false; - this.showSelectAutofillCallout = true; + this.showHowToAutofill = false; + } + + private async setCallout() { + this.showHowToAutofill = + this.loginCiphers.length > 0 && + !(await this.stateService.getEnableAutoFillOnPageLoad()) && + !(await this.stateService.getDismissedAutofillCallout()); + + if (this.showHowToAutofill) { + const autofillCommand = await this.platformUtilsService.getAutofillKeyboardShortcut(); + await this.setAutofillCalloutText(autofillCommand); + } + } + + private setAutofillCalloutText(command: string) { + if (command) { + this.autofillCalloutText = this.i18nService.t("autofillSelectInfo", command); + } else { + this.autofillCalloutText = this.i18nService.t("autofillSelectInfoNoCommand"); + } } } diff --git a/apps/cli/src/services/cli-platform-utils.service.ts b/apps/cli/src/services/cli-platform-utils.service.ts index 9a10cf62c22..ed2e585774c 100644 --- a/apps/cli/src/services/cli-platform-utils.service.ts +++ b/apps/cli/src/services/cli-platform-utils.service.ts @@ -150,4 +150,8 @@ export class CliPlatformUtilsService implements PlatformUtilsService { supportsSecureStorage(): boolean { return false; } + + getAutofillKeyboardShortcut(): Promise { + return null; + } } diff --git a/apps/desktop/src/services/electron-platform-utils.service.ts b/apps/desktop/src/services/electron-platform-utils.service.ts index 8af865d8452..7a5b57d895f 100644 --- a/apps/desktop/src/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/services/electron-platform-utils.service.ts @@ -184,4 +184,8 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { supportsSecureStorage(): boolean { return true; } + + getAutofillKeyboardShortcut(): Promise { + return null; + } } diff --git a/apps/web/src/app/core/web-platform-utils.service.ts b/apps/web/src/app/core/web-platform-utils.service.ts index 7f693200bc7..c8b95538da3 100644 --- a/apps/web/src/app/core/web-platform-utils.service.ts +++ b/apps/web/src/app/core/web-platform-utils.service.ts @@ -262,4 +262,8 @@ export class WebPlatformUtilsService implements PlatformUtilsService { supportsSecureStorage() { return false; } + + getAutofillKeyboardShortcut(): Promise { + return null; + } } diff --git a/libs/common/src/abstractions/platformUtils.service.ts b/libs/common/src/abstractions/platformUtils.service.ts index 296b8a2404c..f98d82e741d 100644 --- a/libs/common/src/abstractions/platformUtils.service.ts +++ b/libs/common/src/abstractions/platformUtils.service.ts @@ -44,4 +44,5 @@ export abstract class PlatformUtilsService { supportsBiometric: () => Promise; authenticateBiometric: () => Promise; supportsSecureStorage: () => boolean; + getAutofillKeyboardShortcut: () => Promise; }