mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 22:44:11 +00:00
Merge branch 'main' into ps/extension-refresh
This commit is contained in:
@@ -293,6 +293,12 @@
|
||||
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/send-ui/*", "src/**/*"] }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["libs/tools/card/src/**/*.ts"],
|
||||
"rules": {
|
||||
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/tools-card/*", "src/**/*"] }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["libs/vault/src/**/*.ts"],
|
||||
"rules": {
|
||||
|
||||
1
.github/whitelist-capital-letters.txt
vendored
1
.github/whitelist-capital-letters.txt
vendored
@@ -14,6 +14,7 @@
|
||||
./libs/tools/README.md
|
||||
./libs/tools/export/vault-export/README.md
|
||||
./libs/tools/send/README.md
|
||||
./libs/tools/card/README.md
|
||||
./libs/vault/README.md
|
||||
./README.md
|
||||
./LICENSE_BITWARDEN.txt
|
||||
|
||||
@@ -19,6 +19,8 @@ const config: StorybookConfig = {
|
||||
"../apps/browser/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||
"../bitwarden_license/bit-web/src/**/*.mdx",
|
||||
"../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||
"../libs/tools/card/src/**/*.mdx",
|
||||
"../libs/tools/card/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||
],
|
||||
addons: [
|
||||
getAbsolutePath("@storybook/addon-links"),
|
||||
|
||||
@@ -1408,6 +1408,12 @@
|
||||
"showInlineMenuLabel": {
|
||||
"message": "Show autofill suggestions on form fields"
|
||||
},
|
||||
"showInlineMenuIdentitiesLabel": {
|
||||
"message": "Display identities as suggestions"
|
||||
},
|
||||
"showInlineMenuCardsLabel": {
|
||||
"message": "Display cards as suggestions"
|
||||
},
|
||||
"showInlineMenuOnIconSelectionLabel": {
|
||||
"message": "Display suggestions when icon is selected"
|
||||
},
|
||||
@@ -2647,6 +2653,15 @@
|
||||
"message": "Your organization requires you to set a master password.",
|
||||
"description": "Used as a card title description on the set password page to explain why the user is there"
|
||||
},
|
||||
"cardMetrics": {
|
||||
"message": "out of $TOTAL$",
|
||||
"placeholders": {
|
||||
"total": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"verificationRequired": {
|
||||
"message": "Verification required",
|
||||
"description": "Default title for the user verification dialog."
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
AnonLayoutWrapperDataService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Icon, IconModule } from "@bitwarden/components";
|
||||
import { Icon, IconModule, Translation } from "@bitwarden/components";
|
||||
|
||||
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
@@ -90,11 +90,11 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (firstChildRouteData["pageTitle"] !== undefined) {
|
||||
this.pageTitle = this.i18nService.t(firstChildRouteData["pageTitle"]);
|
||||
this.pageTitle = this.handleStringOrTranslation(firstChildRouteData["pageTitle"]);
|
||||
}
|
||||
|
||||
if (firstChildRouteData["pageSubtitle"] !== undefined) {
|
||||
this.pageSubtitle = this.i18nService.t(firstChildRouteData["pageSubtitle"]);
|
||||
this.pageSubtitle = this.handleStringOrTranslation(firstChildRouteData["pageSubtitle"]);
|
||||
}
|
||||
|
||||
if (firstChildRouteData["pageIcon"] !== undefined) {
|
||||
@@ -132,19 +132,11 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (data.pageTitle) {
|
||||
this.pageTitle = this.i18nService.t(data.pageTitle);
|
||||
this.pageTitle = this.handleStringOrTranslation(data.pageTitle);
|
||||
}
|
||||
|
||||
if (data.pageSubtitle) {
|
||||
// If you pass just a string, we translate it by default
|
||||
if (typeof data.pageSubtitle === "string") {
|
||||
this.pageSubtitle = this.i18nService.t(data.pageSubtitle);
|
||||
} else {
|
||||
// if you pass an object, you can specify if you want to translate it or not
|
||||
this.pageSubtitle = data.pageSubtitle.translate
|
||||
? this.i18nService.t(data.pageSubtitle.subtitle)
|
||||
: data.pageSubtitle.subtitle;
|
||||
}
|
||||
this.pageSubtitle = this.handleStringOrTranslation(data.pageSubtitle);
|
||||
}
|
||||
|
||||
if (data.pageIcon) {
|
||||
@@ -168,6 +160,16 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private handleStringOrTranslation(value: string | Translation): string {
|
||||
if (typeof value === "string") {
|
||||
// If it's a string, return it as is
|
||||
return value;
|
||||
}
|
||||
|
||||
// If it's a Translation object, translate it
|
||||
return this.i18nService.t(value.key, ...(value.placeholders ?? []));
|
||||
}
|
||||
|
||||
private resetPageData() {
|
||||
this.pageTitle = null;
|
||||
this.pageSubtitle = null;
|
||||
|
||||
@@ -221,8 +221,12 @@ export const DefaultContentExample: Story = {
|
||||
|
||||
// Dynamic Content Example
|
||||
const initialData: ExtensionAnonLayoutWrapperData = {
|
||||
pageTitle: "setAStrongPassword",
|
||||
pageSubtitle: "finishCreatingYourAccountBySettingAPassword",
|
||||
pageTitle: {
|
||||
key: "setAStrongPassword",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "finishCreatingYourAccountBySettingAPassword",
|
||||
},
|
||||
pageIcon: LockIcon,
|
||||
showAcctSwitcher: true,
|
||||
showBackButton: true,
|
||||
@@ -230,8 +234,12 @@ const initialData: ExtensionAnonLayoutWrapperData = {
|
||||
};
|
||||
|
||||
const changedData: ExtensionAnonLayoutWrapperData = {
|
||||
pageTitle: "enterpriseSingleSignOn",
|
||||
pageSubtitle: "checkYourEmail",
|
||||
pageTitle: {
|
||||
key: "enterpriseSingleSignOn",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "checkYourEmail",
|
||||
},
|
||||
pageIcon: RegistrationCheckEmailIcon,
|
||||
showAcctSwitcher: false,
|
||||
showBackButton: false,
|
||||
|
||||
@@ -188,6 +188,8 @@ export type OverlayBackgroundExtensionMessageHandlers = {
|
||||
updateIsFieldCurrentlyFilling: ({ message }: BackgroundMessageParam) => void;
|
||||
checkIsFieldCurrentlyFilling: () => boolean;
|
||||
getAutofillInlineMenuVisibility: () => void;
|
||||
getInlineMenuCardsVisibility: () => void;
|
||||
getInlineMenuIdentitiesVisibility: () => void;
|
||||
openAutofillInlineMenu: () => void;
|
||||
closeAutofillInlineMenu: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
checkAutofillInlineMenuFocused: ({ sender }: BackgroundSenderParam) => void;
|
||||
|
||||
@@ -132,6 +132,8 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
updateIsFieldCurrentlyFilling: ({ message }) => this.updateIsFieldCurrentlyFilling(message),
|
||||
checkIsFieldCurrentlyFilling: () => this.checkIsFieldCurrentlyFilling(),
|
||||
getAutofillInlineMenuVisibility: () => this.getInlineMenuVisibility(),
|
||||
getInlineMenuCardsVisibility: () => this.getInlineMenuCardsVisibility(),
|
||||
getInlineMenuIdentitiesVisibility: () => this.getInlineMenuIdentitiesVisibility(),
|
||||
openAutofillInlineMenu: () => this.openInlineMenu(false),
|
||||
closeAutofillInlineMenu: ({ message, sender }) => this.closeInlineMenu(sender, message),
|
||||
checkAutofillInlineMenuFocused: ({ sender }) => this.checkInlineMenuFocused(sender),
|
||||
@@ -365,7 +367,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.cardAndIdentityCiphers.size) {
|
||||
if (!this.cardAndIdentityCiphers?.size) {
|
||||
this.cardAndIdentityCiphers = null;
|
||||
}
|
||||
|
||||
@@ -1483,6 +1485,20 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
return await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inline menu's visibility setting for Cards from the settings service.
|
||||
*/
|
||||
private async getInlineMenuCardsVisibility(): Promise<boolean> {
|
||||
return await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inline menu's visibility setting for Identities from the settings service.
|
||||
*/
|
||||
private async getInlineMenuIdentitiesVisibility(): Promise<boolean> {
|
||||
return await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's authentication status from the auth service.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
import { AutofillOverlayElementType } from "../../enums/autofill-overlay.enum";
|
||||
@@ -23,7 +24,7 @@ export type AutofillExtensionMessage = {
|
||||
data?: {
|
||||
direction?: "previous" | "next" | "current";
|
||||
forceCloseInlineMenu?: boolean;
|
||||
inlineMenuVisibility?: number;
|
||||
newSettingValue?: InlineMenuVisibilitySetting;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
let autofillInlineMenuContentService: AutofillInlineMenuContentService;
|
||||
let autofillInit: AutofillInit;
|
||||
let sendExtensionMessageSpy: jest.SpyInstance;
|
||||
let observeBodyMutationsSpy: jest.SpyInstance;
|
||||
let observeContainerMutationsSpy: jest.SpyInstance;
|
||||
const waitForIdleCallback = () =>
|
||||
new Promise((resolve) => globalThis.requestIdleCallback(resolve));
|
||||
|
||||
@@ -25,8 +25,8 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
autofillInlineMenuContentService = new AutofillInlineMenuContentService();
|
||||
autofillInit = new AutofillInit(domQueryService, null, autofillInlineMenuContentService);
|
||||
autofillInit.init();
|
||||
observeBodyMutationsSpy = jest.spyOn(
|
||||
autofillInlineMenuContentService["bodyElementMutationObserver"] as any,
|
||||
observeContainerMutationsSpy = jest.spyOn(
|
||||
autofillInlineMenuContentService["containerElementMutationObserver"] as any,
|
||||
"observe",
|
||||
);
|
||||
sendExtensionMessageSpy = jest.spyOn(
|
||||
@@ -51,7 +51,7 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
describe("extension message handlers", () => {
|
||||
describe("closeAutofillInlineMenu message handler", () => {
|
||||
beforeEach(() => {
|
||||
observeBodyMutationsSpy.mockImplementation();
|
||||
observeContainerMutationsSpy.mockImplementation();
|
||||
});
|
||||
|
||||
it("closes the inline menu button", async () => {
|
||||
@@ -87,9 +87,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
});
|
||||
|
||||
it("closes both inline menu elements and removes the body element mutation observer", async () => {
|
||||
const unobserveBodyElementSpy = jest.spyOn(
|
||||
const unobserveContainerElementSpy = jest.spyOn(
|
||||
autofillInlineMenuContentService as any,
|
||||
"unobserveBodyElement",
|
||||
"unobserveContainerElement",
|
||||
);
|
||||
sendMockExtensionMessage({
|
||||
command: "appendAutofillInlineMenuToDom",
|
||||
@@ -104,7 +104,7 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
command: "closeAutofillInlineMenu",
|
||||
});
|
||||
|
||||
expect(unobserveBodyElementSpy).toHaveBeenCalled();
|
||||
expect(unobserveContainerElementSpy).toHaveBeenCalled();
|
||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayElementClosed", {
|
||||
overlayElement: AutofillOverlayElement.Button,
|
||||
});
|
||||
@@ -127,7 +127,7 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
.spyOn(autofillInlineMenuContentService as any, "isInlineMenuListVisible")
|
||||
.mockResolvedValue(true);
|
||||
jest.spyOn(globalThis.document.body, "appendChild");
|
||||
observeBodyMutationsSpy.mockImplementation();
|
||||
observeContainerMutationsSpy.mockImplementation();
|
||||
});
|
||||
|
||||
describe("creating the inline menu button", () => {
|
||||
@@ -279,7 +279,8 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleBodyElementMutationObserverUpdate", () => {
|
||||
describe("handleContainerElementMutationObserverUpdate", () => {
|
||||
let mockMutationRecord: MockProxy<MutationRecord>;
|
||||
let buttonElement: HTMLElement;
|
||||
let listElement: HTMLElement;
|
||||
let isInlineMenuListVisibleSpy: jest.SpyInstance;
|
||||
@@ -289,6 +290,7 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
<div class="overlay-button"></div>
|
||||
<div class="overlay-list"></div>
|
||||
`;
|
||||
mockMutationRecord = mock<MutationRecord>({ target: globalThis.document.body } as any);
|
||||
buttonElement = document.querySelector(".overlay-button") as HTMLElement;
|
||||
listElement = document.querySelector(".overlay-list") as HTMLElement;
|
||||
autofillInlineMenuContentService["buttonElement"] = buttonElement;
|
||||
@@ -309,7 +311,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
autofillInlineMenuContentService["buttonElement"] = undefined;
|
||||
autofillInlineMenuContentService["listElement"] = undefined;
|
||||
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
@@ -323,7 +327,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
)
|
||||
.mockReturnValue(true);
|
||||
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
@@ -332,14 +338,18 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
it("skips re-arranging the DOM elements if the last child of the body is non-existent", async () => {
|
||||
document.body.innerHTML = "";
|
||||
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips re-arranging the DOM elements if the last child of the body is the overlay list and the second to last child of the body is the overlay button", async () => {
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
@@ -349,7 +359,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
listElement.remove();
|
||||
isInlineMenuListVisibleSpy.mockResolvedValue(false);
|
||||
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||
@@ -359,7 +371,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
const injectedElement = document.createElement("div");
|
||||
document.body.insertBefore(injectedElement, listElement);
|
||||
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||
@@ -371,7 +385,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
it("positions the overlay button before the overlay list if the elements have inserted in incorrect order", async () => {
|
||||
document.body.appendChild(buttonElement);
|
||||
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||
@@ -384,7 +400,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
const injectedElement = document.createElement("div");
|
||||
document.body.appendChild(injectedElement);
|
||||
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||
@@ -409,7 +427,9 @@ describe("AutofillInlineMenuContentService", () => {
|
||||
1000,
|
||||
);
|
||||
|
||||
await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||
autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([
|
||||
mockMutationRecord,
|
||||
]);
|
||||
await waitForIdleCallback();
|
||||
|
||||
expect(persistentLastChild.style.getPropertyValue("z-index")).toBe("2147483646");
|
||||
|
||||
@@ -30,7 +30,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
private buttonElement: HTMLElement;
|
||||
private listElement: HTMLElement;
|
||||
private inlineMenuElementsMutationObserver: MutationObserver;
|
||||
private bodyElementMutationObserver: MutationObserver;
|
||||
private containerElementMutationObserver: MutationObserver;
|
||||
private mutationObserverIterations = 0;
|
||||
private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout;
|
||||
private handlePersistentLastChildOverrideTimeout: number | NodeJS.Timeout;
|
||||
@@ -102,7 +102,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
return;
|
||||
}
|
||||
|
||||
this.unobserveBodyElement();
|
||||
this.unobserveContainerElement();
|
||||
this.closeInlineMenuButton();
|
||||
this.closeInlineMenuList();
|
||||
};
|
||||
@@ -153,7 +153,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
}
|
||||
|
||||
if (!(await this.isInlineMenuButtonVisible())) {
|
||||
this.appendInlineMenuElementToBody(this.buttonElement);
|
||||
this.appendInlineMenuElementToDom(this.buttonElement);
|
||||
this.updateInlineMenuElementIsVisibleStatus(AutofillOverlayElement.Button, true);
|
||||
}
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
}
|
||||
|
||||
if (!(await this.isInlineMenuListVisible())) {
|
||||
this.appendInlineMenuElementToBody(this.listElement);
|
||||
this.appendInlineMenuElementToDom(this.listElement);
|
||||
this.updateInlineMenuElementIsVisibleStatus(AutofillOverlayElement.List, true);
|
||||
}
|
||||
}
|
||||
@@ -196,8 +196,15 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
*
|
||||
* @param element - The inline menu element to append to the body element.
|
||||
*/
|
||||
private appendInlineMenuElementToBody(element: HTMLElement) {
|
||||
this.observeBodyElement();
|
||||
private appendInlineMenuElementToDom(element: HTMLElement) {
|
||||
const parentDialogElement = globalThis.document.activeElement?.closest("dialog");
|
||||
if (parentDialogElement && parentDialogElement.open && parentDialogElement.matches(":modal")) {
|
||||
this.observeContainerElement(parentDialogElement);
|
||||
parentDialogElement.appendChild(element);
|
||||
return;
|
||||
}
|
||||
|
||||
this.observeContainerElement(globalThis.document.body);
|
||||
globalThis.document.body.appendChild(element);
|
||||
}
|
||||
|
||||
@@ -276,8 +283,8 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
this.handleInlineMenuElementMutationObserverUpdate,
|
||||
);
|
||||
|
||||
this.bodyElementMutationObserver = new MutationObserver(
|
||||
this.handleBodyElementMutationObserverUpdate,
|
||||
this.containerElementMutationObserver = new MutationObserver(
|
||||
this.handleContainerElementMutationObserverUpdate,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -306,19 +313,17 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a mutation observer for the body element. The mutation observer is used
|
||||
* to ensure that the inline menu elements are always present at the bottom of the
|
||||
* body element.
|
||||
* Sets up a mutation observer for the element which contains the inline menu.
|
||||
*/
|
||||
private observeBodyElement() {
|
||||
this.bodyElementMutationObserver?.observe(globalThis.document.body, { childList: true });
|
||||
private observeContainerElement(element: HTMLElement) {
|
||||
this.containerElementMutationObserver?.observe(element, { childList: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the mutation observer for the body element.
|
||||
* Disconnects the mutation observer for the element which contains the inline menu.
|
||||
*/
|
||||
private unobserveBodyElement() {
|
||||
this.bodyElementMutationObserver?.disconnect();
|
||||
private unobserveContainerElement() {
|
||||
this.containerElementMutationObserver?.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,11 +375,11 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mutation observer update for the body element. This method will
|
||||
* ensure that the inline menu elements are always present at the bottom of the
|
||||
* body element.
|
||||
* Handles the mutation observer update for the element that contains the inline menu.
|
||||
* This method will ensure that the inline menu elements are always present at the
|
||||
* bottom of the container.
|
||||
*/
|
||||
private handleBodyElementMutationObserverUpdate = () => {
|
||||
private handleContainerElementMutationObserverUpdate = (mutations: MutationRecord[]) => {
|
||||
if (
|
||||
(!this.buttonElement && !this.listElement) ||
|
||||
this.isTriggeringExcessiveMutationObserverIterations()
|
||||
@@ -382,15 +387,18 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
return;
|
||||
}
|
||||
|
||||
requestIdleCallbackPolyfill(this.processBodyElementMutation, { timeout: 500 });
|
||||
const containerElement = mutations[0].target as HTMLElement;
|
||||
requestIdleCallbackPolyfill(() => this.processContainerElementMutation(containerElement), {
|
||||
timeout: 500,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes the mutation of the body element. Will trigger when an
|
||||
* Processes the mutation of the element that contains the inline menu. Will trigger when an
|
||||
* idle moment in the execution of the main thread is detected.
|
||||
*/
|
||||
private processBodyElementMutation = async () => {
|
||||
const lastChild = globalThis.document.body.lastElementChild;
|
||||
private processContainerElementMutation = async (containerElement: HTMLElement) => {
|
||||
const lastChild = containerElement.lastElementChild;
|
||||
const secondToLastChild = lastChild?.previousElementSibling;
|
||||
const lastChildIsInlineMenuList = lastChild === this.listElement;
|
||||
const lastChildIsInlineMenuButton = lastChild === this.buttonElement;
|
||||
@@ -424,11 +432,11 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
|
||||
(lastChildIsInlineMenuList && !secondToLastChildIsInlineMenuButton) ||
|
||||
(lastChildIsInlineMenuButton && isInlineMenuListVisible)
|
||||
) {
|
||||
globalThis.document.body.insertBefore(this.buttonElement, this.listElement);
|
||||
containerElement.insertBefore(this.buttonElement, this.listElement);
|
||||
return;
|
||||
}
|
||||
|
||||
globalThis.document.body.insertBefore(lastChild, this.buttonElement);
|
||||
containerElement.insertBefore(lastChild, this.buttonElement);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box tw-mb-5" *ngIf="inlineMenuPositioningImprovementsEnabled && inlineMenuIsEnabled">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="show-inline-menu-identities" class="!tw-mr-0">{{
|
||||
"showInlineMenuIdentitiesLabel" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="show-inline-menu-identities"
|
||||
type="checkbox"
|
||||
(change)="updateShowInlineMenuIdentities()"
|
||||
[(ngModel)]="showInlineMenuIdentities"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="show-inline-menu-cards" class="!tw-mr-0">{{
|
||||
"showInlineMenuCardsLabel" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="show-inline-menu-cards"
|
||||
type="checkbox"
|
||||
(change)="updateShowInlineMenuCards()"
|
||||
[(ngModel)]="showInlineMenuCards"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content" *ngIf="canOverrideBrowserAutofillSetting">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
InlineMenuVisibilitySetting,
|
||||
ClearClipboardDelaySetting,
|
||||
} from "@bitwarden/common/autofill/types";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import {
|
||||
UriMatchStrategy,
|
||||
UriMatchStrategySetting,
|
||||
} from "@bitwarden/common/models/domain/domain-service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -20,7 +22,6 @@ import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
import { AutofillService } from "../../services/abstractions/autofill.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-autofill-v1",
|
||||
@@ -32,6 +33,10 @@ export class AutofillV1Component implements OnInit {
|
||||
protected autoFillOverlayVisibility: InlineMenuVisibilitySetting;
|
||||
protected autoFillOverlayVisibilityOptions: any[];
|
||||
protected disablePasswordManagerLink: string;
|
||||
protected inlineMenuPositioningImprovementsEnabled: boolean = false;
|
||||
protected showInlineMenuIdentities: boolean = true;
|
||||
protected showInlineMenuCards: boolean = true;
|
||||
inlineMenuIsEnabled: boolean = false;
|
||||
enableAutoFillOnPageLoad = false;
|
||||
autoFillOnPageLoadDefault = false;
|
||||
autoFillOnPageLoadOptions: any[];
|
||||
@@ -50,7 +55,7 @@ export class AutofillV1Component implements OnInit {
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
private autofillService: AutofillService,
|
||||
private configService: ConfigService,
|
||||
private dialogService: DialogService,
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private messagingService: MessagingService,
|
||||
@@ -109,6 +114,20 @@ export class AutofillV1Component implements OnInit {
|
||||
this.autofillSettingsService.inlineMenuVisibility$,
|
||||
);
|
||||
|
||||
this.inlineMenuPositioningImprovementsEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.InlineMenuPositioningImprovements,
|
||||
);
|
||||
|
||||
this.inlineMenuIsEnabled = this.isInlineMenuEnabled();
|
||||
|
||||
this.showInlineMenuIdentities =
|
||||
this.inlineMenuPositioningImprovementsEnabled &&
|
||||
(await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$));
|
||||
|
||||
this.showInlineMenuCards =
|
||||
this.inlineMenuPositioningImprovementsEnabled &&
|
||||
(await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$));
|
||||
|
||||
this.enableAutoFillOnPageLoad = await firstValueFrom(
|
||||
this.autofillSettingsService.autofillOnPageLoad$,
|
||||
);
|
||||
@@ -140,9 +159,18 @@ export class AutofillV1Component implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
isInlineMenuEnabled() {
|
||||
return (
|
||||
this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnFieldFocus ||
|
||||
this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnButtonClick
|
||||
);
|
||||
}
|
||||
|
||||
async updateAutoFillOverlayVisibility() {
|
||||
await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility);
|
||||
await this.requestPrivacyPermission();
|
||||
|
||||
this.inlineMenuIsEnabled = this.isInlineMenuEnabled();
|
||||
}
|
||||
|
||||
async updateAutoFillOnPageLoad() {
|
||||
@@ -298,4 +326,12 @@ export class AutofillV1Component implements OnInit {
|
||||
async updateShowIdentitiesCurrentTab() {
|
||||
await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
|
||||
}
|
||||
|
||||
async updateShowInlineMenuCards() {
|
||||
await this.autofillSettingsService.setShowInlineMenuCards(this.showInlineMenuCards);
|
||||
}
|
||||
|
||||
async updateShowInlineMenuIdentities() {
|
||||
await this.autofillSettingsService.setShowInlineMenuIdentities(this.showInlineMenuIdentities);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,37 @@
|
||||
{{ "showInlineMenuOnFormFieldsDescAlt" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-control>
|
||||
<bit-form-control *ngIf="enableInlineMenu" class="tw-pl-5">
|
||||
<bit-form-control
|
||||
*ngIf="inlineMenuPositioningImprovementsEnabled && enableInlineMenu"
|
||||
class="tw-ml-5"
|
||||
>
|
||||
<input
|
||||
bitCheckbox
|
||||
id="show-inline-menu-identities"
|
||||
type="checkbox"
|
||||
(change)="updateShowInlineMenuIdentities()"
|
||||
[(ngModel)]="showInlineMenuIdentities"
|
||||
/>
|
||||
<bit-label for="show-inline-menu-identities">
|
||||
{{ "showInlineMenuIdentitiesLabel" | i18n }}
|
||||
</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-control
|
||||
*ngIf="inlineMenuPositioningImprovementsEnabled && enableInlineMenu"
|
||||
class="tw-ml-5"
|
||||
>
|
||||
<input
|
||||
bitCheckbox
|
||||
id="show-inline-menu-cards"
|
||||
type="checkbox"
|
||||
(change)="updateShowInlineMenuCards()"
|
||||
[(ngModel)]="showInlineMenuCards"
|
||||
/>
|
||||
<bit-label for="show-inline-menu-cards">
|
||||
{{ "showInlineMenuCardsLabel" | i18n }}
|
||||
</bit-label>
|
||||
</bit-form-control>
|
||||
<bit-form-control *ngIf="enableInlineMenu" class="tw-ml-5">
|
||||
<input
|
||||
bitCheckbox
|
||||
id="show-autofill-suggestions-on-icon"
|
||||
|
||||
@@ -21,10 +21,12 @@ import {
|
||||
DisablePasswordManagerUri,
|
||||
InlineMenuVisibilitySetting,
|
||||
} from "@bitwarden/common/autofill/types";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import {
|
||||
UriMatchStrategy,
|
||||
UriMatchStrategySetting,
|
||||
} from "@bitwarden/common/models/domain/domain-service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -82,6 +84,7 @@ export class AutofillComponent implements OnInit {
|
||||
protected defaultBrowserAutofillDisabled: boolean = false;
|
||||
protected inlineMenuVisibility: InlineMenuVisibilitySetting =
|
||||
AutofillOverlayVisibility.OnFieldFocus;
|
||||
protected inlineMenuPositioningImprovementsEnabled: boolean = false;
|
||||
protected browserClientVendor: BrowserClientVendor = BrowserClientVendors.Unknown;
|
||||
protected disablePasswordManagerURI: DisablePasswordManagerUri =
|
||||
DisablePasswordManagerUris.Unknown;
|
||||
@@ -93,6 +96,8 @@ export class AutofillComponent implements OnInit {
|
||||
enableAutofillOnPageLoad: boolean = false;
|
||||
enableInlineMenu: boolean = false;
|
||||
enableInlineMenuOnIconSelect: boolean = false;
|
||||
showInlineMenuIdentities: boolean = true;
|
||||
showInlineMenuCards: boolean = true;
|
||||
autofillOnPageLoadDefault: boolean = false;
|
||||
autofillOnPageLoadOptions: { name: string; value: boolean }[];
|
||||
enableContextMenuItem: boolean = false;
|
||||
@@ -114,6 +119,7 @@ export class AutofillComponent implements OnInit {
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private messagingService: MessagingService,
|
||||
private vaultSettingsService: VaultSettingsService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.autofillOnPageLoadOptions = [
|
||||
{ name: i18nService.t("autoFillOnPageLoadYes"), value: true },
|
||||
@@ -151,6 +157,18 @@ export class AutofillComponent implements OnInit {
|
||||
this.autofillSettingsService.inlineMenuVisibility$,
|
||||
);
|
||||
|
||||
this.inlineMenuPositioningImprovementsEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.InlineMenuPositioningImprovements,
|
||||
);
|
||||
|
||||
this.showInlineMenuIdentities =
|
||||
this.inlineMenuPositioningImprovementsEnabled &&
|
||||
(await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$));
|
||||
|
||||
this.showInlineMenuCards =
|
||||
this.inlineMenuPositioningImprovementsEnabled &&
|
||||
(await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$));
|
||||
|
||||
this.enableInlineMenuOnIconSelect =
|
||||
this.inlineMenuVisibility === AutofillOverlayVisibility.OnButtonClick;
|
||||
|
||||
@@ -381,4 +399,12 @@ export class AutofillComponent implements OnInit {
|
||||
async updateShowIdentitiesCurrentTab() {
|
||||
await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
|
||||
}
|
||||
|
||||
async updateShowInlineMenuCards() {
|
||||
await this.autofillSettingsService.setShowInlineMenuCards(this.showInlineMenuCards);
|
||||
}
|
||||
|
||||
async updateShowInlineMenuIdentities() {
|
||||
await this.autofillSettingsService.setShowInlineMenuIdentities(this.showInlineMenuIdentities);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2238,7 +2238,7 @@ describe("AutofillOverlayContentService", () => {
|
||||
it("updates the inlineMenuVisibility property", () => {
|
||||
sendMockExtensionMessage({
|
||||
command: "updateAutofillInlineMenuVisibility",
|
||||
data: { inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick },
|
||||
data: { newSettingValue: AutofillOverlayVisibility.OnButtonClick },
|
||||
});
|
||||
|
||||
expect(autofillOverlayContentService["inlineMenuVisibility"]).toEqual(
|
||||
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
AutofillOverlayVisibility,
|
||||
AUTOFILL_OVERLAY_HANDLE_REPOSITION,
|
||||
AUTOFILL_TRIGGER_FORM_FIELD_SUBMIT,
|
||||
AUTOFILL_OVERLAY_HANDLE_SCROLL,
|
||||
} from "@bitwarden/common/autofill/constants";
|
||||
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
import {
|
||||
@@ -51,7 +53,9 @@ import { AutoFillConstants } from "./autofill-constants";
|
||||
|
||||
export class AutofillOverlayContentService implements AutofillOverlayContentServiceInterface {
|
||||
pageDetailsUpdateRequired = false;
|
||||
inlineMenuVisibility: number;
|
||||
inlineMenuVisibility: InlineMenuVisibilitySetting;
|
||||
private showInlineMenuIdentities: boolean;
|
||||
private showInlineMenuCards: boolean;
|
||||
private readonly findTabs = tabbable;
|
||||
private readonly sendExtensionMessage = sendExtensionMessage;
|
||||
private formFieldElements: Map<ElementWithOpId<FormFieldElement>, AutofillField> = new Map();
|
||||
@@ -183,6 +187,18 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
autofillFieldData: AutofillField,
|
||||
pageDetails: AutofillPageDetails,
|
||||
) {
|
||||
if (!this.inlineMenuVisibility) {
|
||||
await this.getInlineMenuVisibility();
|
||||
}
|
||||
|
||||
if (this.showInlineMenuCards == null) {
|
||||
await this.getInlineMenuCardsVisibility();
|
||||
}
|
||||
|
||||
if (this.showInlineMenuIdentities == null) {
|
||||
await this.getInlineMenuIdentitiesVisibility();
|
||||
}
|
||||
|
||||
if (
|
||||
this.formFieldElements.has(formFieldElement) ||
|
||||
this.isIgnoredField(autofillFieldData, pageDetails)
|
||||
@@ -1019,10 +1035,16 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
const { width, height, top, left } =
|
||||
await this.getMostRecentlyFocusedFieldRects(formFieldElement);
|
||||
const autofillFieldData = this.formFieldElements.get(formFieldElement);
|
||||
|
||||
let accountCreationFieldType = null;
|
||||
|
||||
if (
|
||||
// user setting allows display of identities in inline menu
|
||||
this.showInlineMenuIdentities &&
|
||||
// `showInlineMenuAccountCreation` has been set or field is filled by Login cipher
|
||||
(autofillFieldData?.showInlineMenuAccountCreation ||
|
||||
autofillFieldData?.filledByCipherType === CipherType.Login) &&
|
||||
// field is a username field, which is relevant to both Identity and Login ciphers
|
||||
this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData)
|
||||
) {
|
||||
accountCreationFieldType = this.inlineMenuFieldQualificationService.isEmailField(
|
||||
@@ -1125,6 +1147,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
}
|
||||
|
||||
if (
|
||||
this.showInlineMenuCards &&
|
||||
this.inlineMenuFieldQualificationService.isFieldForCreditCardForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
@@ -1135,6 +1158,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
}
|
||||
|
||||
if (
|
||||
this.showInlineMenuIdentities &&
|
||||
this.inlineMenuFieldQualificationService.isFieldForAccountCreationForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
@@ -1146,6 +1170,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
}
|
||||
|
||||
if (
|
||||
this.showInlineMenuIdentities &&
|
||||
this.inlineMenuFieldQualificationService.isFieldForIdentityForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
@@ -1244,6 +1269,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
autofillFieldData.readonly = getAttributeBoolean(formFieldElement, "disabled");
|
||||
autofillFieldData.disabled = getAttributeBoolean(formFieldElement, "disabled");
|
||||
autofillFieldData.viewable = true;
|
||||
|
||||
void this.setupOverlayListenersOnQualifiedField(formFieldElement, autofillFieldData);
|
||||
}
|
||||
|
||||
@@ -1266,10 +1292,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
await this.updateMostRecentlyFocusedField(formFieldElement);
|
||||
}
|
||||
|
||||
if (!this.inlineMenuVisibility) {
|
||||
await this.getInlineMenuVisibility();
|
||||
}
|
||||
|
||||
this.setupFormFieldElementEventListeners(formFieldElement);
|
||||
this.setupFormSubmissionEventListeners(formFieldElement, autofillFieldData);
|
||||
|
||||
@@ -1291,6 +1313,30 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
this.inlineMenuVisibility = inlineMenuVisibility || AutofillOverlayVisibility.OnFieldFocus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the background script for the autofill inline menu's Cards visibility setting.
|
||||
* If the setting is not found, a default value of true will be used
|
||||
* @private
|
||||
*/
|
||||
private async getInlineMenuCardsVisibility() {
|
||||
const inlineMenuCardsVisibility = await this.sendExtensionMessage(
|
||||
"getInlineMenuCardsVisibility",
|
||||
);
|
||||
this.showInlineMenuCards = inlineMenuCardsVisibility ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the background script for the autofill inline menu's Identities visibility setting.
|
||||
* If the setting is not found, a default value of true will be used
|
||||
* @private
|
||||
*/
|
||||
private async getInlineMenuIdentitiesVisibility() {
|
||||
const inlineMenuIdentitiesVisibility = await this.sendExtensionMessage(
|
||||
"getInlineMenuIdentitiesVisibility",
|
||||
);
|
||||
this.showInlineMenuIdentities = inlineMenuIdentitiesVisibility ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value that indicates if we should hide the inline menu list due to a filled field.
|
||||
*
|
||||
@@ -1318,8 +1364,10 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
* @param data - The data object from the extension message.
|
||||
*/
|
||||
private updateInlineMenuVisibility({ data }: AutofillExtensionMessage) {
|
||||
if (!isNaN(data?.inlineMenuVisibility)) {
|
||||
this.inlineMenuVisibility = data.inlineMenuVisibility;
|
||||
const newSettingValue = data?.newSettingValue;
|
||||
|
||||
if (!isNaN(newSettingValue)) {
|
||||
this.inlineMenuVisibility = newSettingValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1600,15 +1648,28 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
* the overlay elements on scroll or resize.
|
||||
*/
|
||||
private setOverlayRepositionEventListeners() {
|
||||
const handler = this.useEventHandlersMemo(
|
||||
const repositionHandler = this.useEventHandlersMemo(
|
||||
throttle(this.handleOverlayRepositionEvent, 250),
|
||||
AUTOFILL_OVERLAY_HANDLE_REPOSITION,
|
||||
);
|
||||
globalThis.addEventListener(EVENTS.SCROLL, handler, {
|
||||
|
||||
const eventTargetDoesNotContainFocusedField = (element: Element) =>
|
||||
typeof element?.contains === "function" && !element.contains(this.mostRecentlyFocusedField);
|
||||
const scrollHandler = this.useEventHandlersMemo(
|
||||
throttle((event) => {
|
||||
if (eventTargetDoesNotContainFocusedField(event.target as Element)) {
|
||||
return;
|
||||
}
|
||||
repositionHandler(event);
|
||||
}, 50),
|
||||
AUTOFILL_OVERLAY_HANDLE_SCROLL,
|
||||
);
|
||||
|
||||
globalThis.addEventListener(EVENTS.SCROLL, scrollHandler, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
globalThis.addEventListener(EVENTS.RESIZE, handler);
|
||||
globalThis.addEventListener(EVENTS.RESIZE, repositionHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1616,12 +1677,19 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
* the overlay elements on scroll or resize.
|
||||
*/
|
||||
private removeOverlayRepositionEventListeners() {
|
||||
const handler = this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_REPOSITION];
|
||||
globalThis.removeEventListener(EVENTS.SCROLL, handler, {
|
||||
capture: true,
|
||||
});
|
||||
globalThis.removeEventListener(EVENTS.RESIZE, handler);
|
||||
globalThis.removeEventListener(
|
||||
EVENTS.SCROLL,
|
||||
this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_SCROLL],
|
||||
{
|
||||
capture: true,
|
||||
},
|
||||
);
|
||||
globalThis.removeEventListener(
|
||||
EVENTS.RESIZE,
|
||||
this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_REPOSITION],
|
||||
);
|
||||
|
||||
delete this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_SCROLL];
|
||||
delete this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_REPOSITION];
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ describe("AutofillService", () => {
|
||||
let autofillService: AutofillService;
|
||||
const cipherService = mock<CipherService>();
|
||||
let inlineMenuVisibilityMock$!: BehaviorSubject<InlineMenuVisibilitySetting>;
|
||||
let showInlineMenuCardsMock$!: BehaviorSubject<boolean>;
|
||||
let showInlineMenuIdentitiesMock$!: BehaviorSubject<boolean>;
|
||||
let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>;
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||
@@ -98,8 +100,12 @@ describe("AutofillService", () => {
|
||||
beforeEach(() => {
|
||||
scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService);
|
||||
inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus);
|
||||
showInlineMenuCardsMock$ = new BehaviorSubject(false);
|
||||
showInlineMenuIdentitiesMock$ = new BehaviorSubject(false);
|
||||
autofillSettingsService = mock<AutofillSettingsServiceAbstraction>();
|
||||
autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$;
|
||||
autofillSettingsService.showInlineMenuCards$ = showInlineMenuCardsMock$;
|
||||
autofillSettingsService.showInlineMenuIdentities$ = showInlineMenuIdentitiesMock$;
|
||||
activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked);
|
||||
authService = mock<AuthService>();
|
||||
authService.activeAccountStatus$ = activeAccountStatusMock$;
|
||||
@@ -291,12 +297,12 @@ describe("AutofillService", () => {
|
||||
expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith(
|
||||
tab1,
|
||||
"updateAutofillInlineMenuVisibility",
|
||||
{ inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick },
|
||||
{ newSettingValue: AutofillOverlayVisibility.OnButtonClick },
|
||||
);
|
||||
expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith(
|
||||
tab2,
|
||||
"updateAutofillInlineMenuVisibility",
|
||||
{ inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick },
|
||||
{ newSettingValue: AutofillOverlayVisibility.OnButtonClick },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -308,12 +314,12 @@ describe("AutofillService", () => {
|
||||
expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith(
|
||||
tab1,
|
||||
"updateAutofillInlineMenuVisibility",
|
||||
{ inlineMenuVisibility: AutofillOverlayVisibility.OnFieldFocus },
|
||||
{ newSettingValue: AutofillOverlayVisibility.OnFieldFocus },
|
||||
);
|
||||
expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith(
|
||||
tab2,
|
||||
"updateAutofillInlineMenuVisibility",
|
||||
{ inlineMenuVisibility: AutofillOverlayVisibility.OnFieldFocus },
|
||||
{ newSettingValue: AutofillOverlayVisibility.OnFieldFocus },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,10 +130,23 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
async loadAutofillScriptsOnInstall() {
|
||||
BrowserApi.addListener(chrome.runtime.onConnect, this.handleInjectedScriptPortConnection);
|
||||
void this.injectAutofillScriptsInAllTabs();
|
||||
|
||||
this.autofillSettingsService.inlineMenuVisibility$
|
||||
.pipe(startWith(undefined), pairwise())
|
||||
.subscribe(([previousSetting, currentSetting]) =>
|
||||
this.handleInlineMenuVisibilityChange(previousSetting, currentSetting),
|
||||
this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting),
|
||||
);
|
||||
|
||||
this.autofillSettingsService.showInlineMenuCards$
|
||||
.pipe(startWith(undefined), pairwise())
|
||||
.subscribe(([previousSetting, currentSetting]) =>
|
||||
this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting),
|
||||
);
|
||||
|
||||
this.autofillSettingsService.showInlineMenuIdentities$
|
||||
.pipe(startWith(undefined), pairwise())
|
||||
.subscribe(([previousSetting, currentSetting]) =>
|
||||
this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3043,27 +3056,36 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the autofill inline menu visibility setting in all active tabs
|
||||
* when the InlineMenuVisibilitySetting observable is updated.
|
||||
* Updates the autofill inline menu visibility settings in all active tabs
|
||||
* when the inlineMenuVisibility, showInlineMenuCards, or showInlineMenuIdentities
|
||||
* observables are updated.
|
||||
*
|
||||
* @param previousSetting - The previous setting value
|
||||
* @param currentSetting - The current setting value
|
||||
* @param oldSettingValue - The previous setting value
|
||||
* @param newSettingValue - The current setting value
|
||||
* @param cipherType - The cipher type of the changed inline menu setting
|
||||
*/
|
||||
private async handleInlineMenuVisibilityChange(
|
||||
previousSetting: InlineMenuVisibilitySetting,
|
||||
currentSetting: InlineMenuVisibilitySetting,
|
||||
private async handleInlineMenuVisibilitySettingsChange(
|
||||
oldSettingValue: InlineMenuVisibilitySetting | boolean,
|
||||
newSettingValue: InlineMenuVisibilitySetting | boolean,
|
||||
) {
|
||||
if (previousSetting === undefined || previousSetting === currentSetting) {
|
||||
if (oldSettingValue === undefined || oldSettingValue === newSettingValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inlineMenuPreviouslyDisabled = previousSetting === AutofillOverlayVisibility.Off;
|
||||
const inlineMenuCurrentlyDisabled = currentSetting === AutofillOverlayVisibility.Off;
|
||||
if (!inlineMenuPreviouslyDisabled && !inlineMenuCurrentlyDisabled) {
|
||||
const isInlineMenuVisibilitySubSetting =
|
||||
typeof oldSettingValue === "boolean" || typeof newSettingValue === "boolean";
|
||||
const inlineMenuPreviouslyDisabled = oldSettingValue === AutofillOverlayVisibility.Off;
|
||||
const inlineMenuCurrentlyDisabled = newSettingValue === AutofillOverlayVisibility.Off;
|
||||
|
||||
if (
|
||||
!isInlineMenuVisibilitySubSetting &&
|
||||
!inlineMenuPreviouslyDisabled &&
|
||||
!inlineMenuCurrentlyDisabled
|
||||
) {
|
||||
const tabs = await BrowserApi.tabsQuery({});
|
||||
tabs.forEach((tab) =>
|
||||
BrowserApi.tabSendMessageData(tab, "updateAutofillInlineMenuVisibility", {
|
||||
inlineMenuVisibility: currentSetting,
|
||||
newSettingValue,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -1340,7 +1340,7 @@ export default class MainBackground {
|
||||
}
|
||||
|
||||
if (!supported) {
|
||||
this.sdkService.failedToInitialize().catch(this.logService.error);
|
||||
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -449,7 +449,9 @@ const routes: Routes = [
|
||||
canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh), lockGuard()],
|
||||
data: {
|
||||
pageIcon: LockIcon,
|
||||
pageTitle: "yourVaultIsLockedV2",
|
||||
pageTitle: {
|
||||
key: "yourVaultIsLockedV2",
|
||||
},
|
||||
showReadonlyHostname: true,
|
||||
showAcctSwitcher: true,
|
||||
} satisfies ExtensionAnonLayoutWrapperData,
|
||||
@@ -471,7 +473,9 @@ const routes: Routes = [
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
|
||||
data: {
|
||||
state: "signup",
|
||||
pageTitle: "createAccount",
|
||||
pageTitle: {
|
||||
key: "createAccount",
|
||||
},
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
{
|
||||
@@ -492,8 +496,12 @@ const routes: Routes = [
|
||||
path: "finish-signup",
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "setAStrongPassword",
|
||||
pageSubtitle: "finishCreatingYourAccountBySettingAPassword",
|
||||
pageTitle: {
|
||||
key: "setAStrongPassword",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "finishCreatingYourAccountBySettingAPassword",
|
||||
},
|
||||
state: "finish-signup",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -508,8 +516,12 @@ const routes: Routes = [
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification)],
|
||||
component: SetPasswordJitComponent,
|
||||
data: {
|
||||
pageTitle: "joinOrganization",
|
||||
pageSubtitle: "finishJoiningThisOrganizationBySettingAMasterPassword",
|
||||
pageTitle: {
|
||||
key: "joinOrganization",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "finishJoiningThisOrganizationBySettingAMasterPassword",
|
||||
},
|
||||
state: "set-password-jit",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
},
|
||||
|
||||
@@ -81,7 +81,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
.subscribe((supported) => {
|
||||
if (!supported) {
|
||||
this.logService.debug("SDK is not supported");
|
||||
this.sdkService.failedToInitialize().catch(this.logService.error);
|
||||
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
|
||||
} else {
|
||||
this.logService.debug("SDK is supported");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ItemModule } from "@bitwarden/components";
|
||||
@@ -22,6 +23,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
|
||||
PopupHeaderComponent,
|
||||
PopupPageComponent,
|
||||
PopupFooterComponent,
|
||||
RouterModule,
|
||||
ItemModule,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<bit-item>
|
||||
<button type="button" bit-item-content (click)="about()">
|
||||
{{ "aboutBitwarden" | i18n }}
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@bitwarden/importer/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/platform": ["../../libs/platform/src"],
|
||||
"@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["../../libs/tools/card/src"],
|
||||
"@bitwarden/key-management": ["../../libs/key-management/src"],
|
||||
"@bitwarden/vault": ["../../libs/vault/src"]
|
||||
},
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"papaparse": "5.4.1",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tldts": "6.1.48",
|
||||
"tldts": "6.1.51",
|
||||
"zxcvbn": "4.4.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,7 +858,7 @@ export class ServiceContainer {
|
||||
}
|
||||
|
||||
if (!supported) {
|
||||
this.sdkService.failedToInitialize().catch(this.logService.error);
|
||||
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ publish = false
|
||||
anyhow = "=1.0.86"
|
||||
desktop_core = { path = "../core", default-features = false }
|
||||
futures = "0.3.30"
|
||||
log = "0.4.21"
|
||||
log = "0.4.22"
|
||||
simplelog = "0.12.2"
|
||||
tokio = { version = "1.38.0", features = ["io-std", "io-util", "macros", "rt"] }
|
||||
tokio-util = { version = "0.7.11", features = ["codec"] }
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"yargs": "17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.16.10",
|
||||
"@types/node": "20.16.11",
|
||||
"@types/node-ipc": "9.2.3",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
@@ -106,9 +106,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.16.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
|
||||
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
|
||||
"version": "20.16.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
|
||||
"integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"yargs": "17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.16.10",
|
||||
"@types/node": "20.16.11",
|
||||
"@types/node-ipc": "9.2.3",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
|
||||
@@ -141,8 +141,12 @@ const routes: Routes = [
|
||||
path: "hint",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "requestPasswordHint",
|
||||
pageSubtitle: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
pageTitle: {
|
||||
key: "requestPasswordHint",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou",
|
||||
},
|
||||
pageIcon: UserLockIcon,
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -164,7 +168,11 @@ const routes: Routes = [
|
||||
{
|
||||
path: "signup",
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
|
||||
data: { pageTitle: "createAccount" } satisfies AnonLayoutWrapperData,
|
||||
data: {
|
||||
pageTitle: {
|
||||
key: "createAccount",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
@@ -184,8 +192,12 @@ const routes: Routes = [
|
||||
path: "finish-signup",
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "setAStrongPassword",
|
||||
pageSubtitle: "finishCreatingYourAccountBySettingAPassword",
|
||||
pageTitle: {
|
||||
key: "setAStrongPassword",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "finishCreatingYourAccountBySettingAPassword",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{
|
||||
@@ -199,7 +211,9 @@ const routes: Routes = [
|
||||
canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh), lockGuard()],
|
||||
data: {
|
||||
pageIcon: LockIcon,
|
||||
pageTitle: "yourVaultIsLockedV2",
|
||||
pageTitle: {
|
||||
key: "yourVaultIsLockedV2",
|
||||
},
|
||||
showReadonlyHostname: true,
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -214,8 +228,12 @@ const routes: Routes = [
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification)],
|
||||
component: SetPasswordJitComponent,
|
||||
data: {
|
||||
pageTitle: "joinOrganization",
|
||||
pageSubtitle: "finishJoiningThisOrganizationBySettingAMasterPassword",
|
||||
pageTitle: {
|
||||
key: "joinOrganization",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "finishJoiningThisOrganizationBySettingAMasterPassword",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -167,7 +167,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
.subscribe((supported) => {
|
||||
if (!supported) {
|
||||
this.logService.debug("SDK is not supported");
|
||||
this.sdkService.failedToInitialize().catch(this.logService.error);
|
||||
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
|
||||
} else {
|
||||
this.logService.debug("SDK is supported");
|
||||
}
|
||||
|
||||
@@ -1752,6 +1752,15 @@
|
||||
"message": "Your organization requires you to set a master password.",
|
||||
"description": "Used as a card title description on the set password page to explain why the user is there"
|
||||
},
|
||||
"cardMetrics": {
|
||||
"message": "out of $TOTAL$",
|
||||
"placeholders": {
|
||||
"total": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"verificationRequired": {
|
||||
"message": "Verification required",
|
||||
"description": "Default title for the user verification dialog."
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@bitwarden/node/*": ["../../libs/node/src/*"],
|
||||
"@bitwarden/platform": ["../../libs/platform/src"],
|
||||
"@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["../../libs/tools/card/src"],
|
||||
"@bitwarden/vault": ["../../libs/vault/src"]
|
||||
},
|
||||
"useDefineForClassFields": false
|
||||
|
||||
@@ -105,7 +105,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
.subscribe((supported) => {
|
||||
if (!supported) {
|
||||
this.logService.debug("SDK is not supported");
|
||||
this.sdkService.failedToInitialize().catch(this.logService.error);
|
||||
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
|
||||
} else {
|
||||
this.logService.debug("SDK is supported");
|
||||
}
|
||||
|
||||
@@ -14,22 +14,24 @@ describe("freeTrialTextResolver", () => {
|
||||
it("shows password manager text", () => {
|
||||
route.queryParams.product = `${ProductType.PasswordManager}`;
|
||||
|
||||
expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe(
|
||||
"continueSettingUpFreeTrialPasswordManager",
|
||||
);
|
||||
expect(freeTrialTextResolver(route, routerStateSnapshot)).toEqual({
|
||||
key: "continueSettingUpFreeTrialPasswordManager",
|
||||
});
|
||||
});
|
||||
|
||||
it("shows secret manager text", () => {
|
||||
route.queryParams.product = `${ProductType.SecretsManager}`;
|
||||
|
||||
expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe(
|
||||
"continueSettingUpFreeTrialSecretsManager",
|
||||
);
|
||||
expect(freeTrialTextResolver(route, routerStateSnapshot)).toEqual({
|
||||
key: "continueSettingUpFreeTrialSecretsManager",
|
||||
});
|
||||
});
|
||||
|
||||
it("shows default text", () => {
|
||||
route.queryParams.product = `${ProductType.PasswordManager},${ProductType.SecretsManager}`;
|
||||
|
||||
expect(freeTrialTextResolver(route, routerStateSnapshot)).toBe("continueSettingUpFreeTrial");
|
||||
expect(freeTrialTextResolver(route, routerStateSnapshot)).toEqual({
|
||||
key: "continueSettingUpFreeTrial",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ActivatedRouteSnapshot, ResolveFn } from "@angular/router";
|
||||
|
||||
import { ProductType } from "@bitwarden/common/billing/enums";
|
||||
import { Translation } from "@bitwarden/components";
|
||||
|
||||
export const freeTrialTextResolver: ResolveFn<string | null> = (
|
||||
export const freeTrialTextResolver: ResolveFn<Translation | null> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
): string | null => {
|
||||
): Translation | null => {
|
||||
const { product } = route.queryParams;
|
||||
const products: ProductType[] = (product ?? "").split(",").map((p: string) => parseInt(p));
|
||||
|
||||
@@ -13,10 +14,16 @@ export const freeTrialTextResolver: ResolveFn<string | null> = (
|
||||
|
||||
switch (true) {
|
||||
case onlyPasswordManager:
|
||||
return "continueSettingUpFreeTrialPasswordManager";
|
||||
return {
|
||||
key: "continueSettingUpFreeTrialPasswordManager",
|
||||
};
|
||||
case onlySecretsManager:
|
||||
return "continueSettingUpFreeTrialSecretsManager";
|
||||
return {
|
||||
key: "continueSettingUpFreeTrialSecretsManager",
|
||||
};
|
||||
default:
|
||||
return "continueSettingUpFreeTrial";
|
||||
return {
|
||||
key: "continueSettingUpFreeTrial",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -228,7 +228,9 @@ const routes: Routes = [
|
||||
path: "signup",
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "createAccount",
|
||||
pageTitle: {
|
||||
key: "createAccount",
|
||||
},
|
||||
titleId: "createAccount",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -250,8 +252,12 @@ const routes: Routes = [
|
||||
path: "finish-signup",
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "setAStrongPassword",
|
||||
pageSubtitle: "finishCreatingYourAccountBySettingAPassword",
|
||||
pageTitle: {
|
||||
key: "setAStrongPassword",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "finishCreatingYourAccountBySettingAPassword",
|
||||
},
|
||||
titleId: "setAStrongPassword",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -264,7 +270,9 @@ const routes: Routes = [
|
||||
{
|
||||
path: "send/:sendId/:key",
|
||||
data: {
|
||||
pageTitle: "viewSend",
|
||||
pageTitle: {
|
||||
key: "viewSend",
|
||||
},
|
||||
showReadonlyHostname: true,
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -284,15 +292,21 @@ const routes: Routes = [
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification)],
|
||||
component: SetPasswordJitComponent,
|
||||
data: {
|
||||
pageTitle: "joinOrganization",
|
||||
pageSubtitle: "finishJoiningThisOrganizationBySettingAMasterPassword",
|
||||
pageTitle: {
|
||||
key: "joinOrganization",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "finishJoiningThisOrganizationBySettingAMasterPassword",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
},
|
||||
{
|
||||
path: "signup-link-expired",
|
||||
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "expiredLink",
|
||||
pageTitle: {
|
||||
key: "expiredLink",
|
||||
},
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
children: [
|
||||
{
|
||||
@@ -308,7 +322,9 @@ const routes: Routes = [
|
||||
path: "sso",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "enterpriseSingleSignOn",
|
||||
pageTitle: {
|
||||
key: "enterpriseSingleSignOn",
|
||||
},
|
||||
titleId: "enterpriseSingleSignOn",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -338,7 +354,9 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
data: {
|
||||
pageTitle: "logIn",
|
||||
pageTitle: {
|
||||
key: "logIn",
|
||||
},
|
||||
},
|
||||
},
|
||||
...extensionRefreshSwap(
|
||||
@@ -354,7 +372,9 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
data: {
|
||||
pageTitle: "yourVaultIsLockedV2",
|
||||
pageTitle: {
|
||||
key: "yourVaultIsLockedV2",
|
||||
},
|
||||
pageIcon: LockIcon,
|
||||
showReadonlyHostname: true,
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
@@ -369,7 +389,9 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
data: {
|
||||
pageTitle: "yourAccountIsLocked",
|
||||
pageTitle: {
|
||||
key: "yourAccountIsLocked",
|
||||
},
|
||||
pageIcon: LockIcon,
|
||||
showReadonlyHostname: true,
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
@@ -390,7 +412,9 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
data: {
|
||||
pageTitle: "verifyIdentity",
|
||||
pageTitle: {
|
||||
key: "verifyIdentity",
|
||||
},
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
},
|
||||
{
|
||||
@@ -408,7 +432,9 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
data: {
|
||||
pageTitle: "recoverAccountTwoStep",
|
||||
pageTitle: {
|
||||
key: "recoverAccountTwoStep",
|
||||
},
|
||||
titleId: "recoverAccountTwoStep",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
},
|
||||
@@ -416,7 +442,9 @@ const routes: Routes = [
|
||||
path: "accept-emergency",
|
||||
canActivate: [deepLinkGuard()],
|
||||
data: {
|
||||
pageTitle: "emergencyAccess",
|
||||
pageTitle: {
|
||||
key: "emergencyAccess",
|
||||
},
|
||||
titleId: "acceptEmergency",
|
||||
doNotSaveUrl: false,
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
@@ -434,7 +462,9 @@ const routes: Routes = [
|
||||
path: "recover-delete",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "deleteAccount",
|
||||
pageTitle: {
|
||||
key: "deleteAccount",
|
||||
},
|
||||
titleId: "deleteAccount",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -453,7 +483,9 @@ const routes: Routes = [
|
||||
path: "verify-recover-delete",
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageTitle: "deleteAccount",
|
||||
pageTitle: {
|
||||
key: "deleteAccount",
|
||||
},
|
||||
titleId: "deleteAccount",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
@@ -468,7 +500,9 @@ const routes: Routes = [
|
||||
component: RemovePasswordComponent,
|
||||
canActivate: [authGuard],
|
||||
data: {
|
||||
pageTitle: "removeMasterPassword",
|
||||
pageTitle: {
|
||||
key: "removeMasterPassword",
|
||||
},
|
||||
titleId: "removeMasterPassword",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
},
|
||||
|
||||
@@ -157,8 +157,8 @@ export class AccessComponent implements OnInit {
|
||||
if (this.creatorIdentifier != null) {
|
||||
this.layoutWrapperDataService.setAnonLayoutWrapperData({
|
||||
pageSubtitle: {
|
||||
subtitle: this.i18nService.t("sendAccessCreatorIdentifier", this.creatorIdentifier),
|
||||
translate: false,
|
||||
key: "sendAccessCreatorIdentifier",
|
||||
placeholders: [this.creatorIdentifier],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<td bitCell [ngClass]="RowHeightClass" class="tw-min-w-fit">
|
||||
<input
|
||||
*ngIf="showCheckbox"
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
appStopProp
|
||||
|
||||
@@ -42,6 +42,7 @@ export class VaultCipherRowComponent implements OnInit {
|
||||
@Output() checkedToggled = new EventEmitter<void>();
|
||||
|
||||
protected CipherType = CipherType;
|
||||
protected organization?: Organization;
|
||||
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
@@ -53,6 +54,9 @@ export class VaultCipherRowComponent implements OnInit {
|
||||
this.extensionRefreshEnabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
|
||||
);
|
||||
if (this.cipher.organizationId != null) {
|
||||
this.organization = this.organizations.find((o) => o.id === this.cipher.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
protected get showTotpCopyButton() {
|
||||
@@ -138,4 +142,12 @@ export class VaultCipherRowComponent implements OnInit {
|
||||
protected assignToCollections() {
|
||||
this.onEvent.emit({ type: "assignToCollections", items: [this.cipher] });
|
||||
}
|
||||
|
||||
protected get showCheckbox() {
|
||||
if (!this.viewingOrgVault || !this.organization) {
|
||||
return true; // Always show checkbox in individual vault or for non-org items
|
||||
}
|
||||
|
||||
return this.organization.canEditAllCiphers || this.cipher.edit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,10 @@ export class VaultCollectionRowComponent {
|
||||
}
|
||||
|
||||
protected get showCheckbox() {
|
||||
return this.collection?.id !== Unassigned;
|
||||
if (this.collection?.id === Unassigned) {
|
||||
return false; // Never show checkbox for Unassigned
|
||||
}
|
||||
|
||||
return this.canEditCollection || this.canDeleteCollection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SelectionModel } from "@angular/cdk/collections";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { Unassigned, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView, Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { TableDataSource } from "@bitwarden/components";
|
||||
@@ -205,11 +205,12 @@ export class VaultItemsComponent {
|
||||
|
||||
this.selection.clear();
|
||||
|
||||
// Every item except for the Unassigned collection is selectable, individual bulk actions check the user's permission
|
||||
// All ciphers are selectable, collections only if they can be edited or deleted
|
||||
this.editableItems = items.filter(
|
||||
(item) =>
|
||||
item.cipher !== undefined ||
|
||||
(item.collection !== undefined && item.collection.id !== Unassigned),
|
||||
(item.collection !== undefined &&
|
||||
(this.canEditCollection(item.collection) || this.canDeleteCollection(item.collection))),
|
||||
);
|
||||
|
||||
this.dataSource.data = items;
|
||||
|
||||
@@ -7922,6 +7922,15 @@
|
||||
"message": "Your organization requires you to set a master password.",
|
||||
"description": "Used as a card title description on the set password page to explain why the user is there"
|
||||
},
|
||||
"cardMetrics": {
|
||||
"message": "out of $TOTAL$",
|
||||
"placeholders": {
|
||||
"total": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notFound": {
|
||||
"message": "$RESOURCE$ not found",
|
||||
"placeholders": {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@bitwarden/key-management": ["../../libs/key-management/src"],
|
||||
"@bitwarden/platform": ["../../libs/platform/src"],
|
||||
"@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["../../libs/tools/card/src"],
|
||||
"@bitwarden/vault": ["../../libs/vault/src"],
|
||||
"@bitwarden/web-vault/*": ["src/*"]
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
],
|
||||
"@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"],
|
||||
"@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["../../libs/tools/card/src"],
|
||||
"@bitwarden/key-management": ["../../libs/key-management/src"],
|
||||
"@bitwarden/platform": ["../../libs/platform/src"],
|
||||
"@bitwarden/vault": ["../../libs/vault/src"],
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
<ng-container *ngIf="subscription">
|
||||
<tr bitRow *ngFor="let i of subscription.plans">
|
||||
<tr bitRow *ngFor="let i of activePlans">
|
||||
<td bitCell class="tw-pl-0 tw-py-3">
|
||||
{{ getFormattedPlanName(i.planName) }} {{ "orgSeats" | i18n }} ({{
|
||||
i.cadence.toLowerCase()
|
||||
|
||||
@@ -101,4 +101,14 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get activePlans(): ProviderPlanResponse[] {
|
||||
return this.subscription.plans.filter((plan) => {
|
||||
if (plan.purchasedSeats === 0) {
|
||||
return plan.seatMinimum > 0;
|
||||
} else {
|
||||
return plan.purchasedSeats > 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@bitwarden/key-management": ["../../libs/key-management/src"],
|
||||
"@bitwarden/platform": ["../../libs/platform/src"],
|
||||
"@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["../../libs/tools/card/src"],
|
||||
"@bitwarden/vault": ["../../libs/vault/src"],
|
||||
"@bitwarden/web-vault/*": ["../../apps/web/src/*"],
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 321 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -239,11 +239,15 @@ $icons: (
|
||||
"github": "\e950",
|
||||
"facebook": "\e94d",
|
||||
"paypal": "\e938",
|
||||
"google": "\e951",
|
||||
"brave": "\e951",
|
||||
"google": "\e9a5",
|
||||
"duckduckgo": "\e9bb",
|
||||
"tor": "\e9bc",
|
||||
"vivaldi": "\e9bd",
|
||||
"linkedin": "\e955",
|
||||
"discourse": "\e91e",
|
||||
"twitter": "\e961",
|
||||
"x-twitter": "\e9a5",
|
||||
"x-twitter": "\e9be",
|
||||
"youtube": "\e966",
|
||||
"windows": "\e964",
|
||||
"apple": "\e945",
|
||||
@@ -276,6 +280,21 @@ $icons: (
|
||||
"popout": "\e9aa",
|
||||
"wand": "\e9a6",
|
||||
"msp": "\e9a1",
|
||||
"totp-codes-alt": "\e9ac",
|
||||
"totp-codes-alt2": "\e9ad",
|
||||
"totp-codes": "\e9ae",
|
||||
"authenticator": "\e9af",
|
||||
"fingerprint": "\e9b0",
|
||||
"expired": "\e9ba",
|
||||
"icon-1": "\e9b1",
|
||||
"icon-2": "\e9b2",
|
||||
"icon-3": "\e9b3",
|
||||
"icon-4": "\e9b4",
|
||||
"icon-5": "\e9b5",
|
||||
"icon-6": "\e9b6",
|
||||
"icon-7": "\e9b7",
|
||||
"icon-8": "\e9b8",
|
||||
"icon-9": "\e9b9",
|
||||
);
|
||||
|
||||
@each $name, $glyph in $icons {
|
||||
|
||||
@@ -4,20 +4,34 @@ import { Subject, filter, switchMap, takeUntil, tap } from "rxjs";
|
||||
|
||||
import { AnonLayoutComponent } from "@bitwarden/auth/angular";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Icon } from "@bitwarden/components";
|
||||
import { Icon, Translation } from "@bitwarden/components";
|
||||
|
||||
import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service";
|
||||
|
||||
export interface AnonLayoutWrapperData {
|
||||
pageTitle?: string;
|
||||
pageSubtitle?:
|
||||
| string
|
||||
| {
|
||||
subtitle: string;
|
||||
translate: boolean;
|
||||
};
|
||||
/**
|
||||
* The optional title of the page.
|
||||
* If a string is provided, it will be presented as is (ex: Organization name)
|
||||
* If a Translation object (supports placeholders) is provided, it will be translated
|
||||
*/
|
||||
pageTitle?: string | Translation;
|
||||
/**
|
||||
* The optional subtitle of the page.
|
||||
* If a string is provided, it will be presented as is (ex: user's email)
|
||||
* If a Translation object (supports placeholders) is provided, it will be translated
|
||||
*/
|
||||
pageSubtitle?: string | Translation;
|
||||
/**
|
||||
* The optional icon to display on the page.
|
||||
*/
|
||||
pageIcon?: Icon;
|
||||
/**
|
||||
* Optional flag to either show the optional environment selector (false) or just a readonly hostname (true).
|
||||
*/
|
||||
showReadonlyHostname?: boolean;
|
||||
/**
|
||||
* Optional flag to set the max-width of the page. Defaults to 'md' if not provided.
|
||||
*/
|
||||
maxWidth?: "md" | "3xl";
|
||||
}
|
||||
|
||||
@@ -71,11 +85,11 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (firstChildRouteData["pageTitle"] !== undefined) {
|
||||
this.pageTitle = this.i18nService.t(firstChildRouteData["pageTitle"]);
|
||||
this.pageTitle = this.handleStringOrTranslation(firstChildRouteData["pageTitle"]);
|
||||
}
|
||||
|
||||
if (firstChildRouteData["pageSubtitle"] !== undefined) {
|
||||
this.pageSubtitle = this.i18nService.t(firstChildRouteData["pageSubtitle"]);
|
||||
this.pageSubtitle = this.handleStringOrTranslation(firstChildRouteData["pageSubtitle"]);
|
||||
}
|
||||
|
||||
if (firstChildRouteData["pageIcon"] !== undefined) {
|
||||
@@ -101,19 +115,11 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (data.pageTitle) {
|
||||
this.pageTitle = this.i18nService.t(data.pageTitle);
|
||||
this.pageTitle = this.handleStringOrTranslation(data.pageTitle);
|
||||
}
|
||||
|
||||
if (data.pageSubtitle) {
|
||||
// If you pass just a string, we translate it by default
|
||||
if (typeof data.pageSubtitle === "string") {
|
||||
this.pageSubtitle = this.i18nService.t(data.pageSubtitle);
|
||||
} else {
|
||||
// if you pass an object, you can specify if you want to translate it or not
|
||||
this.pageSubtitle = data.pageSubtitle.translate
|
||||
? this.i18nService.t(data.pageSubtitle.subtitle)
|
||||
: data.pageSubtitle.subtitle;
|
||||
}
|
||||
this.pageSubtitle = this.handleStringOrTranslation(data.pageSubtitle);
|
||||
}
|
||||
|
||||
if (data.pageIcon) {
|
||||
@@ -129,6 +135,16 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
private handleStringOrTranslation(value: string | Translation): string {
|
||||
if (typeof value === "string") {
|
||||
// If it's a string, return it as is
|
||||
return value;
|
||||
}
|
||||
|
||||
// If it's a Translation object, translate it
|
||||
return this.i18nService.t(value.key, ...(value.placeholders ?? []));
|
||||
}
|
||||
|
||||
private resetPageData() {
|
||||
this.pageTitle = null;
|
||||
this.pageSubtitle = null;
|
||||
|
||||
@@ -163,17 +163,20 @@ export const DefaultContentExample: Story = {
|
||||
|
||||
// Dynamic Content Example
|
||||
const initialData: AnonLayoutWrapperData = {
|
||||
pageTitle: "setAStrongPassword",
|
||||
pageSubtitle: "finishCreatingYourAccountBySettingAPassword",
|
||||
pageTitle: {
|
||||
key: "setAStrongPassword",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "finishCreatingYourAccountBySettingAPassword",
|
||||
},
|
||||
pageIcon: LockIcon,
|
||||
};
|
||||
|
||||
const changedData: AnonLayoutWrapperData = {
|
||||
pageTitle: "enterpriseSingleSignOn",
|
||||
pageSubtitle: {
|
||||
subtitle: "user@email.com (non-translated)",
|
||||
translate: false,
|
||||
pageTitle: {
|
||||
key: "enterpriseSingleSignOn",
|
||||
},
|
||||
pageSubtitle: "user@email.com (non-translated)",
|
||||
pageIcon: RegistrationCheckEmailIcon,
|
||||
};
|
||||
|
||||
|
||||
@@ -233,10 +233,7 @@ export class LockV2Component implements OnInit, OnDestroy {
|
||||
|
||||
private setEmailAsPageSubtitle(email: string) {
|
||||
this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
|
||||
pageSubtitle: {
|
||||
subtitle: email,
|
||||
translate: false,
|
||||
},
|
||||
pageSubtitle: email,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ export const NOTIFICATION_BAR_LIFESPAN_MS = 150000; // 150 seconds
|
||||
|
||||
export const AUTOFILL_OVERLAY_HANDLE_REPOSITION = "autofill-overlay-handle-reposition-event";
|
||||
|
||||
export const AUTOFILL_OVERLAY_HANDLE_SCROLL = "autofill-overlay-handle-scroll-event";
|
||||
|
||||
export const UPDATE_PASSKEYS_HEADINGS_ON_SCROLL = "update-passkeys-headings-on-scroll";
|
||||
|
||||
export const AUTOFILL_TRIGGER_FORM_FIELD_SUBMIT = "autofill-trigger-form-field-submit";
|
||||
|
||||
@@ -59,6 +59,24 @@ const INLINE_MENU_VISIBILITY = new KeyDefinition(
|
||||
},
|
||||
);
|
||||
|
||||
const SHOW_INLINE_MENU_IDENTITIES = new UserKeyDefinition(
|
||||
AUTOFILL_SETTINGS_DISK,
|
||||
"showInlineMenuIdentities",
|
||||
{
|
||||
deserializer: (value: boolean) => value ?? true,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
const SHOW_INLINE_MENU_CARDS = new UserKeyDefinition(
|
||||
AUTOFILL_SETTINGS_DISK,
|
||||
"showInlineMenuCards",
|
||||
{
|
||||
deserializer: (value: boolean) => value ?? true,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
const ENABLE_CONTEXT_MENU = new KeyDefinition(AUTOFILL_SETTINGS_DISK, "enableContextMenu", {
|
||||
deserializer: (value: boolean) => value ?? true,
|
||||
});
|
||||
@@ -86,6 +104,10 @@ export abstract class AutofillSettingsServiceAbstraction {
|
||||
setAutoCopyTotp: (newValue: boolean) => Promise<void>;
|
||||
inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>;
|
||||
setInlineMenuVisibility: (newValue: InlineMenuVisibilitySetting) => Promise<void>;
|
||||
showInlineMenuIdentities$: Observable<boolean>;
|
||||
setShowInlineMenuIdentities: (newValue: boolean) => Promise<void>;
|
||||
showInlineMenuCards$: Observable<boolean>;
|
||||
setShowInlineMenuCards: (newValue: boolean) => Promise<void>;
|
||||
enableContextMenu$: Observable<boolean>;
|
||||
setEnableContextMenu: (newValue: boolean) => Promise<void>;
|
||||
clearClipboardDelay$: Observable<ClearClipboardDelaySetting>;
|
||||
@@ -113,6 +135,12 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
|
||||
private inlineMenuVisibilityState: GlobalState<InlineMenuVisibilitySetting>;
|
||||
readonly inlineMenuVisibility$: Observable<InlineMenuVisibilitySetting>;
|
||||
|
||||
private showInlineMenuIdentitiesState: ActiveUserState<boolean>;
|
||||
readonly showInlineMenuIdentities$: Observable<boolean>;
|
||||
|
||||
private showInlineMenuCardsState: ActiveUserState<boolean>;
|
||||
readonly showInlineMenuCards$: Observable<boolean>;
|
||||
|
||||
private enableContextMenuState: GlobalState<boolean>;
|
||||
readonly enableContextMenu$: Observable<boolean>;
|
||||
|
||||
@@ -157,6 +185,14 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
|
||||
map((x) => x ?? AutofillOverlayVisibility.Off),
|
||||
);
|
||||
|
||||
this.showInlineMenuIdentitiesState = this.stateProvider.getActive(SHOW_INLINE_MENU_IDENTITIES);
|
||||
this.showInlineMenuIdentities$ = this.showInlineMenuIdentitiesState.state$.pipe(
|
||||
map((x) => x ?? true),
|
||||
);
|
||||
|
||||
this.showInlineMenuCardsState = this.stateProvider.getActive(SHOW_INLINE_MENU_CARDS);
|
||||
this.showInlineMenuCards$ = this.showInlineMenuCardsState.state$.pipe(map((x) => x ?? true));
|
||||
|
||||
this.enableContextMenuState = this.stateProvider.getGlobal(ENABLE_CONTEXT_MENU);
|
||||
this.enableContextMenu$ = this.enableContextMenuState.state$.pipe(map((x) => x ?? true));
|
||||
|
||||
@@ -190,6 +226,14 @@ export class AutofillSettingsService implements AutofillSettingsServiceAbstracti
|
||||
await this.inlineMenuVisibilityState.update(() => newValue);
|
||||
}
|
||||
|
||||
async setShowInlineMenuIdentities(newValue: boolean): Promise<void> {
|
||||
await this.showInlineMenuIdentitiesState.update(() => newValue);
|
||||
}
|
||||
|
||||
async setShowInlineMenuCards(newValue: boolean): Promise<void> {
|
||||
await this.showInlineMenuCardsState.update(() => newValue);
|
||||
}
|
||||
|
||||
async setEnableContextMenu(newValue: boolean): Promise<void> {
|
||||
await this.enableContextMenuState.update(() => newValue);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ or an options menu icon.
|
||||
| <i class="bwi bwi-ban"></i> | bwi-ban | option or feature not available. Example: send maximum access count was reached |
|
||||
| <i class="bwi bwi-check"></i> | bwi-check | confirmation action (Example: "confirm member"), successful confirmation (toast or callout), or shows currently selected option in a menu. Use with success color variable if applicable. |
|
||||
| <i class="bwi bwi-error"></i> | bwi-error | error; used in form field error states and error toasts, banners, and callouts. Do not use as a close or clear icon. Use with danger color variable. |
|
||||
| <i class="bwi bwi-expired"></i> | bwi-expired | - |
|
||||
| <i class="bwi bwi-exclamation-circle"></i> | bwi-exclamation-circle | deprecated error icon; use bwi-error |
|
||||
| <i class="bwi bwi-exclamation-triangle"></i> | bwi-exclamation-triangle | warning; used in warning callouts, banners, and toasts. Use with warning color variable. |
|
||||
| <i class="bwi bwi-info-circle"></i> | bwi-info-circle | information; used in info callouts, banners, and toasts. Use with info color variable. |
|
||||
@@ -25,21 +26,22 @@ or an options menu icon.
|
||||
|
||||
## Bitwarden Objects
|
||||
|
||||
| Icon | bwi-name | Usage |
|
||||
| ----------------------------------- | --------------- | --------------------------------------------------- |
|
||||
| <i class="bwi bwi-business"></i> | bwi-business | organization or vault for Free, Teams or Enterprise |
|
||||
| <i class="bwi bwi-collection"></i> | bwi-collection | collection |
|
||||
| <i class="bwi bwi-credit-card"></i> | bwi-credit-card | card item type |
|
||||
| <i class="bwi bwi-family"></i> | bwi-family | family vault or organization |
|
||||
| <i class="bwi bwi-folder"></i> | bwi-folder | folder |
|
||||
| <i class="bwi bwi-globe"></i> | bwi-globe | login item type |
|
||||
| <i class="bwi bwi-id-card"></i> | bwi-id-card | identity item type |
|
||||
| <i class="bwi bwi-send"></i> | bwi-send | send action or feature |
|
||||
| <i class="bwi bwi-send-f"></i> | bwi-send-f | - |
|
||||
| <i class="bwi bwi-sticky-note"></i> | bwi-sticky-note | secure note item type |
|
||||
| <i class="bwi bwi-users"></i> | bwi-users | user group |
|
||||
| <i class="bwi bwi-vault"></i> | bwi-vault | general vault |
|
||||
| <i class="bwi bwi-vault-f"></i> | bwi-vault-f | general vault |
|
||||
| Icon | bwi-name | Usage |
|
||||
| ------------------------------------- | ----------------- | --------------------------------------------------- |
|
||||
| <i class="bwi bwi-authenticator"></i> | bwi-authenticator | authenticator app |
|
||||
| <i class="bwi bwi-business"></i> | bwi-business | organization or vault for Free, Teams or Enterprise |
|
||||
| <i class="bwi bwi-collection"></i> | bwi-collection | collection |
|
||||
| <i class="bwi bwi-credit-card"></i> | bwi-credit-card | card item type |
|
||||
| <i class="bwi bwi-family"></i> | bwi-family | family vault or organization |
|
||||
| <i class="bwi bwi-folder"></i> | bwi-folder | folder |
|
||||
| <i class="bwi bwi-globe"></i> | bwi-globe | login item type |
|
||||
| <i class="bwi bwi-id-card"></i> | bwi-id-card | identity item type |
|
||||
| <i class="bwi bwi-send"></i> | bwi-send | send action or feature |
|
||||
| <i class="bwi bwi-send-f"></i> | bwi-send-f | - |
|
||||
| <i class="bwi bwi-sticky-note"></i> | bwi-sticky-note | secure note item type |
|
||||
| <i class="bwi bwi-users"></i> | bwi-users | user group |
|
||||
| <i class="bwi bwi-vault"></i> | bwi-vault | general vault |
|
||||
| <i class="bwi bwi-vault-f"></i> | bwi-vault-f | general vault |
|
||||
|
||||
## Actions
|
||||
|
||||
@@ -146,11 +148,21 @@ or an options menu icon.
|
||||
| <i class="bwi bwi-file"></i> | bwi-file | file related objects or actions |
|
||||
| <i class="bwi bwi-file-pdf"></i> | bwi-file-pdf | PDF related object or actions |
|
||||
| <i class="bwi bwi-file-text"></i> | bwi-file-text | text related objects or actions |
|
||||
| <i class="bwi bwi-fingerprint"></i> | bwi-fingerprint | - |
|
||||
| <i class="bwi bwi-bw-folder-open-f1"></i> | bwi-bw-folder-open-f1 | - |
|
||||
| <i class="bwi bwi-folder-closed-f"></i> | bwi-folder-closed-f | - |
|
||||
| <i class="bwi bwi-folder-open"></i> | bwi-folder-open | - |
|
||||
| <i class="bwi bwi-frown"></i> | bwi-frown | - |
|
||||
| <i class="bwi bwi-hashtag"></i> | bwi-hashtag | link to specific id |
|
||||
| <i class="bwi bwi-icon-1"></i> | bwi-icon-1 | - |
|
||||
| <i class="bwi bwi-icon-2"></i> | bwi-icon-2 | - |
|
||||
| <i class="bwi bwi-icon-3"></i> | bwi-icon-3 | - |
|
||||
| <i class="bwi bwi-icon-4"></i> | bwi-icon-4 | - |
|
||||
| <i class="bwi bwi-icon-5"></i> | bwi-icon-5 | - |
|
||||
| <i class="bwi bwi-icon-6"></i> | bwi-icon-6 | - |
|
||||
| <i class="bwi bwi-icon-7"></i> | bwi-icon-7 | - |
|
||||
| <i class="bwi bwi-icon-8"></i> | bwi-icon-8 | - |
|
||||
| <i class="bwi bwi-icon-9"></i> | bwi-icon-9 | - |
|
||||
| <i class="bwi bwi-insurance"></i> | bwi-insurance | - |
|
||||
| <i class="bwi bwi-key"></i> | bwi-key | key or password related objects or actions |
|
||||
| <i class="bwi bwi-learning"></i> | bwi-learning | learning center |
|
||||
@@ -178,6 +190,9 @@ or an options menu icon.
|
||||
| <i class="bwi bwi-tag"></i> | bwi-tag | labels |
|
||||
| <i class="bwi bwi-thumb-tack"></i> | bwi-thumb-tack | - |
|
||||
| <i class="bwi bwi-thumbs-up"></i> | bwi-thumbs-up | - |
|
||||
| <i class="bwi bwi-totp-codes"></i> | bwi-totp-codes | - |
|
||||
| <i class="bwi bwi-totp-codes-alt"></i> | bwi-totp-codes-alt | - |
|
||||
| <i class="bwi bwi-totp-codes-alt2"></i> | bwi-totp-codes-alt2 | - |
|
||||
| <i class="bwi bwi-universal-access"></i> | bwi-universal-access | use for accessibility related actions |
|
||||
| <i class="bwi bwi-user"></i> | bwi-user | relates to current user or organization member |
|
||||
| <i class="bwi bwi-user-circle"></i> | bwi-user-circle | - |
|
||||
@@ -189,27 +204,31 @@ or an options menu icon.
|
||||
|
||||
## Platforms and Logos
|
||||
|
||||
| Icon | bwi-name | Usage |
|
||||
| --------------------------------- | ------------- | ---------------------------- |
|
||||
| <i class="bwi bwi-android"></i> | bwi-android | android support |
|
||||
| <i class="bwi bwi-apple"></i> | bwi-apple | apple/IOS support |
|
||||
| <i class="bwi bwi-chrome"></i> | bwi-chrome | chrome support |
|
||||
| <i class="bwi bwi-discourse"></i> | bwi-discourse | community forum |
|
||||
| <i class="bwi bwi-edge"></i> | bwi-edge | edge support |
|
||||
| <i class="bwi bwi-facebook"></i> | bwi-facebook | link to our facebook page |
|
||||
| <i class="bwi bwi-firefox"></i> | bwi-firefox | support for firefox |
|
||||
| <i class="bwi bwi-github"></i> | bwi-github | link to our github page |
|
||||
| <i class="bwi bwi-google"></i> | bwi-google | link to our google page |
|
||||
| <i class="bwi bwi-instagram"></i> | bwi-instagram | link to our Instagram page |
|
||||
| <i class="bwi bwi-linkedin"></i> | bwi-linkedin | link to our linkedIn page |
|
||||
| <i class="bwi bwi-linux"></i> | bwi-linux | linux support |
|
||||
| <i class="bwi bwi-mastodon"></i> | bwi-mastodon | link to our Mastodon page |
|
||||
| <i class="bwi bwi-opera"></i> | bwi-opera | support for Opera |
|
||||
| <i class="bwi bwi-paypal"></i> | bwi-paypal | PayPal |
|
||||
| <i class="bwi bwi-reddit"></i> | bwi-reddit | link to our reddit community |
|
||||
| <i class="bwi bwi-safari"></i> | bwi-safari | safari support |
|
||||
| <i class="bwi bwi-twitch"></i> | bwi-twitch | link to our Twitch page |
|
||||
| <i class="bwi bwi-twitter"></i> | bwi-twitter | link to our twitter page |
|
||||
| <i class="bwi bwi-windows"></i> | bwi-windows | support for windows |
|
||||
| <i class="bwi bwi-x-twitter"></i> | bwi-x-twitter | x version of twitter |
|
||||
| <i class="bwi bwi-youtube"></i> | bwi-youtube | link to our youtube page |
|
||||
| Icon | bwi-name | Usage |
|
||||
| ---------------------------------- | -------------- | ---------------------------- |
|
||||
| <i class="bwi bwi-android"></i> | bwi-android | android support |
|
||||
| <i class="bwi bwi-apple"></i> | bwi-apple | apple/IOS support |
|
||||
| <i class="bwi bwi-brave"></i> | bwi-brave | - |
|
||||
| <i class="bwi bwi-chrome"></i> | bwi-chrome | chrome support |
|
||||
| <i class="bwi bwi-discourse"></i> | bwi-discourse | community forum |
|
||||
| <i class="bwi bwi-duckduckgo"></i> | bwi-duckduckgo | - |
|
||||
| <i class="bwi bwi-edge"></i> | bwi-edge | edge support |
|
||||
| <i class="bwi bwi-facebook"></i> | bwi-facebook | link to our facebook page |
|
||||
| <i class="bwi bwi-firefox"></i> | bwi-firefox | support for firefox |
|
||||
| <i class="bwi bwi-github"></i> | bwi-github | link to our github page |
|
||||
| <i class="bwi bwi-google"></i> | bwi-google | link to our google page |
|
||||
| <i class="bwi bwi-instagram"></i> | bwi-instagram | link to our Instagram page |
|
||||
| <i class="bwi bwi-linkedin"></i> | bwi-linkedin | link to our linkedIn page |
|
||||
| <i class="bwi bwi-linux"></i> | bwi-linux | linux support |
|
||||
| <i class="bwi bwi-mastodon"></i> | bwi-mastodon | link to our Mastodon page |
|
||||
| <i class="bwi bwi-opera"></i> | bwi-opera | support for Opera |
|
||||
| <i class="bwi bwi-paypal"></i> | bwi-paypal | PayPal |
|
||||
| <i class="bwi bwi-reddit"></i> | bwi-reddit | link to our reddit community |
|
||||
| <i class="bwi bwi-safari"></i> | bwi-safari | safari support |
|
||||
| <i class="bwi bwi-twitch"></i> | bwi-twitch | link to our Twitch page |
|
||||
| <i class="bwi bwi-twitter"></i> | bwi-twitter | link to our twitter page |
|
||||
| <i class="bwi bwi-tor"></i> | bwi-tor | - |
|
||||
| <i class="bwi bwi-vivaldi"></i> | bwi-vivaldi | - |
|
||||
| <i class="bwi bwi-windows"></i> | bwi-windows | support for windows |
|
||||
| <i class="bwi bwi-x-twitter"></i> | bwi-x-twitter | x version of twitter |
|
||||
| <i class="bwi bwi-youtube"></i> | bwi-youtube | link to our youtube page |
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@bitwarden/key-management": ["../key-management/src"],
|
||||
"@bitwarden/platform": ["../platform/src"],
|
||||
"@bitwarden/send-ui": ["../tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["../tools/card/src"],
|
||||
"@bitwarden/node/*": ["../node/src/*"],
|
||||
"@bitwarden/vault": ["../vault/src"]
|
||||
}
|
||||
|
||||
5
libs/tools/card/README.md
Normal file
5
libs/tools/card/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## Tools Card
|
||||
|
||||
Package name: `@bitwarden/tools-card`
|
||||
|
||||
Generic Tools Card Component
|
||||
13
libs/tools/card/jest.config.js
Normal file
13
libs/tools/card/jest.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const { pathsToModuleNameMapper } = require("ts-jest");
|
||||
|
||||
const { compilerOptions } = require("../../../shared/tsconfig.libs");
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
preset: "jest-preset-angular",
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/../../",
|
||||
}),
|
||||
};
|
||||
24
libs/tools/card/package.json
Normal file
24
libs/tools/card/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@bitwarden/tools-card",
|
||||
"version": "0.0.0",
|
||||
"description": "Angular card component",
|
||||
"keywords": [
|
||||
"bitwarden"
|
||||
],
|
||||
"author": "Bitwarden Inc.",
|
||||
"homepage": "https://bitwarden.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bitwarden/clients"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"build": "npm run clean && tsc",
|
||||
"build:watch": "npm run clean && tsc -watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bitwarden/common": "file:../../../common",
|
||||
"@bitwarden/components": "file:../../../components"
|
||||
}
|
||||
}
|
||||
7
libs/tools/card/src/card.component.html
Normal file
7
libs/tools/card/src/card.component.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="tw-flex-col">
|
||||
<span bitTypography="body2" class="tw-flex tw-text-muted">{{ title }}</span>
|
||||
<div class="tw-flex tw-items-baseline tw-gap-2">
|
||||
<span bitTypography="h1">{{ value }}</span>
|
||||
<span bitTypography="body2">{{ "cardMetrics" | i18n: maxValue }}</span>
|
||||
</div>
|
||||
</div>
|
||||
30
libs/tools/card/src/card.component.ts
Normal file
30
libs/tools/card/src/card.component.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { TypographyModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "tools-card",
|
||||
templateUrl: "./card.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, TypographyModule, JslibModule],
|
||||
host: {
|
||||
class:
|
||||
"tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-border [&:not(bit-layout_*)]:tw-rounded-lg tw-p-6",
|
||||
},
|
||||
})
|
||||
export class CardComponent {
|
||||
/**
|
||||
* The title of the card
|
||||
*/
|
||||
@Input() title: string;
|
||||
/**
|
||||
* The current value of the card as emphasized text
|
||||
*/
|
||||
@Input() value: number;
|
||||
/**
|
||||
* The maximum value of the card
|
||||
*/
|
||||
@Input() maxValue: number;
|
||||
}
|
||||
36
libs/tools/card/src/card.stories.ts
Normal file
36
libs/tools/card/src/card.stories.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { I18nMockService, TypographyModule } from "@bitwarden/components";
|
||||
|
||||
import { CardComponent } from "./card.component";
|
||||
|
||||
export default {
|
||||
title: "Toools/Card",
|
||||
component: CardComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [CardComponent, CommonModule, TypographyModule],
|
||||
providers: [
|
||||
{
|
||||
provide: I18nService,
|
||||
useFactory: () =>
|
||||
new I18nMockService({
|
||||
cardMetrics: (value) => `out of ${value}`,
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
} as Meta;
|
||||
|
||||
type Story = StoryObj<CardComponent>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<tools-card [title]="'Unsecured Members'" [value]="'38'" [maxValue]="'157'"></tools-card>`,
|
||||
}),
|
||||
};
|
||||
1
libs/tools/card/src/index.ts
Normal file
1
libs/tools/card/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { CardComponent } from "./card.component";
|
||||
1
libs/tools/card/test.setup.ts
Normal file
1
libs/tools/card/test.setup.ts
Normal file
@@ -0,0 +1 @@
|
||||
import "jest-preset-angular/setup-jest";
|
||||
5
libs/tools/card/tsconfig.json
Normal file
5
libs/tools/card/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../shared/tsconfig.libs",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
6
libs/tools/card/tsconfig.spec.json
Normal file
6
libs/tools/card/tsconfig.spec.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src"],
|
||||
"files": ["./test.setup.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<div class="tw-grow tw-flex tw-items-center">
|
||||
<bit-color-password class="tw-font-mono" [password]="value$ | async"></bit-color-password>
|
||||
</div>
|
||||
<div class="tw-space-x-1">
|
||||
<div class="tw-flex tw-items-center tw-space-x-1">
|
||||
<button type="button" bitIconButton="bwi-generate" buttonType="main" (click)="generate$.next()">
|
||||
{{ "generatePassword" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
SectionHeaderComponent,
|
||||
SelectModule,
|
||||
ToggleGroupModule,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import {
|
||||
createRandomizer,
|
||||
@@ -55,6 +56,7 @@ const RANDOMIZER = new SafeInjectionToken<Randomizer>("Randomizer");
|
||||
SectionHeaderComponent,
|
||||
SelectModule,
|
||||
ToggleGroupModule,
|
||||
TypographyModule,
|
||||
],
|
||||
providers: [
|
||||
safeProvider({
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="tw-grow tw-flex tw-items-center">
|
||||
<bit-color-password class="tw-font-mono" [password]="value$ | async"></bit-color-password>
|
||||
</div>
|
||||
<div class="tw-space-x-1">
|
||||
<div class="tw-flex tw-items-center tw-space-x-1">
|
||||
<button type="button" bitIconButton="bwi-generate" buttonType="main" (click)="generate$.next()">
|
||||
{{ "generatePassword" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<bit-section>
|
||||
<bit-section-header *ngIf="showHeader">
|
||||
<h6 bitTypography="h6">{{ "options" | i18n }}</h6>
|
||||
<h2 bitTypography="h6">{{ "options" | i18n }}</h2>
|
||||
</bit-section-header>
|
||||
<form class="box" [formGroup]="settings" class="tw-container">
|
||||
<div class="tw-mb-4">
|
||||
@@ -55,7 +55,7 @@
|
||||
</bit-form-control>
|
||||
</div>
|
||||
<div class="tw-flex">
|
||||
<bit-form-field class="tw-basis-1/2 tw-mr-4">
|
||||
<bit-form-field class="tw-w-full tw-basis-1/2 tw-mr-4">
|
||||
<bit-label>{{ "minNumbers" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
@@ -65,7 +65,7 @@
|
||||
formControlName="minNumber"
|
||||
/>
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-basis-1/2">
|
||||
<bit-form-field class="tw-w-full tw-basis-1/2">
|
||||
<bit-label>{{ "minSpecial" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="tw-grow tw-flex tw-items-center">
|
||||
<bit-color-password class="tw-font-mono" [password]="value$ | async"></bit-color-password>
|
||||
</div>
|
||||
<div class="tw-space-x-1">
|
||||
<div class="tw-flex tw-items-center tw-space-x-1">
|
||||
<button type="button" bitIconButton="bwi-generate" buttonType="main" (click)="generate$.next()">
|
||||
{{ "generatePassword" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
>
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label *ngIf="!originalSendView || !hasPassword">{{ "password" | i18n }}</bit-label>
|
||||
<bit-label *ngIf="originalSendView && hasPassword">{{ "newPassword" | i18n }}</bit-label>
|
||||
<bit-label *ngIf="!shouldShowNewPassword">{{ "password" | i18n }}</bit-label>
|
||||
<bit-label *ngIf="shouldShowNewPassword">{{ "newPassword" | i18n }}</bit-label>
|
||||
<input bitInput type="password" formControlName="password" />
|
||||
<button
|
||||
data-testid="toggle-visibility-for-password"
|
||||
|
||||
@@ -53,10 +53,8 @@ export class SendOptionsComponent implements OnInit {
|
||||
hideEmail: [false as boolean],
|
||||
});
|
||||
|
||||
get hasPassword(): boolean {
|
||||
return (
|
||||
this.sendOptionsForm.value.password !== null && this.sendOptionsForm.value.password !== ""
|
||||
);
|
||||
get shouldShowNewPassword(): boolean {
|
||||
return this.originalSendView && this.originalSendView.password !== null;
|
||||
}
|
||||
|
||||
get shouldShowCount(): boolean {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BehaviorSubject, first } from "rxjs";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -46,16 +46,6 @@ describe("SendListFiltersService", () => {
|
||||
expect(service.sendTypes.map((c) => c.value)).toEqual([SendType.File, SendType.Text]);
|
||||
});
|
||||
|
||||
it("filters disabled sends", (done) => {
|
||||
const sends = [{ disabled: true }, { disabled: false }, { disabled: true }] as SendView[];
|
||||
service.filterFunction$.pipe(first()).subscribe((filterFunction) => {
|
||||
expect(filterFunction(sends)).toEqual([sends[1]]);
|
||||
done();
|
||||
});
|
||||
|
||||
service.filterForm.patchValue({});
|
||||
});
|
||||
|
||||
it("resets the filter form", () => {
|
||||
service.filterForm.patchValue({ sendType: SendType.Text });
|
||||
service.resetFilterForm();
|
||||
|
||||
@@ -44,11 +44,6 @@ export class SendListFiltersService {
|
||||
map(
|
||||
(filters) => (sends: SendView[]) =>
|
||||
sends.filter((send) => {
|
||||
// do not show disabled sends
|
||||
if (send.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filters.sendType !== null && send.type !== filters.sendType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -58,7 +58,7 @@
|
||||
"node-fetch": "2.6.12",
|
||||
"node-forge": "1.3.1",
|
||||
"nord": "0.2.1",
|
||||
"oidc-client-ts": "2.4.0",
|
||||
"oidc-client-ts": "2.4.1",
|
||||
"open": "8.4.2",
|
||||
"papaparse": "5.4.1",
|
||||
"patch-package": "8.0.0",
|
||||
@@ -68,7 +68,7 @@
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tabbable": "6.2.0",
|
||||
"tldts": "6.1.48",
|
||||
"tldts": "6.1.51",
|
||||
"utf-8-validate": "6.0.4",
|
||||
"zone.js": "0.13.3",
|
||||
"zxcvbn": "4.4.2"
|
||||
@@ -110,7 +110,7 @@
|
||||
"@types/koa-json": "2.0.23",
|
||||
"@types/lowdb": "1.0.15",
|
||||
"@types/lunr": "2.3.7",
|
||||
"@types/node": "20.16.10",
|
||||
"@types/node": "20.16.11",
|
||||
"@types/node-fetch": "2.6.4",
|
||||
"@types/node-forge": "1.3.11",
|
||||
"@types/node-ipc": "9.2.3",
|
||||
@@ -225,7 +225,7 @@
|
||||
"papaparse": "5.4.1",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tldts": "6.1.48",
|
||||
"tldts": "6.1.51",
|
||||
"zxcvbn": "4.4.2"
|
||||
},
|
||||
"bin": {
|
||||
@@ -9646,9 +9646,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.16.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
|
||||
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
|
||||
"version": "20.16.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
|
||||
"integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -29879,9 +29879,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/oidc-client-ts": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz",
|
||||
"integrity": "sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.1.tgz",
|
||||
"integrity": "sha512-IxlGMsbkZPsHJGCliWT3LxjUcYzmiN21656n/Zt2jDncZlBFc//cd8WqFF0Lt681UT3AImM57E6d4N53ziTCYA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.2.0",
|
||||
@@ -36519,21 +36519,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.48",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.48.tgz",
|
||||
"integrity": "sha512-SPbnh1zaSzi/OsmHb1vrPNnYuwJbdWjwo5TbBYYMlTtH3/1DSb41t8bcSxkwDmmbG2q6VLPVvQc7Yf23T+1EEw==",
|
||||
"version": "6.1.51",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.51.tgz",
|
||||
"integrity": "sha512-33lfQoL0JsDogIbZ8fgRyvv77GnRtwkNE/MOKocwUgPO1WrSfsq7+vQRKxRQZai5zd+zg97Iv9fpFQSzHyWdLA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^6.1.48"
|
||||
"tldts-core": "^6.1.51"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "6.1.50",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.50.tgz",
|
||||
"integrity": "sha512-na2EcZqmdA2iV9zHV7OHQDxxdciEpxrjbkp+aHmZgnZKHzoElLajP59np5/4+sare9fQBfixgvXKx8ev1d7ytw==",
|
||||
"version": "6.1.51",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.51.tgz",
|
||||
"integrity": "sha512-bu9oCYYWC1iRjx+3UnAjqCsfrWNZV1ghNQf49b3w5xE8J/tNShHTzp5syWJfwGH+pxUgTTLUnzHnfuydW7wmbg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"@types/koa-json": "2.0.23",
|
||||
"@types/lowdb": "1.0.15",
|
||||
"@types/lunr": "2.3.7",
|
||||
"@types/node": "20.16.10",
|
||||
"@types/node": "20.16.11",
|
||||
"@types/node-fetch": "2.6.4",
|
||||
"@types/node-forge": "1.3.11",
|
||||
"@types/node-ipc": "9.2.3",
|
||||
@@ -192,7 +192,7 @@
|
||||
"node-fetch": "2.6.12",
|
||||
"node-forge": "1.3.1",
|
||||
"nord": "0.2.1",
|
||||
"oidc-client-ts": "2.4.0",
|
||||
"oidc-client-ts": "2.4.1",
|
||||
"open": "8.4.2",
|
||||
"papaparse": "5.4.1",
|
||||
"patch-package": "8.0.0",
|
||||
@@ -202,7 +202,7 @@
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tabbable": "6.2.0",
|
||||
"tldts": "6.1.48",
|
||||
"tldts": "6.1.51",
|
||||
"utf-8-validate": "6.0.4",
|
||||
"zone.js": "0.13.3",
|
||||
"zxcvbn": "4.4.2"
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"@bitwarden/importer/core": ["./libs/importer/src"],
|
||||
"@bitwarden/importer/ui": ["./libs/importer/src/components"],
|
||||
"@bitwarden/send-ui": [".libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": [".libs/tools/card/src"],
|
||||
"@bitwarden/platform": ["./libs/platform/src"],
|
||||
"@bitwarden/node/*": ["./libs/node/src/*"],
|
||||
"@bitwarden/vault": ["./libs/vault/src"],
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@bitwarden/key-management": ["./libs/key-management/src"],
|
||||
"@bitwarden/platform": ["./libs/platform/src"],
|
||||
"@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["./libs/tools/card/src"],
|
||||
"@bitwarden/node/*": ["./libs/node/src/*"],
|
||||
"@bitwarden/web-vault/*": ["./apps/web/src/*"],
|
||||
"@bitwarden/vault": ["./libs/vault/src"],
|
||||
@@ -51,6 +52,7 @@
|
||||
"apps/browser/src/**/*",
|
||||
"libs/*/src/**/*",
|
||||
"libs/tools/send/**/src/**/*",
|
||||
"libs/tools/card/src/**/*",
|
||||
"bitwarden_license/bit-web/src/**/*",
|
||||
"bitwarden_license/bit-common/src/**/*"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user