diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2a692715fdf..081fecf1310 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -185,5 +185,8 @@ apps/web/src/locales/en/messages.json **/entrypoint.sh ## Overrides -# tsconfig files are potentially dangerous and will be reviewed by platform to prevent misconfigurations +# For the time being platform owns tsconfig and jest config +# These overrides will be removed after Nx is implemented +# To track that effort please see https://bitwarden.atlassian.net/browse/PM-21636 **/tsconfig.json @bitwarden/team-platform-dev +**/jest.config.js @bitwarden/team-platform-dev diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e5cd47077fb..d0066ddd7ba 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -222,7 +222,6 @@ "@types/chrome", "@types/firefox-webext-browser", "@types/glob", - "@types/jquery", "@types/lowdb", "@types/node", "@types/node-forge", @@ -330,9 +329,7 @@ "autoprefixer", "bootstrap", "chromatic", - "jquery", "ngx-toastr", - "popper.js", "react", "react-dom", "remark-gfm", diff --git a/.github/workflows/build-browser-target.yml b/.github/workflows/build-browser-target.yml index 6f05cb71934..a2ae48d419b 100644 --- a/.github/workflows/build-browser-target.yml +++ b/.github/workflows/build-browser-target.yml @@ -8,10 +8,9 @@ name: Build Browser on PR Target on: pull_request_target: - types: [opened, synchronize] - branches-ignore: - - 'l10n_master' - - 'cf-pages' + types: [opened, synchronize, reopened] + branches: + - main paths: - 'apps/browser/**' - 'libs/**' diff --git a/.github/workflows/build-cli-target.yml b/.github/workflows/build-cli-target.yml index f817046ff30..6b493d4e6d9 100644 --- a/.github/workflows/build-cli-target.yml +++ b/.github/workflows/build-cli-target.yml @@ -8,10 +8,9 @@ name: Build CLI on PR Target on: pull_request_target: - types: [opened, synchronize] - branches-ignore: - - 'l10n_master' - - 'cf-pages' + types: [opened, synchronize, reopened] + branches: + - main paths: - 'apps/cli/**' - 'libs/**' diff --git a/.github/workflows/build-desktop-target.yml b/.github/workflows/build-desktop-target.yml index 65772223722..fa21b3fe5d9 100644 --- a/.github/workflows/build-desktop-target.yml +++ b/.github/workflows/build-desktop-target.yml @@ -9,10 +9,9 @@ name: Build Desktop on PR Target on: pull_request_target: - types: [opened, synchronize] - branches-ignore: - - 'l10n_master' - - 'cf-pages' + types: [opened, synchronize, reopened] + branches: + - main paths: - 'apps/desktop/**' - 'libs/**' diff --git a/.github/workflows/build-web-target.yml b/.github/workflows/build-web-target.yml index 8f06d066d34..ca10e6d46f2 100644 --- a/.github/workflows/build-web-target.yml +++ b/.github/workflows/build-web-target.yml @@ -8,10 +8,9 @@ name: Build Web on PR Target on: pull_request_target: - types: [opened, synchronize] - branches-ignore: - - 'l10n_master' - - 'cf-pages' + types: [opened, synchronize, reopened] + branches: + - main paths: - 'apps/web/**' - 'libs/**' diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index f436f1b3760..47f3b310504 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -73,7 +73,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@8a12962215a66cd05b1ac5b0f1c08768d1aab155 # v11.25.0 + uses: chromaui/action@e8cc4c31775280b175a3c440076c00d19a9014d7 # v11.28.2 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index 77b66ba8bf1..585115ef6dd 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -7,8 +7,14 @@ on: - "main" - "rc" - "hotfix-rc" + pull_request: + types: [opened, synchronize, reopened] + branches-ignore: + - main pull_request_target: - types: [opened, synchronize] + types: [opened, synchronize, reopened] + branches: + - "main" jobs: check-run: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 50f04ebaeb1..64cc86f1db6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ on: - "rc" - "hotfix-rc-*" pull_request: - types: [opened, synchronize] + types: [ opened, synchronize ] jobs: @@ -66,12 +66,15 @@ jobs: reporter: jest-junit fail-on-error: true - - name: Upload coverage to codecov.io - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 - - name: Upload results to codecov.io uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0 + - name: Upload test coverage + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: jest-coverage + path: ./coverage/lcov.info + rust: name: Run Rust tests on ${{ matrix.os }} runs-on: ${{ matrix.os || 'ubuntu-22.04' }} @@ -148,7 +151,37 @@ jobs: working-directory: ./apps/desktop/desktop_native run: cargo llvm-cov --all-features --lcov --output-path lcov.info --workspace --no-cfg-coverage - - name: Upload to codecov.io + - name: Upload test coverage + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: rust-coverage + path: ./apps/desktop/desktop_native/lcov.info + + upload-codecov: + name: Upload to Codecov + runs-on: ubuntu-22.04 + needs: + - testing + - rust-coverage + steps: + - name: Check out repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Download jest coverage + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: jest-coverage + path: ./ + + - name: Download rust coverage + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: rust-coverage + path: ./apps/desktop/desktop_native + + - name: Upload coverage to codecov.io uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 with: - files: ./apps/desktop/desktop_native/lcov.info + files: | + ./lcov.info + ./apps/desktop/desktop_native/lcov.info diff --git a/angular.json b/angular.json index 665d810cf4e..87ee7dc57b2 100644 --- a/angular.json +++ b/angular.json @@ -6,6 +6,32 @@ "analytics": false }, "projects": { + "bit-web": { + "projectType": "application", + "schematics": { + "@schematics/angular:application": { + "strict": true + } + }, + "root": "bitwarden_license/bit-web", + "sourceRoot": "bitwarden_license/bit-web/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/web", + "index": "apps/web/src/index.html", + "main": "bitwarden_license/bit-web/src/app/main.ts", + "polyfills": "apps/web/src/polyfills.ts", + "tsConfig": "bitwarden_license/bit-web/tsconfig.json", + "assets": ["apps/web/src/favicon.ico"], + "styles": [], + "scripts": [] + } + } + } + }, "web": { "projectType": "application", "schematics": { @@ -22,8 +48,8 @@ "options": { "outputPath": "dist/web", "index": "apps/web/src/index.html", - "main": "apps/web/src/app/main.ts", - "polyfills": "apps/web/src/app/polyfills.ts", + "main": "apps/web/src/main.ts", + "polyfills": "apps/web/src/polyfills.ts", "tsConfig": "apps/web/tsconfig.json", "assets": ["apps/web/src/favicon.ico"], "styles": [], diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 290fce30e52..5ea9a46bed5 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1090,13 +1094,24 @@ }, "notificationLoginSaveConfirmation": { "message": "saved to Bitwarden.", - "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { "message": "updated in Bitwarden.", "description": "Shown to user after item is updated." }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } + }, "saveAsNewLoginAction": { "message": "Save as new login", "description": "Button text for saving login details as a new entry." @@ -1105,6 +1120,10 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, "saveLogin": { "message": "Save login", "description": "Prompt asking the user if they want to save their login details." @@ -2208,15 +2227,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -3621,7 +3631,7 @@ "orgTrustWarning1": { "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, - "trustUser":{ + "trustUser": { "message": "Trust user" }, "sendsNoItemsTitle": { @@ -5285,6 +5295,9 @@ "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." }, + "nudgeBadgeAria": { + "message": "1 notification" + }, "emptyVaultNudgeTitle": { "message": "Import existing passwords" }, @@ -5297,8 +5310,14 @@ "hasItemsVaultNudgeTitle": { "message": "Welcome to your vault!" }, - "hasItemsVaultNudgeBody": { - "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" }, "newLoginNudgeTitle": { "message": "Save time with autofill" @@ -5348,5 +5367,8 @@ "message": "Learn more about SSH agent", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index a0990485d49..78ca577a69d 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -168,18 +168,21 @@ type Story = StoryObj; @Component({ selector: "bit-default-primary-outlet-example-component", template: "

Primary Outlet Example:
your primary component goes here

", + standalone: false, }) class DefaultPrimaryOutletExampleComponent {} @Component({ selector: "bit-default-secondary-outlet-example-component", template: "

Secondary Outlet Example:
your secondary component goes here

", + standalone: false, }) class DefaultSecondaryOutletExampleComponent {} @Component({ selector: "bit-default-env-selector-outlet-example-component", template: "

Env Selector Outlet Example:
your env selector component goes here

", + standalone: false, }) class DefaultEnvSelectorOutletExampleComponent {} @@ -264,6 +267,7 @@ const changedData: ExtensionAnonLayoutWrapperData = { template: ` `, + standalone: false, }) export class DynamicContentExampleComponent { initialData = true; diff --git a/apps/browser/src/auth/popup/set-password.component.ts b/apps/browser/src/auth/popup/set-password.component.ts index accde2e9a09..2a796854531 100644 --- a/apps/browser/src/auth/popup/set-password.component.ts +++ b/apps/browser/src/auth/popup/set-password.component.ts @@ -5,5 +5,6 @@ import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/ang @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent {} diff --git a/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts b/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts index c56e6578a0b..25a4d01333d 100644 --- a/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts +++ b/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts @@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b useExisting: VaultTimeoutInputComponent, }, ], + standalone: false, }) export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} diff --git a/apps/browser/src/auth/popup/update-temp-password.component.ts b/apps/browser/src/auth/popup/update-temp-password.component.ts index 465bc3f7038..e8cf64b7548 100644 --- a/apps/browser/src/auth/popup/update-temp-password.component.ts +++ b/apps/browser/src/auth/popup/update-temp-password.component.ts @@ -8,6 +8,7 @@ import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { onSuccessfulChangePassword: () => Promise = this.doOnSuccessfulChangePassword.bind(this); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 0f59d2225bb..cb495af4755 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -946,9 +946,7 @@ export default class NotificationBackground { private async getDecryptedCipherById(cipherId: string, userId: UserId) { const cipher = await this.cipherService.get(cipherId, userId); if (cipher != null && cipher.type === CipherType.Login) { - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), - ); + return await this.cipherService.decrypt(cipher, userId); } return null; } diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts index 722f07e8e76..74ac2518226 100644 --- a/apps/browser/src/autofill/content/components/buttons/action-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts @@ -74,6 +74,10 @@ const actionButtonStyles = ({ background-color: ${themes[theme].primary["700"]}; color: ${themes[theme].text.contrast}; } + :focus { + outline: 2px solid ${themes[theme].primary["600"]}; + outline-offset: 1px; + } `} svg { diff --git a/apps/browser/src/autofill/content/components/buttons/badge-button.ts b/apps/browser/src/autofill/content/components/buttons/badge-button.ts index 9852c07d47b..3cdd453ee1a 100644 --- a/apps/browser/src/autofill/content/components/buttons/badge-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/badge-button.ts @@ -8,15 +8,19 @@ import { border, themes, typography, spacing } from "../constants/styles"; export type BadgeButtonProps = { buttonAction: (e: Event) => void; buttonText: string; + itemName: string; disabled?: boolean; theme: Theme; + username?: string; }; export function BadgeButton({ buttonAction, buttonText, disabled = false, + itemName, theme, + username, }: BadgeButtonProps) { const handleButtonClick = (event: Event) => { if (!disabled) { @@ -28,6 +32,7 @@ export function BadgeButton({ `; diff --git a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts index e3c7e0d54e6..3912c791d34 100644 --- a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts @@ -33,6 +33,9 @@ export function OptionSelectionButton({ class=${selectionButtonStyles({ disabled, toggledOn, theme })} title=${text} type="button" + aria-haspopup="menu" + aria-expanded=${toggledOn} + aria-controls="option-menu" @click=${handleButtonClick} > ${buttonIcon ?? nothing} diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts index 94bfa2c73c1..34ad5e1c9a9 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts @@ -8,8 +8,10 @@ import { I18n } from "../common-types"; export type CipherActionProps = { handleAction?: (e: Event) => void; i18n: I18n; + itemName: string; notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add; theme: Theme; + username?: string; }; export function CipherAction({ @@ -17,14 +19,18 @@ export function CipherAction({ /* no-op */ }, i18n, + itemName, notificationType, theme, + username, }: CipherActionProps) { return notificationType === NotificationTypes.Change ? BadgeButton({ buttonAction: handleAction, buttonText: i18n.notificationUpdate, + itemName, theme, + username, }) : EditButton({ buttonAction: handleAction, diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts index c290b68a291..ab3b57f535c 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts @@ -32,14 +32,21 @@ export function CipherItem({ notificationType, theme = ThemeTypes.Light, }: CipherItemProps) { - const { icon } = cipher; + const { icon, name, login } = cipher; const uri = (icon.imageEnabled && icon.image) || undefined; let cipherActionButton = null; if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) { cipherActionButton = html`
- ${CipherAction({ handleAction, i18n, notificationType, theme })} + ${CipherAction({ + handleAction, + i18n, + itemName: name, + notificationType, + theme, + username: login?.username, + })}
`; } diff --git a/apps/browser/src/autofill/content/components/common-types.ts b/apps/browser/src/autofill/content/components/common-types.ts index 740b6963b16..5967f6205a9 100644 --- a/apps/browser/src/autofill/content/components/common-types.ts +++ b/apps/browser/src/autofill/content/components/common-types.ts @@ -11,6 +11,7 @@ export type IconProps = { color?: string; disabled?: boolean; theme: Theme; + ariaHidden?: boolean; }; export type Option = { diff --git a/apps/browser/src/autofill/content/components/icons/angle-down.ts b/apps/browser/src/autofill/content/components/icons/angle-down.ts index 27cd5ab81c5..2bd54f7dbee 100644 --- a/apps/browser/src/autofill/content/components/icons/angle-down.ts +++ b/apps/browser/src/autofill/content/components/icons/angle-down.ts @@ -4,11 +4,16 @@ import { html } from "lit"; import { IconProps } from "../common-types"; import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; -export function AngleDown({ color, disabled, theme }: IconProps) { +export function AngleDown({ ariaHidden = true, color, disabled, theme }: IconProps) { const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; return html` - + + + + + + + + + +
${NotificationHeader({ handleCloseNotification, + i18n, message: headerMessage, theme, })} diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 6dc0a1e0f7c..2c4a117f588 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -8,6 +8,7 @@ import { spacing, themes, typography } from "../../constants/styles"; export type NotificationConfirmationMessageProps = { buttonAria?: string; buttonText?: string; + error?: string; itemName?: string; message?: string; messageDetails?: string; @@ -18,6 +19,7 @@ export type NotificationConfirmationMessageProps = { export function NotificationConfirmationMessage({ buttonAria, buttonText, + error, itemName, message, messageDetails, @@ -29,7 +31,11 @@ export function NotificationConfirmationMessage({ ${message || buttonText ? html`
- ${itemName} + ${!error && itemName + ? html` + ${itemName} + ` + : nothing} ${NotificationHeader({ handleCloseNotification, + i18n, message: headerMessage, - standalone: showBody, theme, })} ${showBody @@ -65,7 +65,7 @@ export function NotificationContainer({ theme, i18n, }) - : null} + : nothing} ${NotificationFooter({ handleSaveAction, collections, @@ -106,7 +106,7 @@ function getHeaderMessage(i18n: I18n, type?: NotificationType) { case NotificationTypes.Change: return i18n.updateLogin; case NotificationTypes.Unlock: - return ""; + return i18n.unlockToSave; default: return undefined; } diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts index 655fcafcc81..071f2579543 100644 --- a/apps/browser/src/autofill/content/components/notification/footer.ts +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -35,7 +35,13 @@ export function NotificationFooter({ handleSaveAction, }: NotificationFooterProps) { const isChangeNotification = notificationType === NotificationTypes.Change; - const primaryButtonText = i18n.saveAction; + const isUnlockNotification = notificationType === NotificationTypes.Unlock; + + let primaryButtonText = i18n.saveAction; + + if (isUnlockNotification) { + primaryButtonText = i18n.notificationUnlock; + } return html`
diff --git a/apps/browser/src/autofill/content/components/notification/header.ts b/apps/browser/src/autofill/content/components/notification/header.ts index 05ac5693e78..3d657b77ecd 100644 --- a/apps/browser/src/autofill/content/components/notification/header.ts +++ b/apps/browser/src/autofill/content/components/notification/header.ts @@ -4,6 +4,7 @@ import { html } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { CloseButton } from "../buttons/close-button"; +import { I18n } from "../common-types"; import { spacing, themes } from "../constants/styles"; import { BrandIconContainer } from "../icons/brand-icon-container"; @@ -16,6 +17,7 @@ const { css } = createEmotion({ }); export type NotificationHeaderProps = { + i18n: I18n; message?: string; standalone?: boolean; theme: Theme; @@ -23,6 +25,7 @@ export type NotificationHeaderProps = { }; export function NotificationHeader({ + i18n, message, standalone = false, theme = ThemeTypes.Light, @@ -35,7 +38,7 @@ export function NotificationHeader({
${showIcon ? BrandIconContainer({ theme }) : null} ${message ? NotificationHeaderMessage({ message, theme }) : null} - ${isDismissable ? CloseButton({ handleCloseNotification, theme }) : null} + ${isDismissable ? CloseButton({ handleCloseNotification, i18n, theme }) : null}
`; } @@ -56,8 +59,8 @@ const notificationHeaderStyles = ({ white-space: nowrap; ${standalone - ? css` + ? css`` + : css` border-bottom: 0.5px solid ${themes[theme].secondary["300"]}; - ` - : css``} + `} `; diff --git a/apps/browser/src/autofill/content/components/option-selection/option-item.ts b/apps/browser/src/autofill/content/components/option-selection/option-item.ts index 08017da15e8..2316a178eb3 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-item.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-item.ts @@ -13,11 +13,19 @@ const { css } = createEmotion({ }); export type OptionItemProps = Option & { + contextLabel?: string; theme: Theme; handleSelection: () => void; }; -export function OptionItem({ icon, text, value, theme, handleSelection }: OptionItemProps) { +export function OptionItem({ + contextLabel, + icon, + text, + theme, + value, + handleSelection, +}: OptionItemProps) { const handleSelectionKeyUpProxy = (event: KeyboardEvent) => { const listenedForKeys = new Set(["Enter", "Space"]); if (listenedForKeys.has(event.code) && event.target instanceof Element) { @@ -29,12 +37,18 @@ export function OptionItem({ icon, text, value, theme, handleSelection }: Option const iconProps: IconProps = { color: themes[theme].text.main, theme }; const itemIcon = icon?.(iconProps); + const ariaLabel = + contextLabel && text + ? chrome.i18n.getMessage("selectItemAriaLabel", [contextLabel, text]) + : text; return html`
diff --git a/apps/browser/src/autofill/content/components/option-selection/option-items.ts b/apps/browser/src/autofill/content/components/option-selection/option-items.ts index bb7f5afd0fd..854b7c05df1 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-items.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-items.ts @@ -33,17 +33,41 @@ export function OptionItems({ const isSafari = false; return html` -
+
handleMenuKeyUp(e)} + > ${label ? html`
${label}
` : nothing}
${options.map((option) => - OptionItem({ ...option, theme, handleSelection: () => handleOptionSelection(option) }), + OptionItem({ + ...option, + theme, + contextLabel: label, + handleSelection: () => handleOptionSelection(option), + }), )}
`; } +function handleMenuKeyUp(event: KeyboardEvent) { + const items = [ + ...(event.currentTarget as HTMLElement).querySelectorAll('[tabindex="0"]'), + ]; + const index = items.indexOf(document.activeElement as HTMLElement); + const direction = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0; + + if (index === -1 || direction === 0) { + return; + } + + event.preventDefault(); + items[(index + direction + items.length) % items.length]?.focus(); +} + const optionsStyles = ({ theme, topOffset }: { theme: Theme; topOffset: number }) => css` ${typography.body1} diff --git a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts index 49b51852a39..c7dceb2b5b4 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts @@ -48,10 +48,18 @@ export class OptionSelection extends LitElement { @state() private selection?: Option; - private handleButtonClick = (event: Event) => { + private static currentOpenInstance: OptionSelection | null = null; + + private handleButtonClick = async (event: Event) => { if (!this.disabled) { - // Menu is about to be shown - if (!this.showMenu) { + const isOpening = !this.showMenu; + + if (isOpening) { + if (OptionSelection.currentOpenInstance && OptionSelection.currentOpenInstance !== this) { + OptionSelection.currentOpenInstance.showMenu = false; + } + OptionSelection.currentOpenInstance = this; + this.menuTopOffset = this.offsetTop; // Distance from right edge of button to left edge of the viewport @@ -71,9 +79,29 @@ export class OptionSelection extends LitElement { optionsMenuItemMaxWidth + optionItemIconWidth + 2 + 8 + 12 * 2; this.menuIsEndJustified = distanceFromViewportRightEdge < maxDifferenceThreshold; + } else { + if (OptionSelection.currentOpenInstance === this) { + OptionSelection.currentOpenInstance = null; + } } - this.showMenu = !this.showMenu; + this.showMenu = isOpening; + + if (this.showMenu) { + await this.updateComplete; + const firstItem = this.querySelector('#option-menu [tabindex="0"]') as HTMLElement; + firstItem?.focus(); + } + } + }; + + private handleFocusOut = (event: FocusEvent) => { + const relatedTarget = event.relatedTarget; + if (!(relatedTarget instanceof Node) || !this.contains(relatedTarget)) { + this.showMenu = false; + if (OptionSelection.currentOpenInstance === this) { + OptionSelection.currentOpenInstance = null; + } } }; @@ -95,7 +123,10 @@ export class OptionSelection extends LitElement { } return html` -
+
${OptionSelectionButton({ disabled: this.disabled, icon: this.selection?.icon, diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 62b87ced032..3ea071cf61d 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -19,6 +19,7 @@ import { NotificationBarWindowMessage, NotificationBarIframeInitData, NotificationType, + NotificationTypes, } from "./abstractions/notification-bar"; const logService = new ConsoleLogService(false); @@ -89,6 +90,7 @@ function getI18n() { saveFailureDetails: chrome.i18n.getMessage("saveFailureDetails"), saveLogin: chrome.i18n.getMessage("saveLogin"), typeLogin: chrome.i18n.getMessage("typeLogin"), + unlockToSave: chrome.i18n.getMessage("unlockToSave"), updateLogin: chrome.i18n.getMessage("updateLogin"), updateLoginAction: chrome.i18n.getMessage("updateLoginAction"), vault: chrome.i18n.getMessage("vault"), @@ -154,6 +156,26 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { // Current implementations utilize a require for scss files which creates the need to remove the node. document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove()); + if (isVaultLocked) { + return render( + NotificationContainer({ + ...notificationBarIframeInitData, + type: NotificationTypes.Unlock, + theme: resolvedTheme, + personalVaultIsAllowed: !personalVaultDisallowed, + handleCloseNotification, + handleSaveAction: (e) => { + sendSaveCipherMessage(true); + + // @TODO can't close before vault has finished decrypting, but can't leave open during long decrypt because it looks like the experience has failed + }, + handleEditOrUpdateAction, + i18n, + }), + document.body, + ); + } + const orgId = selectedVaultSignal.get(); await Promise.all([ diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index fe0bc217029..31a45118a6c 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -20,7 +20,7 @@ export class OverlayNotificationsContentService private notificationBarIframeElement: HTMLIFrameElement | null = null; private currentNotificationBarType: string | null = null; private removeTabFromNotificationQueueTypes = new Set(["add", "change"]); - private notificationRefreshFlag: boolean; + private notificationRefreshFlag: boolean = false; private notificationBarElementStyles: Partial = { height: "82px", width: "430px", @@ -60,6 +60,7 @@ export class OverlayNotificationsContentService void sendExtensionMessage("checkNotificationQueue"); void sendExtensionMessage("notificationRefreshFlagValue").then((notificationRefreshFlag) => { this.notificationRefreshFlag = !!notificationRefreshFlag; + this.setNotificationRefreshBarHeight(); }); } @@ -228,15 +229,31 @@ export class OverlayNotificationsContentService this.notificationBarElement.id = "bit-notification-bar"; setElementStyles(this.notificationBarElement, this.notificationBarElementStyles, true); - - if (this.notificationRefreshFlag) { - setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true); - } + this.setNotificationRefreshBarHeight(); this.notificationBarElement.appendChild(this.notificationBarIframeElement); } } + /** + * Sets the height of the notification bar based on the value of `notificationRefreshFlag`. + * If the flag is `true`, the bar is expanded to 400px and aligned right. + * If the flag is `false`, `null`, or `undefined`, it defaults to height of 82px. + * Skips if the notification bar element has not yet been created. + * + */ + private setNotificationRefreshBarHeight() { + const isNotificationV3 = !!this.notificationRefreshFlag; + + if (!this.notificationBarElement) { + return; + } + + if (isNotificationV3) { + setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true); + } + } + /** * Sets up the message listener for the initialization of the notification bar. * This will send the initialization data to the notification bar iframe. diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 0471d460fd5..6b7d9120195 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -216,9 +216,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId, activeUserId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return this.cipherService.decrypt(cipher, activeUserId); }), ); @@ -237,9 +235,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId, activeUserId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return this.cipherService.decrypt(cipher, activeUserId); }), ); diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index d63f9a4589d..2d29067cf0f 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -4,14 +4,14 @@ import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { + FormBuilder, + FormControl, + FormGroup, FormsModule, ReactiveFormsModule, - FormBuilder, - FormGroup, - FormControl, } from "@angular/forms"; import { RouterModule } from "@angular/router"; -import { Observable, filter, firstValueFrom, map, switchMap } from "rxjs"; +import { filter, firstValueFrom, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -55,7 +55,7 @@ import { SelectModule, TypographyModule, } from "@bitwarden/components"; -import { SpotlightComponent, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; +import { NudgesService, NudgeType, SpotlightComponent } from "@bitwarden/vault"; import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; @@ -108,9 +108,7 @@ export class AutofillComponent implements OnInit { protected showSpotlightNudge$: Observable = this.accountService.activeAccount$.pipe( filter((account): account is Account => account !== null), switchMap((account) => - this.vaultNudgesService - .showNudge$(VaultNudgeType.AutofillNudge, account.id) - .pipe(map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed)), + this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id), ), ); @@ -155,7 +153,7 @@ export class AutofillComponent implements OnInit { private configService: ConfigService, private formBuilder: FormBuilder, private destroyRef: DestroyRef, - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, private accountService: AccountService, private autofillBrowserSettingsService: AutofillBrowserSettingsService, ) { @@ -343,8 +341,8 @@ export class AutofillComponent implements OnInit { } async dismissSpotlight() { - await this.vaultNudgesService.dismissNudge( - VaultNudgeType.AutofillNudge, + await this.nudgesService.dismissNudge( + NudgeType.AutofillNudge, await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), ); } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index e65d9a20cad..1faa8555a33 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -183,6 +183,7 @@ import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-st import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -199,6 +200,7 @@ import { DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -408,6 +410,7 @@ export default class MainBackground { endUserNotificationService: EndUserNotificationService; inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; taskService: TaskService; + cipherEncryptionService: CipherEncryptionService; ipcContentScriptManagerService: IpcContentScriptManagerService; ipcService: IpcService; @@ -856,6 +859,11 @@ export default class MainBackground { this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService); + this.cipherEncryptionService = new DefaultCipherEncryptionService( + this.sdkService, + this.logService, + ); + this.cipherService = new CipherService( this.keyService, this.domainSettingsService, @@ -871,6 +879,7 @@ export default class MainBackground { this.stateProvider, this.accountService, this.logService, + this.cipherEncryptionService, ); this.folderService = new FolderService( this.keyService, diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.ts b/apps/browser/src/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/browser/src/key-management/key-connector/remove-password.component.ts +++ b/apps/browser/src/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/browser/src/platform/popup/layout/popup-size.service.ts b/apps/browser/src/platform/popup/layout/popup-size.service.ts index 3ae9a633cab..69d3102d24e 100644 --- a/apps/browser/src/platform/popup/layout/popup-size.service.ts +++ b/apps/browser/src/platform/popup/layout/popup-size.service.ts @@ -50,17 +50,40 @@ export class PopupSizeService { PopupSizeService.setStyle(width); localStorage.setItem(PopupSizeService.LocalStorageKey, width); }); + } + async setHeight() { const isInChromeTab = await BrowserPopupUtils.isInTab(); + /** + * To support both browser default zoom and system default zoom, we need to take into account + * the full screen height. When system default zoom is >100%, window.innerHeight still outputs + * a height equivalent to what it would be at 100%, which can cause the extension window to + * render as too tall. So if the screen height is smaller than the max possible extension height, + * we should use that to set our extension height. Otherwise, we want to use the window.innerHeight + * to support browser zoom. + * + * This is basically a workaround for what we consider a bug with browsers reporting the wrong + * available innerHeight when system zoom is turned on. If that gets fixed, we can remove the code + * checking the screen height. + */ + const MAX_EXT_HEIGHT = 600; + const extensionInnerHeight = window.innerHeight; + // Use a 100px offset when calculating screen height to account for browser container elements + const screenAvailHeight = window.screen.availHeight - 100; + const availHeight = + screenAvailHeight < MAX_EXT_HEIGHT ? screenAvailHeight : extensionInnerHeight; + if (!BrowserPopupUtils.inPopup(window) || isInChromeTab) { - window.document.body.classList.add("body-full"); - } else if (window.innerHeight < 400) { - window.document.body.classList.add("body-xxs"); - } else if (window.innerHeight < 500) { - window.document.body.classList.add("body-xs"); - } else if (window.innerHeight < 600) { - window.document.body.classList.add("body-sm"); + window.document.documentElement.classList.add("body-full"); + } else if (availHeight < 300) { + window.document.documentElement.classList.add("body-3xs"); + } else if (availHeight < 400) { + window.document.documentElement.classList.add("body-xxs"); + } else if (availHeight < 500) { + window.document.documentElement.classList.add("body-xs"); + } else if (availHeight < 600) { + window.document.documentElement.classList.add("body-sm"); } } diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts index 465a6e6c69c..22fb7bf99b9 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts @@ -13,7 +13,10 @@ import { PopupRouterCacheService, popupRouterCacheGuard } from "./popup-router-c const flushPromises = async () => await new Promise(process.nextTick); -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class EmptyComponent {} describe("Popup router cache guard", () => { diff --git a/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts index 2ec75791d1b..60baf94eeae 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts @@ -19,10 +19,16 @@ import { import { PopupViewCacheService } from "./popup-view-cache.service"; -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class EmptyComponent {} -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class TestComponent { private viewCacheService = inject(PopupViewCacheService); diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 49579f889b3..5f7fbc1fad7 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -26,6 +26,7 @@ import { import { BiometricsService, BiometricStateService } from "@bitwarden/key-management"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; +import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; @@ -42,6 +43,7 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn
`, + standalone: false, }) export class AppComponent implements OnInit, OnDestroy { private compactModeService = inject(PopupCompactModeService); @@ -71,6 +73,7 @@ export class AppComponent implements OnInit, OnDestroy { private biometricStateService: BiometricStateService, private biometricsService: BiometricsService, private deviceTrustToastService: DeviceTrustToastService, + private popupSizeService: PopupSizeService, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); } @@ -79,6 +82,7 @@ export class AppComponent implements OnInit, OnDestroy { initPopupClosedListener(); this.compactModeService.init(); + await this.popupSizeService.setHeight(); // Component states must not persist between closing and reopening the popup, otherwise they become dead objects // Clear them aggressively to make sure this doesn't occur diff --git a/apps/browser/src/popup/components/user-verification.component.ts b/apps/browser/src/popup/components/user-verification.component.ts index 6befc8973b0..f6cb6cdff12 100644 --- a/apps/browser/src/popup/components/user-verification.component.ts +++ b/apps/browser/src/popup/components/user-verification.component.ts @@ -22,5 +22,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), ]), ], + standalone: false, }) export class UserVerificationComponent extends BaseComponent {} diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 59893b5050d..80ada61f868 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -8,6 +8,34 @@ html { overflow: hidden; + min-height: 600px; + height: 100%; + + &.body-sm { + min-height: 500px; + } + + &.body-xs { + min-height: 400px; + } + + &.body-xxs { + min-height: 300px; + } + + &.body-3xs { + min-height: 240px; + } + + &.body-full { + min-height: unset; + width: 100%; + height: 100%; + + & body { + width: 100%; + } + } } html, @@ -20,9 +48,9 @@ body { body { width: 380px; - height: 600px; + height: 100%; position: relative; - min-height: 100vh; + min-height: inherit; overflow: hidden; color: $text-color; background-color: $background-color; @@ -31,23 +59,6 @@ body { color: themed("textColor"); background-color: themed("backgroundColor"); } - - &.body-sm { - height: 500px; - } - - &.body-xs { - height: 400px; - } - - &.body-xxs { - height: 300px; - } - - &.body-full { - width: 100%; - height: 100%; - } } h1, diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 63b539fddce..0ca763d510d 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -6,18 +6,19 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Icons } from "@bitwarden/components"; -import { VaultNudgesService } from "@bitwarden/vault"; +import { NudgesService } from "@bitwarden/vault"; import { NavButton } from "../platform/popup/layout/popup-tab-navigation.component"; @Component({ selector: "app-tabs-v2", templateUrl: "./tabs-v2.component.html", + standalone: false, }) export class TabsV2Component { private hasActiveBadges$ = this.accountService.activeAccount$ .pipe(getUserId) - .pipe(switchMap((userId) => this.vaultNudgesService.hasActiveBadges$(userId))); + .pipe(switchMap((userId) => this.nudgesService.hasActiveBadges$(userId))); protected navButtons$: Observable = combineLatest([ this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), this.hasActiveBadges$, @@ -53,7 +54,7 @@ export class TabsV2Component { }), ); constructor( - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, private accountService: AccountService, private readonly configService: ConfigService, ) {} diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 22e2d9a28d0..dc53f95a7cf 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -23,6 +23,7 @@ *ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1
@@ -40,7 +41,7 @@
@@ -50,9 +51,10 @@ Will make this dynamic when more nudges are added --> 1
@@ -80,9 +82,10 @@

{{ "downloadBitwardenOnAllDevices" | i18n }}

1
diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.ts b/apps/browser/src/tools/popup/settings/settings-v2.component.ts index be05452529a..211be82c9ed 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.ts @@ -17,7 +17,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { UserId } from "@bitwarden/common/types/guid"; import { BadgeComponent, ItemModule } from "@bitwarden/components"; -import { NudgeStatus, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; +import { NudgesService, NudgeType } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; @@ -42,7 +42,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co ], }) export class SettingsV2Component implements OnInit { - VaultNudgeType = VaultNudgeType; + NudgeType = NudgeType; activeUserId: UserId | null = null; protected isBrowserAutofillSettingOverridden = false; @@ -51,15 +51,15 @@ export class SettingsV2Component implements OnInit { shareReplay({ bufferSize: 1, refCount: true }), ); - downloadBitwardenNudgeStatus$: Observable = this.authenticatedAccount$.pipe( + downloadBitwardenNudgeStatus$: Observable = this.authenticatedAccount$.pipe( switchMap((account) => - this.vaultNudgesService.showNudge$(VaultNudgeType.DownloadBitwarden, account.id), + this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id), ), ); - showVaultBadge$: Observable = this.authenticatedAccount$.pipe( + showVaultBadge$: Observable = this.authenticatedAccount$.pipe( switchMap((account) => - this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, account.id), + this.nudgesService.showNudgeBadge$(NudgeType.EmptyVaultNudge, account.id), ), ); @@ -68,9 +68,9 @@ export class SettingsV2Component implements OnInit { this.authenticatedAccount$, ]).pipe( switchMap(([defaultBrowserAutofillDisabled, account]) => - this.vaultNudgesService.showNudge$(VaultNudgeType.AutofillNudge, account.id).pipe( - map((nudgeStatus) => { - return !defaultBrowserAutofillDisabled && nudgeStatus.hasBadgeDismissed === false; + this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id).pipe( + map((badgeStatus) => { + return !defaultBrowserAutofillDisabled && badgeStatus; }), ), ), @@ -81,7 +81,7 @@ export class SettingsV2Component implements OnInit { ); constructor( - private readonly vaultNudgesService: VaultNudgesService, + private readonly nudgesService: NudgesService, private readonly accountService: AccountService, private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService, private readonly configService: ConfigService, @@ -94,10 +94,10 @@ export class SettingsV2Component implements OnInit { ); } - async dismissBadge(type: VaultNudgeType) { - if (!(await firstValueFrom(this.showVaultBadge$)).hasBadgeDismissed) { + async dismissBadge(type: NudgeType) { + if (await firstValueFrom(this.showVaultBadge$)) { const account = await firstValueFrom(this.authenticatedAccount$); - await this.vaultNudgesService.dismissNudge(type, account.id as UserId, true); + await this.nudgesService.dismissNudge(type, account.id as UserId, true); } } } diff --git a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts index 6bcdddfde81..fc302dd6c36 100644 --- a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts +++ b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts @@ -1,6 +1,6 @@ import { inject } from "@angular/core"; -import { CanActivateFn } from "@angular/router"; -import { switchMap, tap } from "rxjs"; +import { CanActivateFn, Router } from "@angular/router"; +import { map, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -13,18 +13,22 @@ export const canAccessAtRiskPasswords: CanActivateFn = () => { const taskService = inject(TaskService); const toastService = inject(ToastService); const i18nService = inject(I18nService); + const router = inject(Router); return accountService.activeAccount$.pipe( filterOutNullish(), switchMap((user) => taskService.tasksEnabled$(user.id)), - tap((tasksEnabled) => { + map((tasksEnabled) => { if (!tasksEnabled) { toastService.showToast({ variant: "error", title: "", - message: i18nService.t("accessDenied"), + message: i18nService.t("noPermissionsViewPage"), }); + + return router.createUrlTree(["/tabs/vault"]); } + return true; }), ); }; diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index 27f3b7e5e18..7052be5ea62 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -11,7 +11,6 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -66,11 +65,7 @@ export class AssignCollections { route.queryParams.pipe( switchMap(async ({ cipherId }) => { const cipherDomain = await this.cipherService.get(cipherId, userId); - const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption( - cipherDomain, - userId, - ); - return cipherDomain.decrypt(key); + return await this.cipherService.decrypt(cipherDomain, userId); }), ), ), diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index 66d9096cd5c..ec5c93feb9e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -81,6 +81,7 @@ describe("OpenAttachmentsComponent", () => { useValue: { get: getCipher, getKeyForCipherKeyDecryption: () => Promise.resolve(null), + decrypt: jest.fn().mockResolvedValue(cipherView), }, }, { diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 1bc7e22e6d5..9189ea51313 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -81,9 +81,7 @@ export class OpenAttachmentsComponent implements OnInit { this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - const cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + const cipher = await this.cipherService.decrypt(cipherDomain, activeUserId); if (!cipher.organizationId) { this.cipherIsAPartOfFreeOrg = false; diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html index bb3a7b12096..576f6b7def6 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html @@ -40,12 +40,9 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]=" - 'copyFieldValue' | i18n: singleCopiableLogin.key : singleCopiableLogin.value - " - [appCopyClick]="singleCopiableLogin.value" - [valueLabel]="singleCopiableLogin.key" - showToast + [appA11yTitle]="singleCopiableLogin.key" + [appCopyField]="$any(singleCopiableLogin.field)" + [cipher]="cipher" > - -
- - diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts deleted file mode 100644 index 713dc21c424..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { ButtonModule, PopoverModule } from "@bitwarden/components"; - -import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service"; -import { VaultPageService } from "../vault-page.service"; - -@Component({ - selector: "new-settings-callout", - templateUrl: "new-settings-callout.component.html", - standalone: true, - imports: [PopoverModule, JslibModule, CommonModule, ButtonModule], - providers: [VaultPageService], -}) -export class NewSettingsCalloutComponent implements OnInit, OnDestroy { - protected showNewCustomizationSettingsCallout = false; - protected activeUserId: UserId | null = null; - - constructor( - private accountService: AccountService, - private vaultProfileService: VaultProfileService, - private vaultPageService: VaultPageService, - private router: Router, - private logService: LogService, - private copyButtonService: VaultPopupCopyButtonsService, - private vaultSettingsService: VaultSettingsService, - ) {} - - async ngOnInit() { - this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - - const showQuickCopyActions = await firstValueFrom(this.copyButtonService.showQuickCopyActions$); - const clickItemsToAutofillVaultView = await firstValueFrom( - this.vaultSettingsService.clickItemsToAutofillVaultView$, - ); - - let profileCreatedDate: Date; - - try { - profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(this.activeUserId); - } catch (e) { - this.logService.error("Error getting profile creation date", e); - // Default to before the cutoff date to ensure the callout is shown - profileCreatedDate = new Date("2024-12-24"); - } - - const hasCalloutBeenDismissed = await firstValueFrom( - this.vaultPageService.isCalloutDismissed(this.activeUserId), - ); - - this.showNewCustomizationSettingsCallout = - !showQuickCopyActions && - !clickItemsToAutofillVaultView && - !hasCalloutBeenDismissed && - profileCreatedDate < new Date("2024-12-25"); - } - - async goToAppearance() { - await this.router.navigate(["/appearance"]); - } - - async dismissCallout() { - if (this.activeUserId) { - await this.vaultPageService.dismissCallout(this.activeUserId); - } - } - - async ngOnDestroy() { - await this.dismissCallout(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts deleted file mode 100644 index a7c52ed4c51..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { inject, Injectable } from "@angular/core"; -import { map, Observable } from "rxjs"; - -import { - BANNERS_DISMISSED_DISK, - StateProvider, - UserKeyDefinition, -} from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - -export const NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY = new UserKeyDefinition( - BANNERS_DISMISSED_DISK, - "newCustomizationOptionsCalloutDismissed", - { - deserializer: (calloutDismissed) => calloutDismissed, - clearOn: [], // Do not clear dismissed callouts - }, -); - -@Injectable() -export class VaultPageService { - private stateProvider = inject(StateProvider); - - isCalloutDismissed(userId: UserId): Observable { - return this.stateProvider - .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) - .state$.pipe(map((dismissed) => !!dismissed)); - } - - async dismissCallout(userId: UserId): Promise { - await this.stateProvider - .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) - .update(() => true); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index 5d315775b10..d0eef20f044 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -69,8 +69,6 @@ export class PasswordHistoryV2Component implements OnInit { const activeUserId = activeAccount.id as UserId; const cipher = await this.cipherService.get(cipherId, activeUserId); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 894f27245b2..42e772be062 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -36,7 +36,7 @@ [subtitle]="'emptyVaultNudgeBody' | i18n" [buttonText]="'emptyVaultNudgeButton' | i18n" (onButtonClick)="navigateToImport()" - (onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.EmptyVaultNudge)" + (onDismiss)="dismissVaultNudgeSpotlight(NudgeType.EmptyVaultNudge)" > @@ -44,9 +44,13 @@
+
    +
  • {{ "hasItemsVaultNudgeBodyOne" | i18n }}
  • +
  • {{ "hasItemsVaultNudgeBodyTwo" | i18n }}
  • +
  • {{ "hasItemsVaultNudgeBodyThree" | i18n }}
  • +
- diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 4a8625f982c..8dc4c639574 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -19,16 +19,23 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components"; +import { + ButtonModule, + DialogService, + Icons, + NoItemsModule, + TypographyModule, +} from "@bitwarden/components"; import { DecryptionFailureDialogComponent, + NudgesService, + NudgeType, SpotlightComponent, VaultIcons, - VaultNudgesService, - VaultNudgeType, } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; @@ -49,9 +56,7 @@ import { NewItemDropdownV2Component, NewItemInitialValues, } from "./new-item-dropdown/new-item-dropdown-v2.component"; -import { NewSettingsCalloutComponent } from "./new-settings-callout/new-settings-callout.component"; import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; -import { VaultPageService } from "./vault-page.service"; import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; @@ -83,27 +88,24 @@ enum VaultState { ScrollingModule, VaultHeaderV2Component, AtRiskPasswordCalloutComponent, - NewSettingsCalloutComponent, SpotlightComponent, RouterModule, + TypographyModule, ], - providers: [VaultPageService], }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; - VaultNudgeType = VaultNudgeType; + NudgeType = NudgeType; cipherType = CipherType; private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); showEmptyVaultSpotlight$: Observable = this.activeUserId$.pipe( switchMap((userId) => - this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, userId), + this.nudgesService.showNudgeSpotlight$(NudgeType.EmptyVaultNudge, userId), ), - map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed), ); showHasItemsVaultSpotlight$: Observable = this.activeUserId$.pipe( - switchMap((userId) => this.vaultNudgesService.showNudge$(VaultNudgeType.HasVaultItems, userId)), - map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed), + switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.HasVaultItems, userId)), ); activeUserId: UserId | null = null; @@ -144,7 +146,6 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { protected noResultsIcon = Icons.NoResults; protected VaultStateEnum = VaultState; - protected showNewCustomizationSettingsCallout = false; constructor( private vaultPopupItemsService: VaultPopupItemsService, @@ -156,8 +157,9 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private dialogService: DialogService, private vaultCopyButtonsService: VaultPopupCopyButtonsService, private introCarouselService: IntroCarouselService, - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, private router: Router, + private i18nService: I18nService, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, @@ -225,8 +227,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { } } - async dismissVaultNudgeSpotlight(type: VaultNudgeType) { - await this.vaultNudgesService.dismissNudge(type, this.activeUserId as UserId); + async dismissVaultNudgeSpotlight(type: NudgeType) { + await this.nudgesService.dismissNudge(type, this.activeUserId as UserId); } protected readonly FeatureFlag = FeatureFlag; diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 44874221a59..3222f39a162 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -82,6 +82,7 @@ describe("ViewV2Component", () => { getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), deleteWithServer: jest.fn().mockResolvedValue(undefined), softDeleteWithServer: jest.fn().mockResolvedValue(undefined), + decrypt: jest.fn().mockResolvedValue(mockCipher), }; beforeEach(async () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index a834314560b..0a71caf5aee 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -203,9 +203,7 @@ export class ViewV2Component { async getCipherData(id: string, userId: UserId) { const cipher = await this.cipherService.get(id, userId); - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), - ); + return await this.cipherService.decrypt(cipher, userId); } async editCipher() { diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts index 9f04bb58c34..a79aa1d3f14 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts @@ -7,7 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CardComponent, LinkModule, TypographyModule } from "@bitwarden/components"; -import { VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; +import { NudgesService, NudgeType } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; @@ -32,12 +32,12 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co }) export class DownloadBitwardenComponent implements OnInit { constructor( - private vaultNudgeService: VaultNudgesService, + private nudgesService: NudgesService, private accountService: AccountService, ) {} async ngOnInit() { const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - await this.vaultNudgeService.dismissNudge(VaultNudgeType.DownloadBitwarden, userId); + await this.nudgesService.dismissNudge(NudgeType.DownloadBitwarden, userId); } } diff --git a/apps/cli/package.json b/apps/cli/package.json index 82faa7d40e6..b01c96b23d1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -71,6 +71,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "11.1.0", + "core-js": "3.40.0", "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index 6d9e6c8b6c0..540bc2659c9 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -59,15 +59,11 @@ export class ShareCommand { return Response.badRequest("This item already belongs to an organization."); } - const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipher, activeUserId); try { await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId); const updatedCipher = await this.cipherService.get(cipher.id, activeUserId); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 3ad71c62e66..cd5c8ef9bcd 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -106,6 +106,8 @@ export class LoginCommand { return Response.badRequest("client_secret is required."); } } else if (options.sso != null && this.canInteract) { + // If the optional Org SSO Identifier isn't provided, the option value is `true`. + const orgSsoIdentifier = options.sso === true ? null : options.sso; const passwordOptions: any = { type: "password", length: 64, @@ -119,7 +121,7 @@ export class LoginCommand { const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state); + const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier); ssoCode = ssoParams.ssoCode; orgIdentifier = ssoParams.orgIdentifier; } catch { @@ -664,6 +666,7 @@ export class LoginCommand { private async openSsoPrompt( codeChallenge: string, state: string, + orgSsoIdentifier: string, ): Promise<{ ssoCode: string; orgIdentifier: string }> { const env = await firstValueFrom(this.environmentService.environment$); @@ -712,6 +715,8 @@ export class LoginCommand { this.ssoRedirectUri, state, codeChallenge, + null, + orgSsoIdentifier, ); this.platformUtilsService.launchUri(webAppSsoUrl); }); diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 2d4a854135d..4dcf805661d 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -90,9 +90,7 @@ export class EditCommand { return Response.notFound(); } - let cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + let cipherView = await this.cipherService.decrypt(cipher, activeUserId); if (cipherView.isDeleted) { return Response.badRequest("You may not edit a deleted item. Use the restore command first."); } @@ -100,9 +98,7 @@ export class EditCommand { const encCipher = await this.cipherService.encrypt(cipherView, activeUserId); try { const updatedCipher = await this.cipherService.updateWithServer(encCipher); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -132,12 +128,7 @@ export class EditCommand { cipher, activeUserId, ); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption( - updatedCipher, - await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), - ), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 1bdbd051585..c3ba6044f8a 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -116,9 +116,7 @@ export class GetCommand extends DownloadCommand { if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id, activeUserId); if (cipher != null) { - decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + decCipher = await this.cipherService.decrypt(cipher, activeUserId); } } else if (id.trim() !== "") { let ciphers = await this.cipherService.getAllDecrypted(activeUserId); diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index dca4effcdc9..d85f1b366e6 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -118,7 +118,10 @@ export class Program extends BaseProgram { .description("Log into a user account.") .option("--method ", "Two-step login method.") .option("--code ", "Two-step login code.") - .option("--sso", "Log in with Single-Sign On.") + .option( + "--sso [identifier]", + "Log in with Single-Sign On with optional organization identifier.", + ) .option("--apikey", "Log in with an Api Key.") .option("--passwordenv ", "Environment variable storing your password") .option( diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index fe2f506f229..cdf6c4bbfda 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -139,12 +139,14 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider"; import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherAuthorizationService, DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -284,6 +286,7 @@ export class ServiceContainer { ssoUrlService: SsoUrlService; masterPasswordApiService: MasterPasswordApiServiceAbstraction; bulkEncryptService: FallbackBulkEncryptService; + cipherEncryptionService: CipherEncryptionService; constructor() { let p = null; @@ -679,6 +682,11 @@ export class ServiceContainer { this.accountService, ); + this.cipherEncryptionService = new DefaultCipherEncryptionService( + this.sdkService, + this.logService, + ); + this.cipherService = new CipherService( this.keyService, this.domainSettingsService, @@ -694,6 +702,7 @@ export class ServiceContainer { this.stateProvider, this.accountService, this.logService, + this.cipherEncryptionService, ); this.folderService = new FolderService( diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 5b34d2cb507..b1536e23748 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -93,9 +93,7 @@ export class CreateCommand { const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); - const decCipher = await newCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(newCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -162,9 +160,7 @@ export class CreateCommand { new Uint8Array(fileBuf).buffer, activeUserId, ); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); return Response.success(new CipherResponse(decCipher)); } catch (e) { return Response.error(e); diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 1b442fbbb8a..a08764fc9d8 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -3045,9 +3045,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.1" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", @@ -3479,9 +3479,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -3568,7 +3568,7 @@ dependencies = [ "windows-interface 0.59.1", "windows-link", "windows-result 0.3.2", - "windows-strings 0.4.0", + "windows-strings", ] [[package]] @@ -3643,13 +3643,13 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "ad1da3e436dc7653dfdf3da67332e22bff09bb0e28b0239e1624499c7830842e" dependencies = [ + "windows-link", "windows-result 0.3.2", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] @@ -3670,15 +3670,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.0" @@ -3730,29 +3721,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3765,12 +3740,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3783,12 +3752,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3801,24 +3764,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3831,12 +3782,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_plugin_authenticator" version = "0.0.0" @@ -3858,12 +3803,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3876,12 +3815,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3894,12 +3827,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" version = "0.7.3" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index fafaf02eca3..e22b584bb65 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -50,17 +50,17 @@ simplelog = "=0.12.2" ssh-encoding = "=0.2.0" ssh-key = {version = "=0.6.7", default-features = false } sysinfo = "0.35.0" -thiserror = "=1.0.69" -tokio = "=1.43.1" +thiserror = "=2.0.12" +tokio = "=1.45.0" tokio-stream = "=0.1.15" tokio-util = "=0.7.13" typenum = "=1.18.0" uniffi = "=0.28.3" -widestring = "=1.1.0" +widestring = "=1.2.0" windows = "=0.61.1" windows-core = "=0.61.0" windows-future = "=0.2.0" -windows-registry = "=0.4.0" +windows-registry = "=0.5.1" zbus = "=4.4.0" zbus_polkit = "=4.0.0" zeroizing-alloc = "=0.1.0" diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index d51d9412d80..1e96198d4ad 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -243,7 +243,7 @@ }, "snap": { "summary": "Bitwarden is a secure and free password manager for all of your devices.", - "description": "**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.", + "description": "Password Manager\n**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.", "autoStart": true, "base": "core22", "confinement": "strict", diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 2639d819854..83c982fbaba 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -54,6 +54,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man @Component({ selector: "app-settings", templateUrl: "settings.component.html", + standalone: false, }) export class SettingsComponent implements OnInit, OnDestroy { // For use in template diff --git a/apps/desktop/src/app/accounts/vault-timeout-input.component.ts b/apps/desktop/src/app/accounts/vault-timeout-input.component.ts index c56e6578a0b..25a4d01333d 100644 --- a/apps/desktop/src/app/accounts/vault-timeout-input.component.ts +++ b/apps/desktop/src/app/accounts/vault-timeout-input.component.ts @@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b useExisting: VaultTimeoutInputComponent, }, ], + standalone: false, }) export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index c3cfdf49c2c..77ac783ac9f 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -11,7 +11,16 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { filter, firstValueFrom, map, Subject, switchMap, takeUntil, timeout } from "rxjs"; +import { + filter, + firstValueFrom, + lastValueFrom, + map, + Subject, + switchMap, + takeUntil, + timeout, +} from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; @@ -56,11 +65,11 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { DialogRef, DialogService, ToastOptions, ToastService } from "@bitwarden/components"; import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components"; import { KeyService, BiometricStateService } from "@bitwarden/key-management"; +import { AddEditFolderDialogComponent, AddEditFolderDialogResult } from "@bitwarden/vault"; import { DeleteAccountComponent } from "../auth/delete-account.component"; import { PremiumComponent } from "../billing/app/accounts/premium.component"; import { MenuAccount, MenuUpdateRequest } from "../main/menu/menu.updater"; -import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.component"; import { SettingsComponent } from "./accounts/settings.component"; import { ExportDesktopComponent } from "./tools/export/export-desktop.component"; @@ -78,7 +87,6 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours - @@ -93,6 +101,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours `, + standalone: false, }) export class AppComponent implements OnInit, OnDestroy { @ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef; @@ -101,8 +110,6 @@ export class AppComponent implements OnInit, OnDestroy { passwordHistoryRef: ViewContainerRef; @ViewChild("exportVault", { read: ViewContainerRef, static: true }) exportVaultModalRef: ViewContainerRef; - @ViewChild("appFolderAddEdit", { read: ViewContainerRef, static: true }) - folderAddEditModalRef: ViewContainerRef; @ViewChild("appGenerator", { read: ViewContainerRef, static: true }) generatorModalRef: ViewContainerRef; @ViewChild("loginApproval", { read: ViewContainerRef, static: true }) @@ -464,25 +471,11 @@ export class AppComponent implements OnInit, OnDestroy { async addFolder() { this.modalService.closeAll(); - const [modal, childComponent] = await this.modalService.openViewRef( - FolderAddEditComponent, - this.folderAddEditModalRef, - (comp) => (comp.folderId = null), - ); - this.modal = modal; - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onSavedFolder.subscribe(async () => { - this.modal.close(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.syncService.fullSync(false); - }); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.modal.onClosed.subscribe(() => { - this.modal = null; - }); + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService); + const result = await lastValueFrom(dialogRef.closed); + if (result === AddEditFolderDialogResult.Created) { + await this.syncService.fullSync(false); + } } async openGenerator() { diff --git a/apps/desktop/src/app/components/avatar.component.ts b/apps/desktop/src/app/components/avatar.component.ts index d2660763667..1fba864686c 100644 --- a/apps/desktop/src/app/components/avatar.component.ts +++ b/apps/desktop/src/app/components/avatar.component.ts @@ -8,6 +8,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-avatar", template: ``, + standalone: false, }) export class AvatarComponent implements OnChanges, OnInit { @Input() size = 45; diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts index d8ffa5ae546..a54674c3a1e 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.ts +++ b/apps/desktop/src/app/layout/account-switcher.component.ts @@ -54,6 +54,7 @@ type InactiveAccount = ActiveAccount & { transition("* => void", animate("100ms linear", style({ opacity: 0 }))), ]), ], + standalone: false, }) export class AccountSwitcherComponent implements OnInit { activeAccount$: Observable; diff --git a/apps/desktop/src/app/layout/header.component.ts b/apps/desktop/src/app/layout/header.component.ts index 1cf697ad4ed..9aef093423f 100644 --- a/apps/desktop/src/app/layout/header.component.ts +++ b/apps/desktop/src/app/layout/header.component.ts @@ -3,5 +3,6 @@ import { Component } from "@angular/core"; @Component({ selector: "app-header", templateUrl: "header.component.html", + standalone: false, }) export class HeaderComponent {} diff --git a/apps/desktop/src/app/layout/search/search.component.ts b/apps/desktop/src/app/layout/search/search.component.ts index a9faf2a2414..70196d74dda 100644 --- a/apps/desktop/src/app/layout/search/search.component.ts +++ b/apps/desktop/src/app/layout/search/search.component.ts @@ -11,6 +11,7 @@ import { SearchBarService, SearchBarState } from "./search-bar.service"; @Component({ selector: "app-search", templateUrl: "search.component.html", + standalone: false, }) export class SearchComponent implements OnInit, OnDestroy { state: SearchBarState; diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts index 5a78fb08c47..48b18d7294c 100644 --- a/apps/desktop/src/auth/set-password.component.ts +++ b/apps/desktop/src/auth/set-password.component.ts @@ -28,6 +28,7 @@ const BroadcasterSubscriptionId = "SetPasswordComponent"; @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy { constructor( diff --git a/apps/desktop/src/auth/update-temp-password.component.ts b/apps/desktop/src/auth/update-temp-password.component.ts index f7d952b97f4..ead10660b92 100644 --- a/apps/desktop/src/auth/update-temp-password.component.ts +++ b/apps/desktop/src/auth/update-temp-password.component.ts @@ -5,5 +5,6 @@ import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from " @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {} diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index e88e16c2ffc..d6dddf3b23f 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -199,9 +199,7 @@ export class DesktopAutofillService implements OnDestroy { return; } - const decrypted = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const decrypted = await this.cipherService.decrypt(cipher, activeUserId); const fido2Credential = decrypted.login.fido2Credentials?.[0]; if (!fido2Credential) { diff --git a/apps/desktop/src/billing/app/accounts/premium.component.ts b/apps/desktop/src/billing/app/accounts/premium.component.ts index 1b4573fe6d6..5d0fa7a5dde 100644 --- a/apps/desktop/src/billing/app/accounts/premium.component.ts +++ b/apps/desktop/src/billing/app/accounts/premium.component.ts @@ -13,6 +13,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-premium", templateUrl: "premium.component.html", + standalone: false, }) export class PremiumComponent extends BasePremiumComponent { constructor( diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.ts b/apps/desktop/src/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.ts +++ b/apps/desktop/src/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index c3127cffe00..479f8f47c1f 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Voeg Nuwe Item Toe" }, - "addNewFolder": { - "message": "Voeg Nuwe Vouer Toe" - }, "view": { "message": "Bekyk" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha-bronadres", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Laai toeganklikheidskoekie" - }, - "registerAccessibilityUser": { - "message": "Registreer as toeganklikheidsgebruiker by", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopieer en plak die skakel wat na die e-pos hier onder gestuur is" - }, - "enterhCaptchaUrl": { - "message": "Voer die bronadres in om die toeganklikheidskoekie vir hCaptcha te laai", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha-bronadres word benodig", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Ongeldige bronadres" - }, "done": { "message": "Klaar" }, - "accessibilityCookieSaved": { - "message": "Toeganklikheidskoekie is bewaar!" - }, - "noAccessibilityCookieSaved": { - "message": "Geen toeganklikheidskoekie is bewaar nie" - }, "warning": { "message": "WAARSKUWING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index d9d4241e723..510c1aa918a 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "إضافة عنصر جديد" }, - "addNewFolder": { - "message": "إضافة مجلد جديد" - }, "view": { "message": "عرض" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"كلمة مرور الملف\" و \"تأكيد كلمة مرور الملف\" غير متطابقين." }, - "hCaptchaUrl": { - "message": "رابط hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "تحميل ملف تعريف الارتباط الخاص بإمكانية الوصول" - }, - "registerAccessibilityUser": { - "message": "تسجيل كمستخدم مع إمكانية الوصول في", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "نسخ وصق الرابط المرسل إلى بريدك الإلكتروني أدناه" - }, - "enterhCaptchaUrl": { - "message": "أدخل عنوان URL لتحميل ملف تعريف الارتباط للوصول إلى hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "مطلوب عنوان hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "الرابط غير صالح" - }, "done": { "message": "تم" }, - "accessibilityCookieSaved": { - "message": "تم حفظ ملف تعريف الارتباط الخاص بإمكانية الوصول!" - }, - "noAccessibilityCookieSaved": { - "message": "لم يحفظ ملف تعريف الارتباط الخاص بإمكانية الوصول" - }, "warning": { "message": "تحذير", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "السماح" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index ed96da377ad..0f856661551 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -238,22 +238,22 @@ "message": "SSH agenti, SSH tələblərini birbaşa Bitwarden seyfinizdən imzalamağa imkan verən developerlərə yönəlmiş bir xidmətdir." }, "sshAgentPromptBehavior": { - "message": "Ask for authorization when using SSH agent" + "message": "SSH agentini istifadə edərkən səlahiyyətləndirmə üçün soruş" }, "sshAgentPromptBehaviorDesc": { - "message": "Choose how to handle SSH-agent authorization requests." + "message": "SSH agenti səlahiyyətləndirmə sorğularının necə emal ediləcəyini seçin." }, "sshAgentPromptBehaviorHelp": { - "message": "Remember SSH authorizations" + "message": "SSH səlahiyyətləndirmələrini xatırla" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "Həmişə" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "Heç vaxt" }, "sshAgentPromptBehaviorRememberUntilLock": { - "message": "Remember until vault is locked" + "message": "Seyf kilidlənənə qədər xatırla" }, "premiumRequired": { "message": "Premium üzvlük lazımdır" @@ -446,10 +446,10 @@ "message": "Xana əlavə et" }, "editField": { - "message": "Edit field" + "message": "Xanaya düzəliş et" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Bu qoşmanı birdəfəlik silmək istədiyinizə əminsiniz?" }, "fieldType": { "message": "Xana növü" @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Yeni element əlavə et" }, - "addNewFolder": { - "message": "Yeni qovluq əlavə et" - }, "view": { "message": "Bax" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Fayl parolu\" və \"Fayl parolunu təsdiqlə\" uyuşmur." }, - "hCaptchaUrl": { - "message": "hCaptcha ünvanı", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Əlçatımlılıq çərəzini yüklə" - }, - "registerAccessibilityUser": { - "message": "Əlçatımlılıq istifadəçisi kimi qeydiyyatdan keçin", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "E-poçtunuza göndərilən keçidi kopyalayıb aşağıda yapışdırın" - }, - "enterhCaptchaUrl": { - "message": "hCaptcha əlçatımlılıq çərəzini yükləmək üçün ünvanı daxil edin", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha ünvanı tələb olunur", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Yararsız ünvan" - }, "done": { "message": "Bitdi" }, - "accessibilityCookieSaved": { - "message": "Əlçatımlılıq çərəzi saxlanıldı!" - }, - "noAccessibilityCookieSaved": { - "message": "Əlçatımlılıq çərəzi saxlanılmadı" - }, "warning": { "message": "XƏBƏRDARLIQ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." }, + "itemDetails": { + "message": "Element detalları" + }, + "itemName": { + "message": "Element adı" + }, + "loginCredentials": { + "message": "Giriş məlumatları" + }, + "additionalOptions": { + "message": "Əlavə seçimlər" + }, + "itemHistory": { + "message": "Element tarixçəsi" + }, + "lastEdited": { + "message": "Son düzəliş" + }, + "upload": { + "message": "Yüklə" + }, "authorize": { "message": "Səlahiyyət ver" }, @@ -3719,21 +3706,30 @@ "move": { "message": "Daşı" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Avto-doldurma ilə vaxta qənaət edin" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "Bir veb sayt", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "daxil edin ki,", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "bu giriş məlumatları avto-doldurma təklifi kimi görünsün.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -3759,12 +3755,12 @@ "message": "Gəlişdirici dostu SSH müraciəti" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Açarlarınızı saxlayın və sürətli, şifrələnmiş kimlik doğrulama üçün SSH agentinə bağlayın.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "SSH agenti barədə daha ətraflı", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 5b7b4aebf70..4e2441ac168 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Дадаць новы элемент" }, - "addNewFolder": { - "message": "Дадаць новую папку" - }, "view": { "message": "Прагляд" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "URL-адрас hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Загрузіць cookie спецыяльных магчымасцей" - }, - "registerAccessibilityUser": { - "message": "Зарэгістравацца ў якасці карыстальніка са спецыяльнымі магчымасцямі на", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Скапіюйце і ўстаўце адпраўленую спасылку на вашу электронную пошту" - }, - "enterhCaptchaUrl": { - "message": "Увядзіце URL-адрас для загрузкі cookie спецыяльных магчымасцей для hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Патрабуецца URL-адрас hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Памылковы URL-адрас" - }, "done": { "message": "Гатова" }, - "accessibilityCookieSaved": { - "message": "Cookie са спецыяльнымі магчымасцямі захаваны!" - }, - "noAccessibilityCookieSaved": { - "message": "Адсутнічаюць захаваныя cookie са спецыяльнымі магчымасцямі" - }, "warning": { "message": "ПАПЯРЭДЖАННЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 33cd71c1d5e..3a6054c2b52 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Добавяне на нов елемент" }, - "addNewFolder": { - "message": "Добавяне на нова папка" - }, "view": { "message": "Преглед" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "Дънните в полетата „Парола на файла“ и „Потвърждаване на паролата на файла“ не съвпадат." }, - "hCaptchaUrl": { - "message": "Адрес за hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Зареждане на бисквитка за достъпност" - }, - "registerAccessibilityUser": { - "message": "Регистрирайте се като потребител на достъпност на", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Копирайте връзката изпратена на е-пощата Ви и я поставете по-долу" - }, - "enterhCaptchaUrl": { - "message": "Въведете URL за зареждане на бисквитка за достъпност за hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Адресът за hCaptcha е задължителен", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Неправилен адрес" - }, "done": { "message": "Готово" }, - "accessibilityCookieSaved": { - "message": "Бисквитката за достъпност е запазена!" - }, - "noAccessibilityCookieSaved": { - "message": "Няма запазена бисквитка за достъпност" - }, "warning": { "message": "ВНИМАНИЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Отключването с биометрични данни не е налично по неизвестна причина." }, + "itemDetails": { + "message": "Подробности за елемента" + }, + "itemName": { + "message": "Име на елемента" + }, + "loginCredentials": { + "message": "Данни за вписване" + }, + "additionalOptions": { + "message": "Допълнителни настройки" + }, + "itemHistory": { + "message": "История на елемента" + }, + "lastEdited": { + "message": "Последна промяна" + }, + "upload": { + "message": "Качване" + }, "authorize": { "message": "Упълномощаване" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Преместване" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Спестете време с автоматично попълване" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index ed7f99f52c9..3ca32f51395 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "নতুন বস্তু জুড়ুন" }, - "addNewFolder": { - "message": "নতুন ফোল্ডার জুড়ুন" - }, "view": { "message": "দেখুন" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "সতর্কতা", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 0a5efded68b..4e77b51467b 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Dodaj novu stavku" }, - "addNewFolder": { - "message": "Dodajte novi folder" - }, "view": { "message": "Prikaz" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Učitaj kolačić pristupačnosti" - }, - "registerAccessibilityUser": { - "message": "Registruj se kao korisnik pristupačnosti na", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopiraj i zalijepi link poslan na e-mail u nastavku" - }, - "enterhCaptchaUrl": { - "message": "Unesi URL za učitavanje kolačića pristupačnosti za hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url je obavezan", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Neispravan Url" - }, "done": { "message": "Gotovo" }, - "accessibilityCookieSaved": { - "message": "Kolačić pristupačnosti spremljen!" - }, - "noAccessibilityCookieSaved": { - "message": "Nije spremljen kolačić pristupačnosti" - }, "warning": { "message": "UPOZORENJE", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index d95d8896a68..2f13de7c33b 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Element nou" }, - "addNewFolder": { - "message": "Carpeta nova" - }, "view": { "message": "Mostra" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Contrasenya del fitxer\" i \"Confirma contrasenya del fitxer\" no coincideixen." }, - "hCaptchaUrl": { - "message": "Url hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Carrega la galeta d'accessibilitat" - }, - "registerAccessibilityUser": { - "message": "Registreu-vos com a usuari d'accessibilitat a", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copieu i enganxeu l'enllaç enviat al vostre correu electrònic a continuació" - }, - "enterhCaptchaUrl": { - "message": "Introduïu l'URL per carregar la galeta d'accessibilitat per a hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Es requereix l'URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "URL no vàlida" - }, "done": { "message": "Fet" }, - "accessibilityCookieSaved": { - "message": "S'ha guardat la galeta d'accessibilitat!" - }, - "noAccessibilityCookieSaved": { - "message": "No s'ha guardat la galeta d'accessibilitat" - }, "warning": { "message": "ADVERTIMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Autoritza" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 58b986167a3..5f01551a867 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Nová položka" }, - "addNewFolder": { - "message": "Nová složka" - }, "view": { "message": "Zobrazit" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Heslo souboru\" a \"Potvrzení hesla souboru\" se neshodují." }, - "hCaptchaUrl": { - "message": "URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Načíst soubory cookie pro přístupnost" - }, - "registerAccessibilityUser": { - "message": "Registrovat jako uživatele přístupnosti na", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Zkopírujte a vložte odkaz odeslaný na Váš e-mail níže" - }, - "enterhCaptchaUrl": { - "message": "Zadejte URL pro načtení cookie přístupnosti pro hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Je vyžadována adresa URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Neplatná adresa URL" - }, "done": { "message": "Hotovo" }, - "accessibilityCookieSaved": { - "message": "Cookie pro přístupnost byla uložena!" - }, - "noAccessibilityCookieSaved": { - "message": "Žádné cookies pro přístupnost nebyly uloženy" - }, "warning": { "message": "VAROVÁNÍ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." }, + "itemDetails": { + "message": "Detaily položky" + }, + "itemName": { + "message": "Název položky" + }, + "loginCredentials": { + "message": "Přihlašovací údaje" + }, + "additionalOptions": { + "message": "Další volby" + }, + "itemHistory": { + "message": "Historie položky" + }, + "lastEdited": { + "message": "Naposledy upraveno" + }, + "upload": { + "message": "Nahrát" + }, "authorize": { "message": "Autorizovat" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Přesunout" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Ušetřete čas s automatickým vyplňováním" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index b129c97a27a..0031d2de121 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index c7f69e12e10..c92f0caf2d1 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Nyt emne" }, - "addNewFolder": { - "message": "Ny mappe" - }, "view": { "message": "Vis" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“Filadgangskode” og “Bekræft filadgangskode“ matcher ikke." }, - "hCaptchaUrl": { - "message": "hCaptcha-URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Indlæs tilgængelighedscookie" - }, - "registerAccessibilityUser": { - "message": "Registrér som tilgængelighedsbruger på", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopiér og indsæt linket, der er sendt til din e-mail, nedenfor" - }, - "enterhCaptchaUrl": { - "message": "Indtast URL for at indlæse tilgængelighedscookie til hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha-URL er obligatorisk", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Ugyldig URL" - }, "done": { "message": "Udført" }, - "accessibilityCookieSaved": { - "message": "Tilgængelighedscookie gemt!" - }, - "noAccessibilityCookieSaved": { - "message": "Ingen tilgængelighedscookie gemt" - }, "warning": { "message": "ADVARSEL", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Godkend" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 7b1ae3521e3..5a01344185a 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -238,13 +238,13 @@ "message": "Der SSH-Agent ist ein Dienst, der sich an Entwickler richtet, mit dem du SSH-Anfragen direkt aus deinem Bitwarden-Tresor aus signieren kannst." }, "sshAgentPromptBehavior": { - "message": "Ask for authorization when using SSH agent" + "message": "Bei der Verwendung des SSH-Agenten nach einer Autorisierung fragen" }, "sshAgentPromptBehaviorDesc": { - "message": "Choose how to handle SSH-agent authorization requests." + "message": "Wähle aus, wie mit Autorisierungs-Anfragen des SSH-Agenten umgegangen werden soll." }, "sshAgentPromptBehaviorHelp": { - "message": "Remember SSH authorizations" + "message": "SSH-Autorisierungen merken" }, "sshAgentPromptBehaviorAlways": { "message": "Immer" @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Neuer Eintrag" }, - "addNewFolder": { - "message": "Neuer Ordner" - }, "view": { "message": "Ansicht" }, @@ -1650,7 +1647,7 @@ "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { - "message": "Standard-Match-Erkennung", + "message": "Standard-Übereinstimmungserkennung", "description": "Default URI match detection for auto-fill." }, "toggleOptions": { @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "„Dateipasswort“ und „Dateipasswort bestätigen“ stimmen nicht überein." }, - "hCaptchaUrl": { - "message": "hCaptcha URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Barrierefreiheits-Cookie laden" - }, - "registerAccessibilityUser": { - "message": "Als Benutzer für Barrierefreiheit registrieren bei", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopiere und füge den Link aus der an dich gesendeten E-Mail unten ein" - }, - "enterhCaptchaUrl": { - "message": "URL eingeben, um den Barrierefreiheits-Cookie für hCaptcha zu laden", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha-URL ist erforderlich", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Ungültige URL" - }, "done": { "message": "Fertig" }, - "accessibilityCookieSaved": { - "message": "Barrierefreiheits-Cookie gespeichert!" - }, - "noAccessibilityCookieSaved": { - "message": "Kein Barrierefreiheits-Cookie gespeichert" - }, "warning": { "message": "WARNUNG", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." }, + "itemDetails": { + "message": "Eintrag-Details" + }, + "itemName": { + "message": "Eintrags-Name" + }, + "loginCredentials": { + "message": "Zugangsdaten" + }, + "additionalOptions": { + "message": "Weitere Optionen" + }, + "itemHistory": { + "message": "Eintragsverlauf" + }, + "lastEdited": { + "message": "Zuletzt bearbeitet" + }, + "upload": { + "message": "Hochladen" + }, "authorize": { "message": "Autorisieren" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Verschieben" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Spare Zeit mit Auto-Ausfüllen" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index d721f8bc9b9..1f4462ce4b5 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -247,10 +247,10 @@ "message": "Remember SSH authorizations" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "Πάντα" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "Ποτέ" }, "sshAgentPromptBehaviorRememberUntilLock": { "message": "Remember until vault is locked" @@ -415,13 +415,13 @@ "message": "Authenticator key" }, "autofillOptions": { - "message": "Autofill options" + "message": "Επιλογές αυτόματης συμπλήρωσης" }, "websiteUri": { - "message": "Website (URI)" + "message": "Ιστότοπος (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Ιστότοπος (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -434,31 +434,31 @@ "message": "Website added" }, "addWebsite": { - "message": "Add website" + "message": "Προσθήκη ιστοτόπου" }, "deleteWebsite": { - "message": "Delete website" + "message": "Διαγραφή ιστοτόπου" }, "owner": { - "message": "Owner" + "message": "Ιδιοκτήτης" }, "addField": { - "message": "Add field" + "message": "Προσθήκη πεδίου" }, "editField": { - "message": "Edit field" + "message": "Επεξεργασία πεδίου" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, "fieldType": { - "message": "Field type" + "message": "Τύπος πεδίου" }, "fieldLabel": { - "message": "Field label" + "message": "Ετικέτα πεδίου" }, "add": { - "message": "Add" + "message": "Προσθήκη" }, "textHelpText": { "message": "Use text fields for data like security questions" @@ -501,7 +501,7 @@ "description": "This describes a field that is 'linked' (related) to another field." }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Πλαίσιο ελέγχου" }, "linkedValue": { "message": "Συνδεδεμένη τιμή", @@ -910,7 +910,7 @@ "message": "Η αυθεντικοποίηση ακυρώθηκε ή διήρκησε πολύ ώρα. Παρακαλώ προσπαθήστε ξανά." }, "openInNewTab": { - "message": "Open in new tab" + "message": "Άνοιγμα σε νέα καρτέλα" }, "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" @@ -1059,7 +1059,7 @@ "message": "Όχι" }, "location": { - "message": "Location" + "message": "Τοποθεσία" }, "overwritePassword": { "message": "Αντικατάσταση κωδικού πρόσβασης" @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Νέο στοιχείο" }, - "addNewFolder": { - "message": "Νέος φάκελος" - }, "view": { "message": "Προβολή" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "Το \"Κωδικός πρόσβασης αρχείου\" και το \"Επιβεβαίωση κωδικού πρόσβασης αρχείου\" δεν ταιριάζουν." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Φόρτωση cookie προσβασιμότητας" - }, - "registerAccessibilityUser": { - "message": "Εγγραφείτε ως χρήστης προσβασιμότητας στο", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Αντιγράψτε και επικολλήστε τον σύνδεσμο που στάλθηκε στο email σας παρακάτω" - }, - "enterhCaptchaUrl": { - "message": "Εισαγάγετε τη διεύθυνση URL για τη φόρτωση του cookie προσβασιμότητας για το hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Απαιτείται διεύθυνση URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Μη έγκυρο Url" - }, "done": { "message": "Εγινε" }, - "accessibilityCookieSaved": { - "message": "Το cookie προσβασιμότητας αποθηκεύτηκε!" - }, - "noAccessibilityCookieSaved": { - "message": "Δεν έχει αποθηκευτεί cookie προσβασιμότητας" - }, "warning": { "message": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -2001,7 +1967,7 @@ } }, "cardDetails": { - "message": "Card details" + "message": "Στοιχεία κάρτας" }, "cardBrandDetails": { "message": "$BRAND$ details", @@ -2047,7 +2013,7 @@ } }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Ληγμένη κάρτα" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" @@ -2222,13 +2188,13 @@ "message": "Μια πολιτική οργανισμού έχει αποτρέψει την εισαγωγή αντικειμένων στο ατομικό σας θησαυ/κιο." }, "personalDetails": { - "message": "Personal details" + "message": "Προσωπικά στοιχεία" }, "identification": { - "message": "Identification" + "message": "Ταυτοποίηση" }, "contactInfo": { - "message": "Contact information" + "message": "Στοιχεία επικοινωνίας" }, "allSends": { "message": "Όλα τα Sends", @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο για άγνωστο λόγο." }, + "itemDetails": { + "message": "Λεπτομέρειες στοιχείου" + }, + "itemName": { + "message": "Όνομα στοιχείου" + }, + "loginCredentials": { + "message": "Στοιχεία σύνδεσης" + }, + "additionalOptions": { + "message": "Πρόσθετες επιλογές" + }, + "itemHistory": { + "message": "Ιστορικό στοιχείου" + }, + "lastEdited": { + "message": "Τελευταία επεξεργασία" + }, + "upload": { + "message": "Μεταφόρτωση" + }, "authorize": { "message": "Εξουσιοδότηση" }, @@ -3717,7 +3704,16 @@ "message": "Change at-risk password" }, "move": { - "message": "Move" + "message": "Μετακίνηση" + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, "newLoginNudgeTitle": { "message": "Save time with autofill" @@ -3728,7 +3724,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Ιστότοπος", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index ea198086f8d..1ae304287c7 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -3709,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 117a5baa674..77e4222ac1f 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha URL is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid URL" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorise" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index e07af9017b0..109ace48efa 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Add new item" }, - "addNewFolder": { - "message": "Add new folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorise" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index cd7e4344cd1..51c350831a2 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Nova ero" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "Vido" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Preta" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "AVERTO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -2463,7 +2429,7 @@ "message": "Uzi la ĉefan pasvorton" }, "usePin": { - "message": "Uzi PIN-on." + "message": "Uzi PIN-n" }, "useBiometrics": { "message": "Use biometrics" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index e012c5e2984..3023fdd0359 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Añadir nuevo elemento" }, - "addNewFolder": { - "message": "Añadir nueva carpeta" - }, "view": { "message": "Ver" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Contraseña del archivo\" y \"Confirmar contraseña del archivo\" no coinciden." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Cargar cookie de accesibilidad" - }, - "registerAccessibilityUser": { - "message": "Registrarse como usuario de accesibilidad en", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copia y pega el enlace enviado a tu correo electrónico" - }, - "enterhCaptchaUrl": { - "message": "Introduzca la URL para cargar la cookie de accesibilidad de hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url es obligatorio", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Url inválida" - }, "done": { "message": "Hecho" }, - "accessibilityCookieSaved": { - "message": "¡Cookie de accesibilidad guardada!" - }, - "noAccessibilityCookieSaved": { - "message": "No hay ninguna cookie de accesibilidad guardada" - }, "warning": { "message": "ADVERTENCIA", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Autorizar" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Mover" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index bd90fb6a82c..922ab7e36a6 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Lisa uus kirje" }, - "addNewFolder": { - "message": "Lisa uus kaust" - }, "view": { "message": "Vaata" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Faili parool\" ja \"Faili parooli kinnitus\" ei kattu." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Lae juurdepääsetavuse küpsis" - }, - "registerAccessibilityUser": { - "message": "Registreeri juurdepääsetavuse kasutajana lehel", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopeeri ja kleebi link, mis saadeti sinu e-postile" - }, - "enterhCaptchaUrl": { - "message": "Sisesta URL, et laadida ligipääsetavuse küpsis hCaptchale", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url on nõutud", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Vale Url" - }, "done": { "message": "Valmis" }, - "accessibilityCookieSaved": { - "message": "Ligipääsetavuse küpsis on salvestatud!" - }, - "noAccessibilityCookieSaved": { - "message": "Ligipääsetavuse küpsis pole salvestatud" - }, "warning": { "message": "HOIATUS", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index febf716a283..3bc76d8427c 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Gehitu elementu berria" }, - "addNewFolder": { - "message": "Karpeta berria gehitu" - }, "view": { "message": "Erakutsi" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Kargatu erabilerraztasun Cookie-ak" - }, - "registerAccessibilityUser": { - "message": "Erabilerraztasun erabiltzaile erregistratu", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopiatu eta itsatsi bidalitako esteka zure emailera" - }, - "enterhCaptchaUrl": { - "message": "Sartu hCaptcha helbidearen cookiea kargatzeko URLa", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url behar da", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Baliogabeko Url-a" - }, "done": { "message": "Egina" }, - "accessibilityCookieSaved": { - "message": "Erabilerraztasun cookie-a gordeta!" - }, - "noAccessibilityCookieSaved": { - "message": "Ez da erabilerraztasun cookie-rik gorde" - }, "warning": { "message": "KONTUZ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 48e1805b62c..4bcbbd3ac33 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "مورد جدید" }, - "addNewFolder": { - "message": "پوشه جدید" - }, "view": { "message": "مشاهده" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "عدم تطابق \"رمز فایل\" و \"تایید رمز فایل\" با یکدیگر." }, - "hCaptchaUrl": { - "message": "نشانی اینترنتی hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "بارگیری کوکی دسترسی" - }, - "registerAccessibilityUser": { - "message": "ثبت نام به عنوان کاربر قابلیت دسترسی در", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "لینک ارسال شده به ایمیل خود را در زیر کپی و پیست کنید" - }, - "enterhCaptchaUrl": { - "message": "برای بارگیری کوکی دسترسی کپچا نشانی اینترنتی را وارد کنید", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "نشانی اینترنتی کپچا مورد نیاز است", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "نشانی اینترنتی نامعتبر" - }, "done": { "message": "انجام شد" }, - "accessibilityCookieSaved": { - "message": "کوکی دسترسی ذخیره شد!" - }, - "noAccessibilityCookieSaved": { - "message": "هیچ کوکی دسترسی ذخیره نشد" - }, "warning": { "message": "اخطار", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 8f2e845a51b..14cf9c95ffc 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Uusi kohde" }, - "addNewFolder": { - "message": "Uusi kansio" - }, "view": { "message": "Näytä" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Tiedoston salasana\" ja \"Vahvista tiedoston salasana\" eivät täsmää." }, - "hCaptchaUrl": { - "message": "hCaptcha-URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Lataa esteettömyyseväste" - }, - "registerAccessibilityUser": { - "message": "Rekisteröidy esteettömyyskäyttäjäksi osoitteessa", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopioi ja liitä alla olevaan sähköpostiosoitteeseen lähetetty linkki" - }, - "enterhCaptchaUrl": { - "message": "Lataa hCaptcha-esteettömyyseväste syöttämällä URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha-URL vaaditaan", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Virheellinen URL" - }, "done": { "message": "Valmis" }, - "accessibilityCookieSaved": { - "message": "Esteettömyyseväste tallennettiin!" - }, - "noAccessibilityCookieSaved": { - "message": "Esteettömyysevästettä ei tallennettu" - }, "warning": { "message": "VAROITUS", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrinen avaus ei ole tällä hetkellä käytettävissä tuntemattomasta syystä." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Valtuuta" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Siirrä" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 2fdd2d48ed6..b3633cd1694 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Bagong item" }, - "addNewFolder": { - "message": "Bagong folder" - }, "view": { "message": "Tanaw" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "I-load ang accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Magrehistro bilang isang gumagamit ng accessibility sa", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopyahin at i paste ang link na ipinadala sa iyong email sa ibaba" - }, - "enterhCaptchaUrl": { - "message": "Ipasok ang URL upang i load ang accessibility cookie para sa hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url ay kinakailangan", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Imbalidong URL" - }, "done": { "message": "Tapos na" }, - "accessibilityCookieSaved": { - "message": "Nai-save ang Accessibility cookie!" - }, - "noAccessibilityCookieSaved": { - "message": "Walang na-save na accessibility cookie" - }, "warning": { "message": "BABALA", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 50c489d479e..e94f4837d99 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Ajouter un nouvel élément" }, - "addNewFolder": { - "message": "Ajouter un dossier" - }, "view": { "message": "Affichage" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "Le \"Mot de passe du fichier\" et la \"Confirmation du mot de passe du fichier\" ne correspondent pas." }, - "hCaptchaUrl": { - "message": "URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Charger le cookie d'accessibilité" - }, - "registerAccessibilityUser": { - "message": "S'inscrire en tant qu'utilisateur avec accessibilité à", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Collez le lien envoyé à votre adresse e-mail ci-dessous" - }, - "enterhCaptchaUrl": { - "message": "Entrez l'URL pour charger le cookie d'accessibilité pour hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "L'URL hCaptcha est nécessaire", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "URL invalide" - }, "done": { "message": "Terminé" }, - "accessibilityCookieSaved": { - "message": "Cookie d'accessibilité enregistré !" - }, - "noAccessibilityCookieSaved": { - "message": "Aucun cookie d'accessibilité enregistré" - }, "warning": { "message": "AVERTISSEMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Le déverrouillage par biométrie n'est pas disponible actuellement pour une raison inconnue." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Autoriser" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index a74f71752af..471d7235945 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 2e3b60dfc29..954c50812be 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "פריט חדש" }, - "addNewFolder": { - "message": "תיקייה חדשה" - }, "view": { "message": "הצג" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"סיסמת קובץ\" ו\"אשר סיסמת קובץ\" אינם תואמים." }, - "hCaptchaUrl": { - "message": "כתובת אתר hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "טען עוגיית נגישות" - }, - "registerAccessibilityUser": { - "message": "הירשם כמשתמש נגישות ב-", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "העתק והדבק את הקישור שנשלח לאימייל שלך" - }, - "enterhCaptchaUrl": { - "message": "הזן את כתובת האתר לטעינת עוגיית נגישות עבור hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "יש להזין את כתובת האתר hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "כתובת אתר לא חוקית" - }, "done": { "message": "בוצע" }, - "accessibilityCookieSaved": { - "message": "עוגיית הנגישות נשמרה!" - }, - "noAccessibilityCookieSaved": { - "message": "עוגיית הנגישות לא נשמרה" - }, "warning": { "message": "אזהרה", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "ביטול נעילה ביומטרי אינו זמין כעת מסיבה לא ידועה." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "אשר" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 437b4845bdc..10307cc03ca 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 2b800ad99a2..8bdfcb40534 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Nova stavka" }, - "addNewFolder": { - "message": "Nova mapa" - }, "view": { "message": "Prikaz" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "Lozinka se ne podudara." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Učitaj kolačić pristupačnosti" - }, - "registerAccessibilityUser": { - "message": "Registriraj se kao korisnik pristupačnosti na", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopiraj i zalijepi vezu poslanu na e-poštu u nastavku" - }, - "enterhCaptchaUrl": { - "message": "Unesi URL za učitavanje kolačića pristupačnosti za hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url je obavezan", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Neispravan Url" - }, "done": { "message": "Gotovo" }, - "accessibilityCookieSaved": { - "message": "Kolačić pristupačnosti spremljen!" - }, - "noAccessibilityCookieSaved": { - "message": "Nije spremljen kolačić pristupačnosti" - }, "warning": { "message": "UPOZORENJE", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrijsko otključavanje trenutno nije dostupno iz nepoznatog razloga." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Autoriziraj" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 0b6f60e9fb2..8091cd72225 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Új elem" }, - "addNewFolder": { - "message": "Új mappa" - }, "view": { "message": "Megtekintés" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "A “Fájl jelszó” és a “Fájl jelszó megerősítés“ nem egyezik." }, - "hCaptchaUrl": { - "message": "hCaptcha webcím", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Akadálymentes süti betöltése" - }, - "registerAccessibilityUser": { - "message": "Regisztrálás akadálymentes felhasználóként:", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Vágólapon át szúrjuk be lentebb a kapott emailben levő hivatkozást." - }, - "enterhCaptchaUrl": { - "message": "Webcím megadása a hCaptcha akadálymentes sütijének betöltéséhez", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "A hCaptcha webcím megadása szükséges", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "A webcím érvénytelen." - }, "done": { "message": "Kész" }, - "accessibilityCookieSaved": { - "message": "Az ajadálymentes süti mentésre került." - }, - "noAccessibilityCookieSaved": { - "message": "Nem lett elmentve akadálymentes süti." - }, "warning": { "message": "FIGYELEM", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." }, + "itemDetails": { + "message": "Elem részletek" + }, + "itemName": { + "message": "Elem név" + }, + "loginCredentials": { + "message": "Bejelentkezési hitelesítések" + }, + "additionalOptions": { + "message": "Kiegészítő opciók" + }, + "itemHistory": { + "message": "Elem előzmény" + }, + "lastEdited": { + "message": "Utoljára szerkesztve" + }, + "upload": { + "message": "Feltöltés" + }, "authorize": { "message": "Hitelesítés" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Áthelyezés" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Idő megtakarítás automatikus kitöltéssel" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index a8c6005261b..1eeb32fdcc6 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Tambahkan Item Baru" }, - "addNewFolder": { - "message": "Tambahkan Folder Baru" - }, "view": { "message": "Tampilan" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Muat cookie aksesibilitas" - }, - "registerAccessibilityUser": { - "message": "Daftar sebagai pengguna aksesibilitas di", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Salin dan tempel tautan yang dikirimkan ke email Anda di bawah" - }, - "enterhCaptchaUrl": { - "message": "Masukkan URL untuk memuat cookie aksesibilitas untuk hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "url hCaptcha diperlukan", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Url tidak valid" - }, "done": { "message": "Selesai" }, - "accessibilityCookieSaved": { - "message": "Cookie aksesibilitas disimpan!" - }, - "noAccessibilityCookieSaved": { - "message": "Tidak ada cookie aksesibilitas yang disimpan" - }, "warning": { "message": "PERINGATAN", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index efe520767ab..db70740d96e 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Nuovo elemento" }, - "addNewFolder": { - "message": "Nuova cartella" - }, "view": { "message": "Visualizza" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "Le due password del file non corrispondono." }, - "hCaptchaUrl": { - "message": "URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Carica cookie di accessibilità" - }, - "registerAccessibilityUser": { - "message": "Registrati come utente di accessibilità su", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copia e incolla il link inviato alla tua email qui sotto" - }, - "enterhCaptchaUrl": { - "message": "Inserisci l'URL per caricare il cookie di accessibilità per hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "URL hCaptcha obbligatorio", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "URL non valido" - }, "done": { "message": "Fatto" }, - "accessibilityCookieSaved": { - "message": "Cookie di accessibilità salvato!" - }, - "noAccessibilityCookieSaved": { - "message": "Nessun cookie di accessibilità salvato" - }, "warning": { "message": "ATTENZIONE", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Autorizza" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index be632f79e12..47ba77508bc 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "新しいアイテムの追加" }, - "addNewFolder": { - "message": "新規フォルダーの追加" - }, "view": { "message": "表示" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "「ファイルパスワード」と「ファイルパスワードの確認」が一致しません。" }, - "hCaptchaUrl": { - "message": "hCaptcha URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "アクセシビリティクッキーを読み込む" - }, - "registerAccessibilityUser": { - "message": "アクセシビリティユーザーとして登録する", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "以下のメールに送信されたリンクをコピーして貼り付けてください" - }, - "enterhCaptchaUrl": { - "message": "hCaptcha のアクセシビリティクッキーを読み込むための URL を入力してください", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha URL が必要です", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "無効な URL" - }, "done": { "message": "完了" }, - "accessibilityCookieSaved": { - "message": "アクセシビリティクッキーを保存しました!" - }, - "noAccessibilityCookieSaved": { - "message": "アクセシビリティクッキーが保存されていません" - }, "warning": { "message": "警告", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "生体認証によるロック解除は、不明な理由により現在利用できません。" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "認可" }, @@ -3719,6 +3706,15 @@ "move": { "message": "移動" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 22c12c8b501..6a2912eed10 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "ახალი ჩანაწერი" }, - "addNewFolder": { - "message": "ახალი საქაღალდე" - }, "view": { "message": "ხედი" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "დასრულებული" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "გაფრთხილება", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index a74f71752af..471d7235945 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index ff5310f356d..767cdcf9d03 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "ಐಟಂ ಸೇರಿಸಿ" }, - "addNewFolder": { - "message": "ಹೊಸ ಫೋಲ್ಡರ್ ಸೇರಿಸಿ" - }, "view": { "message": "ವೀಕ್ಷಣೆ" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "ಎಚ್ಚರಿಕೆ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index c25ee35579a..b7b2a82b4b5 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "새 항목 추가" }, - "addNewFolder": { - "message": "새 폴더 추가" - }, "view": { "message": "보기" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "접근성 쿠키 불러오기" - }, - "registerAccessibilityUser": { - "message": "접근성 사용자로 등록", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha URL이 필요합니다", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "잘못된 URL" - }, "done": { "message": "완료" }, - "accessibilityCookieSaved": { - "message": "접근성 쿠키가 저장되었습니다!" - }, - "noAccessibilityCookieSaved": { - "message": "접근성 쿠키가 저장되지 않았습니다" - }, "warning": { "message": "경고", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index c6fe463d7f3..66dcf1da155 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Naujas elementas" }, - "addNewFolder": { - "message": "Naujas aplankas" - }, "view": { "message": "Peržiūrėti" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha nuoroda", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Užkrauti prieinamumo slapuką" - }, - "registerAccessibilityUser": { - "message": "Registruotis kaip prieinamumo vartotojas", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Nukopijuokite ir įklijuokite adresą atsiųstą į jūsų paštą apačioje" - }, - "enterhCaptchaUrl": { - "message": "Įveskite nuorodą reikalingą hCaptcha prieinamumo slapukui", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha nuoroda yra privaloma", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Klaidingas URL" - }, "done": { "message": "Atlikta" }, - "accessibilityCookieSaved": { - "message": "Prieinamumo slapukai išsaugoti!" - }, - "noAccessibilityCookieSaved": { - "message": "Prieinamumo slapukas neišsaugotas" - }, "warning": { "message": "ĮSPĖJIMAS", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 6527745e5cf..a1e76b5a521 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -110,7 +110,7 @@ "message": "E-pasta adrese" }, "verificationCodeTotp": { - "message": "Apstiprinājuma kods (TOTP)" + "message": "Apliecinājuma kods (TOTP)" }, "website": { "message": "Tīmekļvietne" @@ -573,7 +573,7 @@ "message": "Ievietot vietrādi starpliktuvē" }, "copyVerificationCodeTotp": { - "message": "Ievietot apstiprinājuma kodu (TOTP) starpliktuvē" + "message": "Ievietot Apliecinājuma kodu (TOTP) starpliktuvē" }, "length": { "message": "Garums" @@ -889,7 +889,7 @@ "message": "Nav vienumu, ko parādīt." }, "sendVerificationCode": { - "message": "Sūtīt apstiprinājuma kodu uz e-pastu" + "message": "Nosūtīt apliecinājuma kodu e-pastā" }, "sendCode": { "message": "Nosūtīt kodu" @@ -898,13 +898,13 @@ "message": "Kods nosūtīts" }, "verificationCode": { - "message": "Apstiprinājuma kods" + "message": "Apliecinājuma kods" }, "confirmIdentity": { "message": "Jāapstiprina identitāte, lai turpinātu." }, "verificationCodeRequired": { - "message": "Ir nepieciešams apstiprinājuma kods." + "message": "Apliecinājuma kods ir nepieciešams." }, "webauthnCancelOrTimeout": { "message": "Autentifikācija tika atcelta vai tā aizņēma pārāk daudz laika. Lūgums mēģināt vēlreiz." @@ -913,13 +913,13 @@ "message": "Atvērt jaunā cilnē" }, "invalidVerificationCode": { - "message": "Nederīgs apstiprinājuma kods" + "message": "Nederīgs apliecinājuma kods" }, "continue": { "message": "Turpināt" }, "verificationCodeEmailSent": { - "message": "E-pasts apstiprināšanai nosūtīts uz $EMAIL$.", + "message": "Apliecinājuma e-pasta ziņojums nosūtīts uz $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Pievienot jaunu vienumu" }, - "addNewFolder": { - "message": "Pievienot jaunu mapi" - }, "view": { "message": "Skats" }, @@ -1459,7 +1456,7 @@ "message": "Paroļu higiēnas, konta veselības un datu noplūžu pārskati, lai uzturētu glabātavu drošu." }, "premiumSignUpTotp": { - "message": "TOTP apstiprinājuma koda (2FA) veidotājs glabātavas pieteikšanās vienumiem." + "message": "TOTP apliecinājuma koda (2FA) veidotājs glabātavas pieteikšanās vienumiem." }, "premiumSignUpSupport": { "message": "Priekšrocīgs lietotāju atbalsts." @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Datnes parole\" un \"Apstiprināt datnes paroli\" vērtības nesakrīt." }, - "hCaptchaUrl": { - "message": "hCaptcha URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Ielādēt piekļūstamības sīkdatni" - }, - "registerAccessibilityUser": { - "message": "Reģistrēties kā piekļūstamības lietotājam", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Ievietot starpliktuvē un zemāk ielīmēt saiti, kas tika nosūtīta e-pastā" - }, - "enterhCaptchaUrl": { - "message": "Ievadīt URL, lai ielādētu hCaptcha piekļūstamības sīkdatni", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Ir nepieciešams hCaptcha URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Nederīgs URL" - }, "done": { "message": "Darīts" }, - "accessibilityCookieSaved": { - "message": "Piekļūstamības sīkdatne saglabāta." - }, - "noAccessibilityCookieSaved": { - "message": "Nav saglabātas piekļūstamības sīkdadnes" - }, "warning": { "message": "UZMANĪBU", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -2022,10 +1988,10 @@ "message": "Padarīt divpakāpju apliecināšanu plūdenu" }, "totpHelper": { - "message": "Bitwarden var glabāt un aizpildīt divpakāpju apliecināšanas kodus. Atslēga jāievieto starpliktuvē un jāielīmē šajā laukā." + "message": "Bitwarden var glabāt un aizpildīt divpakāpju apliecinājuma kodus. Atslēga jāievieto starpliktuvē un jāielīmē šajā laukā." }, "totpHelperWithCapture": { - "message": "Bitwarden var glabāt un aizpildīt divpakāpju apliecināšanas kodus. Jāizvēlas kameras ikona, lai veiktu tīmekļvietnes autentificētāja kvadrātkoda ekrānuzņēmumu, vai jāievieto starpliktuvē atslēga un jāielīmē šajā laukā." + "message": "Bitwarden var glabāt un aizpildīt divpakāpju apliecinājuma kodus. Jāizvēlas kameras ikona, lai veiktu tīmekļvietnes autentificētāja kvadrātkoda ekrānuzņēmumu, vai jāievieto starpliktuvē atslēga un jāielīmē šajā laukā." }, "premium": { "message": "Premium", @@ -2406,13 +2372,13 @@ "message": "Viens vai vairāki apvienības nosacījumi ietekmē Send iespējas." }, "emailVerificationRequired": { - "message": "Nepieciešama e-pasta adreses apstiprināšana" + "message": "Nepieciešama e-pasta adreses apliecināšana" }, "emailVerifiedV2": { "message": "E-pasta adrese ir apliecināta" }, "emailVerificationRequiredDesc": { - "message": "Ir jāapstiprina e-pasta adrese, lai izmantotu šo iespēju." + "message": "Ir jāapliecina sava e-pasta adrese, lai izmantotu šo iespēju." }, "passwordPrompt": { "message": "Galvenās paroles pārvaicāšana" @@ -2442,7 +2408,7 @@ "message": "Jāmēģina vēlreiz" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Šai darbībai ir nepieciešama apliecināšana. Jāiestata PIN, lai turpinātu." + "message": "Šai darbībai ir nepieciešama apliecinājums. Jāiestata PIN, lai turpinātu." }, "setPin": { "message": "Iestatīt PIN" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." }, + "itemDetails": { + "message": "Vienuma dati" + }, + "itemName": { + "message": "Vienuma nosaukums" + }, + "loginCredentials": { + "message": "Pieteikšanās dati" + }, + "additionalOptions": { + "message": "Papildu iespējas" + }, + "itemHistory": { + "message": "Vienuma vēsture" + }, + "lastEdited": { + "message": "Pēdējoreiz labots" + }, + "upload": { + "message": "Augšupielādēt" + }, "authorize": { "message": "Pilnvarot" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Pārvietot" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Laika ietaupīšana ar automātisko aizpildi" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 35345d8985e..152877fa552 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Dodaj novu stavku" }, - "addNewFolder": { - "message": "Dodaj novu fasciklu" - }, "view": { "message": "Pogled" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "UPOZORENJE", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 69a6bd66d2e..3fcb52bade5 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "പുതിയ ഇനം ചേർക്കുക" }, - "addNewFolder": { - "message": "പുതിയ ഫോൾഡർ ചേർക്കുക" - }, "view": { "message": "പ്രദർശനം" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "മുന്നറിയിപ്പ്", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index a74f71752af..471d7235945 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 37bef7dbf66..724194808cf 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index d5bd0fc976e..5522218037f 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Legg til et nytt objekt" }, - "addNewFolder": { - "message": "Legg til en ny mappe" - }, "view": { "message": "Vis" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "«Filpassord» og «Bekreft filpassord» stemmer ikke overens." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Last inn tilgjengelighetsinformasjonskapsel" - }, - "registerAccessibilityUser": { - "message": "Registrer deg som tilgjengelighetsbruker på", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopier og lim inn linken som er sendt til e-posten din nedenfor" - }, - "enterhCaptchaUrl": { - "message": "Skriv inn URL-adressen for å laste tilgjengelighets-informasjonskapsler for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url er påkrevd", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Ugyldig url" - }, "done": { "message": "Ferdig" }, - "accessibilityCookieSaved": { - "message": "Tilgjengelighets-informasjonskapsel lagret!" - }, - "noAccessibilityCookieSaved": { - "message": "Ingen tilgjengelighets-informasjonskapsel lagret" - }, "warning": { "message": "ADVARSEL", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Autoriser" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Flytt" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Spar tid med auto-utfylling" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index dc710b5fbda..a56268aa671 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index d7bb803ed62..cad8a495dbf 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -446,10 +446,10 @@ "message": "Veld toevoegen" }, "editField": { - "message": "Edit field" + "message": "Veld bewerken" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Weet je zeker dat je deze bijlage definitief wilt verwijderen?" }, "fieldType": { "message": "Veldtype" @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Nieuw item toevoegen" }, - "addNewFolder": { - "message": "Nieuwe map toevoegen" - }, "view": { "message": "Weergeven" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Bestandswachtwoord\" en \"Bestandswachtwoord bevestigen\" komen niet overeen." }, - "hCaptchaUrl": { - "message": "hCaptcha-URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Toegankelijkheidscookie laden" - }, - "registerAccessibilityUser": { - "message": "Registreer als toegankelijkheidsgebruiker op", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopieer en plak de link die naar uw e-mail is gestuurd hieronder" - }, - "enterhCaptchaUrl": { - "message": "Voer een URL in om toegankelijkheidscookie voor hCaptcha te laden", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha-URL is vereist", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Ongeldige URL" - }, "done": { "message": "Klaar" }, - "accessibilityCookieSaved": { - "message": "Toegankelijkheidscookie opgeslagen!" - }, - "noAccessibilityCookieSaved": { - "message": "Geen toegankelijkheidscookie opgeslagen" - }, "warning": { "message": "WAARSCHUWING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." }, + "itemDetails": { + "message": "Itemdetails" + }, + "itemName": { + "message": "Itemnaam" + }, + "loginCredentials": { + "message": "Inloggegevens" + }, + "additionalOptions": { + "message": "Extra opties" + }, + "itemHistory": { + "message": "Itemgeschiedenis" + }, + "lastEdited": { + "message": "Laatst gewijzigd" + }, + "upload": { + "message": "Uploaden" + }, "authorize": { "message": "Autoriseren" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Verplaatsen" }, + "newFolder": { + "message": "Nieuwe map" + }, + "folderName": { + "message": "Mapnaam" + }, + "folderHintText": { + "message": "Je kunt een map onderbrengen door het toevoegen van de naam van de bovenliggende map gevolgd door een \"/\". Voorbeeld: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Tijd besparen met automatisch aanvullen" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 0a639b209c5..b3f0f0c5d20 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Legg til ei ny oppføring" }, - "addNewFolder": { - "message": "Legg til mappe" - }, "view": { "message": "Vising" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha-nettadresse", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Ei hCaptcha-nettadresse er påkravt", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Ferdig" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "ÅTVARING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 83e1f39d635..d584cd5d117 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index c4978d6c74d..61c438c0425 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Nowy element" }, - "addNewFolder": { - "message": "Nowy folder" - }, "view": { "message": "Widok" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“Hasło pliku” i “Potwierdź hasło pliku“ nie pasują do siebie." }, - "hCaptchaUrl": { - "message": "Adres URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Załaduj pliki cookies ułatwień dostępu" - }, - "registerAccessibilityUser": { - "message": "Zarejestruj się jako użytkownik ułatwień dostępu na", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Skopiuj i wklej link wysłany na poniższy adres e-mail" - }, - "enterhCaptchaUrl": { - "message": "Wpisz adres URL, aby załadować pliki cookies ułatwień dostępu dla hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Adres URL hCaptcha jest wymagany", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Adres URL jest nieprawidłowy" - }, "done": { "message": "Gotowe" }, - "accessibilityCookieSaved": { - "message": "Pliki cookies ułatwień dostępu zostały zapisane!" - }, - "noAccessibilityCookieSaved": { - "message": "Brak zapisanych plików cookies ułatwień dostępu" - }, "warning": { "message": "UWAGA", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Odblokowanie biometryczne jest obecnie niedostępne z nieznanego powodu." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Autoryzuj" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 591357b109d..e44bb7abc8c 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Adicionar Novo Item" }, - "addNewFolder": { - "message": "Adicionar Nova Pasta" - }, "view": { "message": "Ver" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Senha do arquivo\" e \"Confirmação de senha\" não correspondem." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Carregar Cookie de Acessibilidade" - }, - "registerAccessibilityUser": { - "message": "Registre-se como um usuário de acessibilidade em", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copie e cole o link enviado para o seu email abaixo" - }, - "enterhCaptchaUrl": { - "message": "Digite a URL para carregar o cookie de acessibilidade para hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "O URL do hCaptcha é necessário", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "URL inválida" - }, "done": { "message": "Concluído" }, - "accessibilityCookieSaved": { - "message": "Cookies de acessibilidade salvos!" - }, - "noAccessibilityCookieSaved": { - "message": "Nenhum cookie de acessibilidade salvo" - }, "warning": { "message": "AVISO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueio por biometria está indisponível por razões desconhecidas." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Autorizar" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Mover" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index a64b06faa68..ef657f1b658 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Novo item" }, - "addNewFolder": { - "message": "Nova pasta" - }, "view": { "message": "Ver" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Palavra-passe do ficheiro\" e \"Confirmar palavra-passe do ficheiro\" não correspondem." }, - "hCaptchaUrl": { - "message": "URL do hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Carregar cookie de acessibilidade" - }, - "registerAccessibilityUser": { - "message": "Registar como utilizador de acessibilidade em", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copie e cole o link enviado para o seu e-mail abaixo" - }, - "enterhCaptchaUrl": { - "message": "Introduza o URL para carregar o cookie de acessibilidade do hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "É necessário o URL do hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "URL inválido" - }, "done": { "message": "Concluído" }, - "accessibilityCookieSaved": { - "message": "Cookie de acessibilidade guardado!" - }, - "noAccessibilityCookieSaved": { - "message": "Nenhum cookie de acessibilidade guardado" - }, "warning": { "message": "AVISO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." }, + "itemDetails": { + "message": "Detalhes do item" + }, + "itemName": { + "message": "Nome do item" + }, + "loginCredentials": { + "message": "Credenciais de início de sessão" + }, + "additionalOptions": { + "message": "Opções adicionais" + }, + "itemHistory": { + "message": "Histórico do item" + }, + "lastEdited": { + "message": "Última edição" + }, + "upload": { + "message": "Carregar" + }, "authorize": { "message": "Autorizar" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Mover" }, + "newFolder": { + "message": "Nova pasta" + }, + "folderName": { + "message": "Nome da pasta" + }, + "folderHintText": { + "message": "Crie uma subpasta adicionando o nome da pasta principal seguido de um \"/\". Exemplo: Redes Sociais/Fóruns" + }, "newLoginNudgeTitle": { "message": "Poupe tempo com o preenchimento automático" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index f74dbeeb705..9024aaa5533 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Articol nou" }, - "addNewFolder": { - "message": "Dosar nou" - }, "view": { "message": "Afișare" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "Url-ul hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Încărcare cookie de accesibilitate" - }, - "registerAccessibilityUser": { - "message": "Înregistrați-vă ca utilizator de accesibilitate la", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copiați și lipiți linkul trimis pe e-mailul dvs. mai jos" - }, - "enterhCaptchaUrl": { - "message": "Introduceți URL-ul pentru încărcarea cookie-ului de accesibilitate pentru hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Url-ul hCaptcha este necesar", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Url invalid" - }, "done": { "message": "Făcut" }, - "accessibilityCookieSaved": { - "message": "Cookie de accesibilitate salvat!" - }, - "noAccessibilityCookieSaved": { - "message": "Niciun cookie de accesibilitate salvat" - }, "warning": { "message": "AVERTISMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 428d4103039..b61c03462a3 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Новый элемент" }, - "addNewFolder": { - "message": "Новый папка" - }, "view": { "message": "Вид" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Пароль к файлу\" и \"Подтверждение пароля к файлу\" не совпадают." }, - "hCaptchaUrl": { - "message": "URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Загрузить cookie специальных возможностей" - }, - "registerAccessibilityUser": { - "message": "Зарегистрироваться как пользователь специальных возможностей на", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Скопируйте и вставьте ниже ссылку, отправленную на вашу почту" - }, - "enterhCaptchaUrl": { - "message": "Введите URL для загрузки cookie специальных возможностей для hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Требуется URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Неверный URL" - }, "done": { "message": "Готово" }, - "accessibilityCookieSaved": { - "message": "Cookie специальных возможностей сохранены!" - }, - "noAccessibilityCookieSaved": { - "message": "Отсутствуют сохраненные cookie специальных возможностей" - }, "warning": { "message": "ВНИМАНИЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." }, + "itemDetails": { + "message": "Информация об элементе" + }, + "itemName": { + "message": "Название элемента" + }, + "loginCredentials": { + "message": "Данные для авторизации" + }, + "additionalOptions": { + "message": "Дополнительные настройки" + }, + "itemHistory": { + "message": "История элемента" + }, + "lastEdited": { + "message": "Последнее изменение" + }, + "upload": { + "message": "Загрузить" + }, "authorize": { "message": "Разрешить" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Переместить" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Экономьте время с помощью автозаполнения" }, @@ -3747,7 +3743,7 @@ "message": "Упрощение создания аккаунтов" }, "newIdentityNudgeBody": { - "message": "С помощью личностей можно быстро заполнять длинные регистрационные или контактные формы." + "message": "С помощью личной информации можно быстро заполнять длинные регистрационные или контактные формы." }, "newNoteNudgeTitle": { "message": "Храните ваши конфиденциальные данные в безопасности" diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index e90c27094f8..bb843718e28 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "නව බහාලුමක් එකතු කරන්න" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 3503000b6b0..03c362d2cb3 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Pridať novú položku" }, - "addNewFolder": { - "message": "Vytvoriť nový priečinok" - }, "view": { "message": "Zobraziť" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Heslo súboru\" a \"Potvrdiť heslo súboru\" sa nezhodujú." }, - "hCaptchaUrl": { - "message": "URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Načítať súbor cookie prístupnosti" - }, - "registerAccessibilityUser": { - "message": "Zaregistrujte sa ako používateľ prístupnosti na", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Skopírujte a vložte odkaz zaslaný na váš e-mail nižšie" - }, - "enterhCaptchaUrl": { - "message": "Zadajte adresu URL na načítanie súboru cookie prístupnosti pre hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Vyžaduje sa URL hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Neplatná URL" - }, "done": { "message": "Hotovo" }, - "accessibilityCookieSaved": { - "message": "Súbor cookie prístupnosti uložený!" - }, - "noAccessibilityCookieSaved": { - "message": "Nie je uložený žiadny súbor cookie prístupnosti" - }, "warning": { "message": "UPOZORNENIE", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Odomykanie biometrickými údajmi je z neznámych dôvodov nedostupné." }, + "itemDetails": { + "message": "Podrobnosti o položke" + }, + "itemName": { + "message": "Názov položky" + }, + "loginCredentials": { + "message": "Prihlasovacie údaje" + }, + "additionalOptions": { + "message": "Ďalšie možnosti" + }, + "itemHistory": { + "message": "História položky" + }, + "lastEdited": { + "message": "Posledná úprava" + }, + "upload": { + "message": "Nahrať" + }, "authorize": { "message": "Autorizovať" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Presunúť" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Ušetrite čas s automatickým vypĺňaním" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index e81fa844b9b..6cc9e6fc197 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Dodaj nov element" }, - "addNewFolder": { - "message": "Dodaj novo mapo" - }, "view": { "message": "Pogled" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "OPOZORILO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 1684d188905..7509870b0a0 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Додај нову ставку" }, - "addNewFolder": { - "message": "Додај нову фасциклу" - }, "view": { "message": "Приказ" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "Унете лозинке се не подударају." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Учитај колачић приступачности" - }, - "registerAccessibilityUser": { - "message": "Региструјте се као корисник приступачности на", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Копирајте и испод налепите линк који је послат на адресу ваше е-поште" - }, - "enterhCaptchaUrl": { - "message": "Упишите адресу како би се учитао колачић приступачности за hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha адреса је неопходна", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Неисправна адреса" - }, "done": { "message": "Готово" }, - "accessibilityCookieSaved": { - "message": "Колачић приступачности сачуван!" - }, - "noAccessibilityCookieSaved": { - "message": "Нема сачуваног колачића приступачности" - }, "warning": { "message": "УПОЗОРЕЊЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Биометријско откључавање није доступно из непознатог разлога." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Ауторизуј" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Премести" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Уштедите време са ауто-пуњењем" }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index a9a1e9b3a03..eb0d2dcd547 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Nytt objekt" }, - "addNewFolder": { - "message": "Ny mapp" - }, "view": { "message": "Visa" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha-URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Ladda tillgänglighets-cookie" - }, - "registerAccessibilityUser": { - "message": "Registrera som en tillgänglighetsanvändare på", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Kopiera och klistra in länken som skickats till din e-post nedan" - }, - "enterhCaptchaUrl": { - "message": "Ange URL för att ladda tillgänglighets-cookie för hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha-URL krävs", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Ogiltig URL" - }, "done": { "message": "Klar" }, - "accessibilityCookieSaved": { - "message": "Tillgänglighets-cookie sparad!" - }, - "noAccessibilityCookieSaved": { - "message": "Ingen tillgänglighets-cookie sparad" - }, "warning": { "message": "VARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index a74f71752af..471d7235945 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "New item" }, - "addNewFolder": { - "message": "New folder" - }, "view": { "message": "View" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Invalid Url" - }, "done": { "message": "Done" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 62979e4290f..43aaa697a8a 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "เพิ่มรายการใหม่" }, - "addNewFolder": { - "message": "เพิ่มโฟลเดอร์ใหม่" - }, "view": { "message": "แสดง" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." }, - "hCaptchaUrl": { - "message": "hCaptcha Url", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Load accessibility cookie" - }, - "registerAccessibilityUser": { - "message": "Register as an accessibility user at", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" - }, - "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Url ไม่ถูกต้อง" - }, "done": { "message": "เสร็จสิ้น" }, - "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" - }, - "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" - }, "warning": { "message": "คำเตือน", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 2927ef81f5c..35fd2be4706 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Yeni kayıt" }, - "addNewFolder": { - "message": "Yeni klasör" - }, "view": { "message": "Görüntüle" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Dosya parolası\" ile \"Dosya parolasını onaylayın\" eşleşmiyor." }, - "hCaptchaUrl": { - "message": "hCaptcha adresi", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Erişilebilirlik çerezini yükle" - }, - "registerAccessibilityUser": { - "message": "Erişilebilirlik kullanıcısı olarak kaydolun:", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "E-postanıza gönderilen bağlantıyı kopyalayıp aşağıya yapıştırın" - }, - "enterhCaptchaUrl": { - "message": "hCaptcha erişilebilirlik çerezini yüklemek için adresi yazın", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha adresi gereklidir", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Geçersiz adres" - }, "done": { "message": "Tamam" }, - "accessibilityCookieSaved": { - "message": "Erişilebilirlik çerezi kaydedildi." - }, - "noAccessibilityCookieSaved": { - "message": "Erişilebilirlik çerezi kaydedilmedi" - }, "warning": { "message": "UYARI", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Kayıt ayrıntıları" + }, + "itemName": { + "message": "Kayıt adı" + }, + "loginCredentials": { + "message": "Hesap bilgileri" + }, + "additionalOptions": { + "message": "Ek seçenekler" + }, + "itemHistory": { + "message": "Kayıt geçmişi" + }, + "lastEdited": { + "message": "Son düzenlenme" + }, + "upload": { + "message": "Yükle" + }, "authorize": { "message": "Yetkilendir" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Taşı" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Otomatik doldurmayla zaman kazanın" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index aa75d37f54a..156c8610ba2 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -446,10 +446,10 @@ "message": "Додати поле" }, "editField": { - "message": "Edit field" + "message": "Редагувати поле" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Ви дійсно хочете остаточно видалити це вкладення?" }, "fieldType": { "message": "Тип поля" @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Новий запис" }, - "addNewFolder": { - "message": "Нова тека" - }, "view": { "message": "Переглянути" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "Пароль файлу та підтвердження пароля відрізняються." }, - "hCaptchaUrl": { - "message": "URL-адреса hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Завантажити cookie спеціальних можливостей" - }, - "registerAccessibilityUser": { - "message": "Реєстрація користувача зі спеціальними можливостями", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Вставте нижче код, надісланий на вашу е-пошту" - }, - "enterhCaptchaUrl": { - "message": "Введіть URL-адресу для завантаження cookie спеціальних можливостей для hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Необхідно ввести URL-адресу hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Недійсна URL-адреса" - }, "done": { "message": "Виконано" }, - "accessibilityCookieSaved": { - "message": "Cookie спеціальних можливостей збережено!" - }, - "noAccessibilityCookieSaved": { - "message": "Немає збережених cookie особливих можливостей" - }, "warning": { "message": "ПОПЕРЕДЖЕННЯ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Біометричне розблокування зараз недоступне з невідомої причини." }, + "itemDetails": { + "message": "Подробиці запису" + }, + "itemName": { + "message": "Назва запису" + }, + "loginCredentials": { + "message": "Облікові дані для входу" + }, + "additionalOptions": { + "message": "Додаткові налаштування" + }, + "itemHistory": { + "message": "Історія запису" + }, + "lastEdited": { + "message": "Востаннє редаговано" + }, + "upload": { + "message": "Вивантажити" + }, "authorize": { "message": "Авторизувати" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Перемістити" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Заощаджуйте час з автозаповненням" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 826b0033aa2..2fd354ed08b 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "Mục mới" }, - "addNewFolder": { - "message": "Thư mục mới" - }, "view": { "message": "Xem" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“Mật khẩu tập tin” và “Nhập lại mật khẩu tập tin” không khớp." }, - "hCaptchaUrl": { - "message": "Địa chỉ hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "Tải cookie trợ năng" - }, - "registerAccessibilityUser": { - "message": "Đăng ký như người dùng trợ năng tại", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "Sao chép và dán liên kết được gửi tới email của bạn bên dưới" - }, - "enterhCaptchaUrl": { - "message": "Nhập địa chỉ để tải cookie trợ năng cho hCaptcha", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "Địa chỉ hCaptcha là bắt buộc", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "Địa chỉ không hợp lệ" - }, "done": { "message": "Xong" }, - "accessibilityCookieSaved": { - "message": "Đã lưu cookie trợ năng!" - }, - "noAccessibilityCookieSaved": { - "message": "Không có cookie trợ năng nào được lưu" - }, "warning": { "message": "CẢNH BÁO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "Authorize" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 703680440d8..7146a8698c3 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -446,10 +446,10 @@ "message": "添加字段" }, "editField": { - "message": "Edit field" + "message": "编辑字段" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "确定要永久删除此附件吗?" }, "fieldType": { "message": "字段类型" @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "新增项目" }, - "addNewFolder": { - "message": "新增文件夹" - }, "view": { "message": "查看" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "「文件密码」与「确认文件密码」不一致。" }, - "hCaptchaUrl": { - "message": "hCaptcha URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "加载无障碍 Cookie" - }, - "registerAccessibilityUser": { - "message": "注册为无障碍用户到", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "复制发送到您电子邮箱的链接然后粘贴在下方" - }, - "enterhCaptchaUrl": { - "message": "输入 URL 以加载 hCaptcha 的无障碍 Cookie", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha URL 必填", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "无效的 URL" - }, "done": { "message": "完成" }, - "accessibilityCookieSaved": { - "message": "无障碍 Cookie 已保存!" - }, - "noAccessibilityCookieSaved": { - "message": "未保存任何无障碍 Cookie!" - }, "warning": { "message": "警告", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "由于某个未知的原因,生物识别解锁当前不可用。" }, + "itemDetails": { + "message": "项目详细信息" + }, + "itemName": { + "message": "项目名称" + }, + "loginCredentials": { + "message": "登录凭据" + }, + "additionalOptions": { + "message": "附加选项" + }, + "itemHistory": { + "message": "项目历史记录" + }, + "lastEdited": { + "message": "最后编辑于" + }, + "upload": { + "message": "上传" + }, "authorize": { "message": "批准" }, @@ -3719,6 +3706,15 @@ "move": { "message": "移动" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "使用自动填充节省时间" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 0f99d4a3151..eeeaf9b152e 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -1103,9 +1103,6 @@ "addNewItem": { "message": "新增項目" }, - "addNewFolder": { - "message": "新增資料夾" - }, "view": { "message": "檢視" }, @@ -1725,40 +1722,9 @@ "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "「檔案密碼」與「確認檔案密碼」不一致。" }, - "hCaptchaUrl": { - "message": "hCaptcha URL", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "loadAccessibilityCookie": { - "message": "載入無障礙 Cookie" - }, - "registerAccessibilityUser": { - "message": "以無障礙使用者,在下述位置註冊:", - "description": "ex. Register as an accessibility user at hcaptcha.com" - }, - "copyPasteLink": { - "message": "複製寄到您信箱的連結然後貼在下方" - }, - "enterhCaptchaUrl": { - "message": "輸入 URL 以載入 hCaptcha 的無障礙 Cookie", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "hCaptchaUrlRequired": { - "message": "hCaptcha URL 為必填", - "description": "hCaptcha is the name of a website, should not be translated" - }, - "invalidUrl": { - "message": "無效的 URL" - }, "done": { "message": "完成" }, - "accessibilityCookieSaved": { - "message": "已儲存無障礙 Cookie!" - }, - "noAccessibilityCookieSaved": { - "message": "未儲存無障礙 Cookie" - }, "warning": { "message": "警告", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -3647,6 +3613,27 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "基於不明原因,生物辨識解鎖無法使用。" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "authorize": { "message": "授權" }, @@ -3719,6 +3706,15 @@ "move": { "message": "Move" }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder Name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts index 25db5b695e7..f132a464788 100644 --- a/apps/desktop/src/main/menu/menu.file.ts +++ b/apps/desktop/src/main/menu/menu.file.ts @@ -114,8 +114,8 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { private get addNewFolder(): MenuItemConstructorOptions { return { - id: "addNewFolder", - label: this.localize("addNewFolder"), + id: "newFolder", + label: this.localize("newFolder"), click: () => this.sendMessage("newFolder"), enabled: !this._isLocked, }; diff --git a/apps/desktop/src/main/updater.main.ts b/apps/desktop/src/main/updater.main.ts index 0e2efa66f91..51d5073911e 100644 --- a/apps/desktop/src/main/updater.main.ts +++ b/apps/desktop/src/main/updater.main.ts @@ -1,6 +1,6 @@ import { dialog, shell } from "electron"; import log from "electron-log"; -import { autoUpdater } from "electron-updater"; +import { autoUpdater, UpdateDownloadedEvent, VerifyUpdateSupport } from "electron-updater"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -15,6 +15,8 @@ export class UpdaterMain { private doingUpdateCheck = false; private doingUpdateCheckWithFeedback = false; private canUpdate = false; + private updateDownloaded: UpdateDownloadedEvent = null; + private originalRolloutFunction: VerifyUpdateSupport = null; constructor( private i18nService: I18nService, @@ -22,6 +24,8 @@ export class UpdaterMain { ) { autoUpdater.logger = log; + this.originalRolloutFunction = autoUpdater.isUserWithinRollout; + const linuxCanUpdate = process.platform === "linux" && isAppImage(); const windowsCanUpdate = process.platform === "win32" && !isWindowsStore() && !isWindowsPortable(); @@ -57,20 +61,16 @@ export class UpdaterMain { }); if (result.response === 0) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - autoUpdater.downloadUpdate(); + await autoUpdater.downloadUpdate(); } else { this.reset(); } } }); - autoUpdater.on("update-not-available", () => { + autoUpdater.on("update-not-available", async () => { if (this.doingUpdateCheckWithFeedback && this.windowMain.win != null) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - dialog.showMessageBox(this.windowMain.win, { + await dialog.showMessageBox(this.windowMain.win, { message: this.i18nService.t("noUpdatesAvailable"), buttons: [this.i18nService.t("ok")], defaultId: 0, @@ -86,22 +86,8 @@ export class UpdaterMain { return; } - const result = await dialog.showMessageBox(this.windowMain.win, { - type: "info", - title: this.i18nService.t("bitwarden") + " - " + this.i18nService.t("restartToUpdate"), - message: this.i18nService.t("restartToUpdate"), - detail: this.i18nService.t("restartToUpdateDesc", info.version), - buttons: [this.i18nService.t("restart"), this.i18nService.t("later")], - cancelId: 1, - defaultId: 0, - noLink: true, - }); - - if (result.response === 0) { - // Quit and install have a different window logic, setting `isQuitting` just to be safe. - this.windowMain.isQuitting = true; - autoUpdater.quitAndInstall(true, true); - } + this.updateDownloaded = info; + await this.promptRestartUpdate(info); }); autoUpdater.on("error", (error) => { @@ -117,15 +103,22 @@ export class UpdaterMain { } async checkForUpdate(withFeedback = false) { - if (this.doingUpdateCheck || isDev()) { + if (isDev()) { + return; + } + + if (this.updateDownloaded && withFeedback) { + await this.promptRestartUpdate(this.updateDownloaded); + return; + } + + if (this.doingUpdateCheck) { return; } if (!this.canUpdate) { if (withFeedback) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - shell.openExternal("https://github.com/bitwarden/clients/releases"); + void shell.openExternal("https://github.com/bitwarden/clients/releases"); } return; @@ -134,6 +127,10 @@ export class UpdaterMain { this.doingUpdateCheckWithFeedback = withFeedback; if (withFeedback) { autoUpdater.autoDownload = false; + + // If the user has explicitly checked for updates, we want to bypass + // the current staging rollout percentage + autoUpdater.isUserWithinRollout = (info) => true; } await autoUpdater.checkForUpdates(); @@ -141,7 +138,29 @@ export class UpdaterMain { private reset() { autoUpdater.autoDownload = true; + // Reset the rollout check to the default behavior + autoUpdater.isUserWithinRollout = this.originalRolloutFunction; this.doingUpdateCheck = false; + this.updateDownloaded = null; + } + + private async promptRestartUpdate(info: UpdateDownloadedEvent) { + const result = await dialog.showMessageBox(this.windowMain.win, { + type: "info", + title: this.i18nService.t("bitwarden") + " - " + this.i18nService.t("restartToUpdate"), + message: this.i18nService.t("restartToUpdate"), + detail: this.i18nService.t("restartToUpdateDesc", info.version), + buttons: [this.i18nService.t("restart"), this.i18nService.t("later")], + cancelId: 1, + defaultId: 0, + noLink: true, + }); + + if (result.response === 0) { + // Quit and install have a different window logic, setting `isQuitting` just to be safe. + this.windowMain.isQuitting = true; + autoUpdater.quitAndInstall(true, true); + } } private userDisabledUpdates(): boolean { diff --git a/apps/desktop/src/platform/main/clipboard.main.ts b/apps/desktop/src/platform/main/clipboard.main.ts index 5ca6aa29529..3fdbca45822 100644 --- a/apps/desktop/src/platform/main/clipboard.main.ts +++ b/apps/desktop/src/platform/main/clipboard.main.ts @@ -1,16 +1,30 @@ -import { ipcMain } from "electron"; +import { app, ipcMain } from "electron"; import { clipboards } from "@bitwarden/desktop-napi"; import { ClipboardWriteMessage } from "../types/clipboard"; export class ClipboardMain { + lastSavedValue: string | null = null; + init() { + app.on("before-quit", async () => { + if (this.lastSavedValue == null) { + return; + } + + const clipboardNow = await clipboards.read(); + if (clipboardNow == this.lastSavedValue) { + await clipboards.write("", false); + } + }); + ipcMain.handle("clipboard.read", async (_event: any, _message: any) => { return await clipboards.read(); }); ipcMain.handle("clipboard.write", async (_event: any, message: ClipboardWriteMessage) => { + this.lastSavedValue = message.text; return await clipboards.write(message.text, message.password ?? false); }); } diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 591ff6fa8cf..37a8114c1d1 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -207,9 +207,7 @@ export class EncryptedMessageHandlerService { return { status: "failure" }; } - const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipher, activeUserId); cipherView.name = credentialUpdatePayload.name; cipherView.login.password = credentialUpdatePayload.password; cipherView.login.username = credentialUpdatePayload.userName; diff --git a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts index 6992455a8a6..b4be2406c4b 100644 --- a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Component({ selector: "app-vault-add-edit-custom-fields", templateUrl: "add-edit-custom-fields.component.html", + standalone: false, }) export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) { diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index 2c8b5a8321a..eb04003a418 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -30,6 +30,7 @@ const BroadcasterSubscriptionId = "AddEditComponent"; @Component({ selector: "app-vault-add-edit", templateUrl: "add-edit.component.html", + standalone: false, }) export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnChanges, OnDestroy { @ViewChild("form") diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index ea4f49b8431..a116a4d2acb 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -17,6 +18,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-vault-attachments", templateUrl: "attachments.component.html", + standalone: false, }) export class AttachmentsComponent extends BaseAttachmentsComponent { constructor( @@ -33,6 +35,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { billingAccountProfileStateService: BillingAccountProfileStateService, accountService: AccountService, toastService: ToastService, + configService: ConfigService, ) { super( cipherService, @@ -49,6 +52,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { billingAccountProfileStateService, accountService, toastService, + configService, ); } } diff --git a/apps/desktop/src/vault/app/vault/collections.component.ts b/apps/desktop/src/vault/app/vault/collections.component.ts index e7684c3c07a..46455d04cd2 100644 --- a/apps/desktop/src/vault/app/vault/collections.component.ts +++ b/apps/desktop/src/vault/app/vault/collections.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-vault-collections", templateUrl: "collections.component.html", + standalone: false, }) export class CollectionsComponent extends BaseCollectionsComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts index cdb879693c0..cecd5cd671c 100644 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts @@ -14,6 +14,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-folder-add-edit", templateUrl: "folder-add-edit.component.html", + standalone: false, }) export class FolderAddEditComponent extends BaseFolderAddEditComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts index 4a87617d8f4..e83ce0d56ea 100644 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ b/apps/desktop/src/vault/app/vault/password-history.component.ts @@ -10,6 +10,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-password-history", templateUrl: "password-history.component.html", + standalone: false, }) export class PasswordHistoryComponent extends BasePasswordHistoryComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/share.component.ts b/apps/desktop/src/vault/app/vault/share.component.ts index 6926e7e2abf..50842439323 100644 --- a/apps/desktop/src/vault/app/vault/share.component.ts +++ b/apps/desktop/src/vault/app/vault/share.component.ts @@ -13,6 +13,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi @Component({ selector: "app-vault-share", templateUrl: "share.component.html", + standalone: false, }) export class ShareComponent extends BaseShareComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts index 161c9ae5353..22372410e5b 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts @@ -5,5 +5,6 @@ import { CollectionFilterComponent as BaseCollectionFilterComponent } from "@bit @Component({ selector: "app-collection-filter", templateUrl: "collection-filter.component.html", + standalone: false, }) export class CollectionFilterComponent extends BaseCollectionFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts index 790d31a65e6..d7364808f6d 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts @@ -5,5 +5,6 @@ import { FolderFilterComponent as BaseFolderFilterComponent } from "@bitwarden/a @Component({ selector: "app-folder-filter", templateUrl: "folder-filter.component.html", + standalone: false, }) export class FolderFilterComponent extends BaseFolderFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts index 39f1c0200ea..33a47cdc91f 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts @@ -12,6 +12,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-organization-filter", templateUrl: "organization-filter.component.html", + standalone: false, }) export class OrganizationFilterComponent extends BaseOrganizationFilterComponent { get show() { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts index 5d43fd52d20..276b11d7138 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts @@ -5,5 +5,6 @@ import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/a @Component({ selector: "app-status-filter", templateUrl: "status-filter.component.html", + standalone: false, }) export class StatusFilterComponent extends BaseStatusFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts index 5727cc0e9d5..5920233b206 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts @@ -5,6 +5,7 @@ import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angul @Component({ selector: "app-type-filter", templateUrl: "type-filter.component.html", + standalone: false, }) export class TypeFilterComponent extends BaseTypeFilterComponent { constructor() { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts index 12ac1fef425..161d22687e8 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts @@ -5,5 +5,6 @@ import { VaultFilterComponent as BaseVaultFilterComponent } from "@bitwarden/ang @Component({ selector: "app-vault-filter", templateUrl: "vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent extends BaseVaultFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-items.component.ts b/apps/desktop/src/vault/app/vault/vault-items.component.ts index d5838459ff7..2d1ba784753 100644 --- a/apps/desktop/src/vault/app/vault/vault-items.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items.component.ts @@ -14,6 +14,7 @@ import { SearchBarService } from "../../../app/layout/search/search-bar.service" @Component({ selector: "app-vault-items", templateUrl: "vault-items.component.html", + standalone: false, }) export class VaultItemsComponent extends BaseVaultItemsComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 66e77580d1c..b45d943dcdd 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -8,9 +8,8 @@ import { ViewChild, ViewContainerRef, } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs"; +import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs"; import { filter, map, take } from "rxjs/operators"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; @@ -31,13 +30,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { BadgeModule, ButtonModule, @@ -47,6 +46,8 @@ import { } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; import { + AddEditFolderDialogComponent, + AddEditFolderDialogResult, AttachmentDialogResult, AttachmentsV2Component, ChangeLoginPasswordService, @@ -68,7 +69,6 @@ import { DesktopCredentialGenerationService } from "../../../services/desktop-ci import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service"; import { invokeMenu, RendererMenuItem } from "../../../utils"; -import { FolderAddEditComponent } from "./folder-add-edit.component"; import { ItemFooterComponent } from "./item-footer.component"; import { VaultFilterComponent } from "./vault-filter/vault-filter.component"; import { VaultFilterModule } from "./vault-filter/vault-filter.module"; @@ -177,6 +177,7 @@ export class VaultV2Component implements OnInit, OnDestroy { private formConfigService: CipherFormConfigService, private premiumUpgradePromptService: PremiumUpgradePromptService, private collectionService: CollectionService, + private folderService: FolderService, ) {} async ngOnInit() { @@ -538,6 +539,7 @@ export class VaultV2Component implements OnInit, OnDestroy { } this.addType = type || this.activeFilter.cipherType; this.cipher = new CipherView(); + this.cipherId = null; await this.buildFormConfig("add"); this.action = "add"; this.prefillCipherFromFilter(); @@ -633,38 +635,25 @@ export class VaultV2Component implements OnInit, OnDestroy { } async editFolder(folderId: string) { - if (this.modal != null) { - this.modal.close(); - } - if (this.folderAddEditModalRef == null) { - return; - } - const [modal, childComponent] = await this.modalService - .openViewRef( - FolderAddEditComponent, - this.folderAddEditModalRef, - (comp) => (comp.folderId = folderId), - ) - .catch(() => [null, null] as any); - this.modal = modal; - if (childComponent) { - childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { - this.modal?.close(); - await this.vaultFilterComponent - ?.reloadCollectionsAndFolders(this.activeFilter) - .catch(() => {}); - }); - childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => { - this.modal?.close(); - await this.vaultFilterComponent - ?.reloadCollectionsAndFolders(this.activeFilter) - .catch(() => {}); - }); - } - if (this.modal) { - this.modal.onClosed.pipe(takeUntilDestroyed()).subscribe(() => { - this.modal = null; - }); + const folderView = await firstValueFrom( + this.folderService.getDecrypted$(folderId, this.activeUserId), + ); + + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { + editFolderConfig: { + folder: { + ...folderView, + }, + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if ( + result === AddEditFolderDialogResult.Deleted || + result === AddEditFolderDialogResult.Created + ) { + await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); } } diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index 6c0d5ef81d0..6c9a3217bfc 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -10,7 +10,7 @@ import { ViewContainerRef, } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs"; +import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs"; import { filter, first, map, take } from "rxjs/operators"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -23,20 +23,24 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { DialogService, ToastService } from "@bitwarden/components"; -import { DecryptionFailureDialogComponent, PasswordRepromptService } from "@bitwarden/vault"; +import { + AddEditFolderDialogComponent, + AddEditFolderDialogResult, + DecryptionFailureDialogComponent, + PasswordRepromptService, +} from "@bitwarden/vault"; import { SearchBarService } from "../../../app/layout/search/search-bar.service"; import { invokeMenu, RendererMenuItem } from "../../../utils"; @@ -45,7 +49,6 @@ import { AddEditComponent } from "./add-edit.component"; import { AttachmentsComponent } from "./attachments.component"; import { CollectionsComponent } from "./collections.component"; import { CredentialGeneratorDialogComponent } from "./credential-generator-dialog.component"; -import { FolderAddEditComponent } from "./folder-add-edit.component"; import { PasswordHistoryComponent } from "./password-history.component"; import { ShareComponent } from "./share.component"; import { VaultFilterComponent } from "./vault-filter/vault-filter.component"; @@ -57,6 +60,7 @@ const BroadcasterSubscriptionId = "VaultComponent"; @Component({ selector: "app-vault", templateUrl: "vault.component.html", + standalone: false, }) export class VaultComponent implements OnInit, OnDestroy { @ViewChild(ViewComponent) viewComponent: ViewComponent; @@ -72,8 +76,6 @@ export class VaultComponent implements OnInit, OnDestroy { @ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef; @ViewChild("collections", { read: ViewContainerRef, static: true }) collectionsModalRef: ViewContainerRef; - @ViewChild("folderAddEdit", { read: ViewContainerRef, static: true }) - folderAddEditModalRef: ViewContainerRef; action: string; cipherId: string = null; @@ -115,9 +117,9 @@ export class VaultComponent implements OnInit, OnDestroy { private dialogService: DialogService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, - private configService: ConfigService, private accountService: AccountService, private cipherService: CipherService, + private folderService: FolderService, ) {} async ngOnInit() { @@ -705,32 +707,26 @@ export class VaultComponent implements OnInit, OnDestroy { } async editFolder(folderId: string) { - if (this.modal != null) { - this.modal.close(); - } - - const [modal, childComponent] = await this.modalService.openViewRef( - FolderAddEditComponent, - this.folderAddEditModalRef, - (comp) => (comp.folderId = folderId), + const folderView = await firstValueFrom( + this.folderService.getDecrypted$(folderId, this.activeUserId), ); - this.modal = modal; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { - this.modal.close(); - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => { - this.modal.close(); - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { + editFolderConfig: { + folder: { + ...folderView, + }, + }, }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.modal.onClosed.subscribe(() => { - this.modal = null; - }); + const result = await lastValueFrom(dialogRef.closed); + + if ( + result === AddEditFolderDialogResult.Deleted || + result === AddEditFolderDialogResult.Created + ) { + await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); + } } private dirtyInput(): boolean { diff --git a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts index 249f83c4444..efe61ad1fa7 100644 --- a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts +++ b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts @@ -6,6 +6,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve @Component({ selector: "app-vault-view-custom-fields", templateUrl: "view-custom-fields.component.html", + standalone: false, }) export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { constructor(eventCollectionService: EventCollectionService) { diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index e5f677cbca6..084a9a747ed 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -42,6 +42,7 @@ const BroadcasterSubscriptionId = "ViewComponent"; @Component({ selector: "app-vault-view", templateUrl: "view.component.html", + standalone: false, }) export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy, OnChanges { @Output() onViewCipherPasswordHistory = new EventEmitter(); @@ -72,7 +73,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro accountService: AccountService, toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, - private configService: ConfigService, + configService: ConfigService, ) { super( cipherService, @@ -100,6 +101,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro billingAccountProfileStateService, toastService, cipherAuthorizationService, + configService, ); } diff --git a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts index 8e5f261bc26..8f703acf9af 100644 --- a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts @@ -10,6 +10,7 @@ import { GroupView } from "../../core"; @Component({ selector: "app-group-badge", templateUrl: "group-name-badge.component.html", + standalone: false, }) export class GroupNameBadgeComponent implements OnChanges { @Input() selectedGroups: SelectionReadOnlyRequest[]; diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts index 13e90d5275c..f7d7acfdc2d 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts @@ -26,6 +26,7 @@ import { CollectionFilter } from "../../../../vault/individual-vault/vault-filte selector: "app-organization-vault-filter", templateUrl: "../../../../vault/individual-vault/vault-filter/components/vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent extends BaseVaultFilterComponent diff --git a/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts b/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts index fc168f842dc..cd14b73a156 100644 --- a/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts +++ b/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts @@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv @Component({ selector: "app-org-info", templateUrl: "organization-information.component.html", + standalone: false, }) export class OrganizationInformationComponent implements OnInit { @Input() nameOnly = false; diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts index f5fce0e5e42..bc4a942301a 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts @@ -21,16 +21,19 @@ import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This component can only be accessed by a enterprise organization!

", + standalone: false, }) export class IsEnterpriseOrganizationComponent {} @Component({ template: "

This is the organization upgrade screen!

", + standalone: false, }) export class OrganizationUpgradeScreenComponent {} diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts index 8efed8cefa2..ab5fd79321a 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts @@ -20,16 +20,19 @@ import { isPaidOrgGuard } from "./is-paid-org.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This component can only be accessed by a paid organization!

", + standalone: false, }) export class PaidOrganizationOnlyComponent {} @Component({ template: "

This is the organization upgrade screen!

", + standalone: false, }) export class OrganizationUpgradeScreenComponent {} diff --git a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts index fa348867a86..9dc084484f3 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts @@ -19,16 +19,19 @@ import { organizationRedirectGuard } from "./org-redirect.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This is the admin console!

", + standalone: false, }) export class AdminConsoleComponent {} @Component({ template: "

This is a subroute of the admin console!

", + standalone: false, }) export class AdminConsoleSubrouteComponent {} diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index 4e7c2403893..24be9a16090 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -6,6 +6,7 @@ icon="bwi-filter" *ngIf="organization.useRiskInsights" [text]="'accessIntelligence' | i18n" + route="access-intelligence" > = { @Component({ selector: "app-org-events", templateUrl: "events.component.html", + standalone: false, }) export class EventsComponent extends BaseEventsComponent implements OnInit, OnDestroy { exportFileName = "org-events"; diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index f29b4b642cb..53a6a3cf196 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -110,6 +110,7 @@ export const openGroupAddEditDialog = ( @Component({ selector: "app-group-add-edit", templateUrl: "group-add-edit.component.html", + standalone: false, }) export class GroupAddEditComponent implements OnInit, OnDestroy { private organization$ = this.accountService.activeAccount$.pipe( diff --git a/apps/web/src/app/admin-console/organizations/manage/groups.component.html b/apps/web/src/app/admin-console/organizations/manage/groups.component.html index caae23f500d..4518513ba7d 100644 --- a/apps/web/src/app/admin-console/organizations/manage/groups.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/groups.component.html @@ -22,7 +22,7 @@

{{ "noGroupsInList" | i18n }}

- + diff --git a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts index 669dcc251ca..6459cd1f857 100644 --- a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts @@ -75,6 +75,7 @@ const groupsFilter = (filter: string) => { @Component({ templateUrl: "groups.component.html", + standalone: false, }) export class GroupsComponent { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts index b5068ba55a6..03b77cfaa71 100644 --- a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts @@ -18,6 +18,7 @@ export type UserConfirmDialogData = { @Component({ selector: "app-user-confirm", templateUrl: "user-confirm.component.html", + standalone: false, }) export class UserConfirmComponent implements OnInit { name: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts index c19984f980d..4ec50799ae0 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts @@ -33,6 +33,7 @@ type BulkConfirmDialogParams = { @Component({ templateUrl: "bulk-confirm-dialog.component.html", + standalone: false, }) export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts index 51ba98fabb9..8fb60e85b08 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts @@ -18,6 +18,7 @@ type BulkDeleteDialogParams = { @Component({ templateUrl: "bulk-delete-dialog.component.html", + standalone: false, }) export class BulkDeleteDialogComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts index e01809789f3..9132625c587 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts @@ -22,6 +22,7 @@ export type BulkEnableSecretsManagerDialogData = { @Component({ templateUrl: `bulk-enable-sm-dialog.component.html`, + standalone: false, }) export class BulkEnableSecretsManagerDialogComponent implements OnInit { protected dataSource = new TableDataSource(); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts index 00711e355cb..5bbc6f093f0 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts @@ -21,6 +21,7 @@ type BulkRemoveDialogParams = { @Component({ templateUrl: "bulk-remove-dialog.component.html", + standalone: false, }) export class BulkRemoveDialogComponent extends BaseBulkRemoveComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts index 5cb2c2e3d4e..ac99a9b51de 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts @@ -18,6 +18,7 @@ type BulkRestoreDialogParams = { @Component({ selector: "app-bulk-restore-revoke", templateUrl: "bulk-restore-revoke.component.html", + standalone: false, }) export class BulkRestoreRevokeComponent { isRevoking: boolean; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts index b8a2f45053b..078ba6c1fd1 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts @@ -41,6 +41,7 @@ type BulkStatusDialogData = { @Component({ selector: "app-bulk-status", templateUrl: "bulk-status.component.html", + standalone: false, }) export class BulkStatusComponent implements OnInit { users: BulkStatusEntry[]; diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 8349b44c735..10bbc5cfe52 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -106,6 +106,7 @@ export enum MemberDialogResult { @Component({ templateUrl: "member-dialog.component.html", + standalone: false, }) export class MemberDialogComponent implements OnDestroy { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts index 648a5a6ff26..9a2025c2b30 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts @@ -10,6 +10,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-nested-checkbox", templateUrl: "nested-checkbox.component.html", + standalone: false, }) export class NestedCheckboxComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts index 4e78d4dc91f..80f0745f6d5 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts @@ -59,6 +59,7 @@ export enum ResetPasswordDialogResult { @Component({ selector: "app-reset-password", templateUrl: "reset-password.component.html", + standalone: false, }) /** * Used in a dialog for initiating the account recovery process against a diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html index 2162e33081f..690e7aaa855 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.html +++ b/apps/web/src/app/admin-console/organizations/members/members.component.html @@ -67,7 +67,7 @@ - + diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 6a3ca58b73d..834aa2c7111 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -88,6 +88,7 @@ class MembersTableDataSource extends PeopleTableDataSource @Component({ templateUrl: "members.component.html", + standalone: false, }) export class MembersComponent extends BaseMembersComponent { userType = OrganizationUserType; diff --git a/apps/web/src/app/admin-console/organizations/members/members.module.ts b/apps/web/src/app/admin-console/organizations/members/members.module.ts index 81697f8c845..98431758d2f 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.module.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.module.ts @@ -3,6 +3,7 @@ import { NgModule } from "@angular/core"; import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component"; import { PasswordCalloutComponent } from "@bitwarden/auth/angular"; +import { ScrollLayoutDirective } from "@bitwarden/components"; import { LooseComponentsModule } from "../../../shared"; import { SharedOrganizationModule } from "../shared"; @@ -27,6 +28,7 @@ import { MembersComponent } from "./members.component"; PasswordCalloutComponent, ScrollingModule, PasswordStrengthV2Component, + ScrollLayoutDirective, ], declarations: [ BulkConfirmDialogComponent, diff --git a/apps/web/src/app/admin-console/organizations/organization.module.ts b/apps/web/src/app/admin-console/organizations/organization.module.ts index 459948d0f13..687361760c9 100644 --- a/apps/web/src/app/admin-console/organizations/organization.module.ts +++ b/apps/web/src/app/admin-console/organizations/organization.module.ts @@ -1,6 +1,8 @@ import { ScrollingModule } from "@angular/cdk/scrolling"; import { NgModule } from "@angular/core"; +import { ScrollLayoutDirective } from "@bitwarden/components"; + import { LooseComponentsModule } from "../../shared"; import { CoreOrganizationModule } from "./core"; @@ -18,6 +20,7 @@ import { AccessSelectorModule } from "./shared/components/access-selector"; OrganizationsRoutingModule, LooseComponentsModule, ScrollingModule, + ScrollLayoutDirective, ], declarations: [GroupsComponent, GroupAddEditComponent], }) diff --git a/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts b/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts index 2acf175e3da..b323ac00d34 100644 --- a/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts @@ -14,5 +14,6 @@ export class DisableSendPolicy extends BasePolicy { @Component({ selector: "policy-disable-send", templateUrl: "disable-send.component.html", + standalone: false, }) export class DisableSendPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts index 328989df66b..54cf1be88fc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts @@ -28,6 +28,7 @@ export class MasterPasswordPolicy extends BasePolicy { @Component({ selector: "policy-master-password", templateUrl: "master-password.component.html", + standalone: false, }) export class MasterPasswordPolicyComponent extends BasePolicyComponent implements OnInit { MinPasswordLength = Utils.minimumPasswordLength; diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts index 4439f974e55..f11b14aea38 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts @@ -21,6 +21,7 @@ export class PasswordGeneratorPolicy extends BasePolicy { @Component({ selector: "policy-password-generator", templateUrl: "password-generator.component.html", + standalone: false, }) export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { // these properties forward the application default settings to the UI diff --git a/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts index 36c79a61fe4..ef92ee90581 100644 --- a/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts @@ -14,5 +14,6 @@ export class PersonalOwnershipPolicy extends BasePolicy { @Component({ selector: "policy-personal-ownership", templateUrl: "personal-ownership.component.html", + standalone: false, }) export class PersonalOwnershipPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 6e3b34eaa30..73f0d99b4f9 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -31,6 +31,7 @@ import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.compo @Component({ selector: "app-org-policies", templateUrl: "policies.component.html", + standalone: false, }) export class PoliciesComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts index 4d722840e23..d3d03d2aaae 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts @@ -50,6 +50,7 @@ export enum PolicyEditDialogResult { @Component({ selector: "app-policy-edit", templateUrl: "policy-edit.component.html", + standalone: false, }) export class PolicyEditComponent implements AfterViewInit { @ViewChild("policyForm", { read: ViewContainerRef, static: true }) diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts index b737c803b5f..0d0f42b603e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts @@ -14,5 +14,6 @@ export class RemoveUnlockWithPinPolicy extends BasePolicy { @Component({ selector: "remove-unlock-with-pin", templateUrl: "remove-unlock-with-pin.component.html", + standalone: false, }) export class RemoveUnlockWithPinPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts index ea85168f986..21de143dea6 100644 --- a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts @@ -19,5 +19,6 @@ export class RequireSsoPolicy extends BasePolicy { @Component({ selector: "policy-require-sso", templateUrl: "require-sso.component.html", + standalone: false, }) export class RequireSsoPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts index 80e4e5254ff..62fc42f6a06 100644 --- a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts @@ -27,6 +27,7 @@ export class ResetPasswordPolicy extends BasePolicy { @Component({ selector: "policy-reset-password", templateUrl: "reset-password.component.html", + standalone: false, }) export class ResetPasswordPolicyComponent extends BasePolicyComponent implements OnInit { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts b/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts index af6d1f38694..9a0a8871296 100644 --- a/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts @@ -15,6 +15,7 @@ export class SendOptionsPolicy extends BasePolicy { @Component({ selector: "policy-send-options", templateUrl: "send-options.component.html", + standalone: false, }) export class SendOptionsPolicyComponent extends BasePolicyComponent { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts index a40ec87bf6a..ad32b4218bc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts @@ -14,6 +14,7 @@ export class SingleOrgPolicy extends BasePolicy { @Component({ selector: "policy-single-org", templateUrl: "single-org.component.html", + standalone: false, }) export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit { async ngOnInit() { diff --git a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts index 51151808872..691e12c72f6 100644 --- a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts @@ -14,5 +14,6 @@ export class TwoFactorAuthenticationPolicy extends BasePolicy { @Component({ selector: "policy-two-factor-authentication", templateUrl: "two-factor-authentication.component.html", + standalone: false, }) export class TwoFactorAuthenticationPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts index 9fd3170b73a..52cb24c90d1 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts @@ -17,6 +17,7 @@ import { ReportVariant, reports, ReportType, ReportEntry } from "../../../dirt/r @Component({ selector: "app-org-reports-home", templateUrl: "reports-home.component.html", + standalone: false, }) export class ReportsHomeComponent implements OnInit { reports$: Observable; diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index f3997fe669e..b376c48b39a 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -41,6 +41,7 @@ import { DeleteOrganizationDialogResult, openDeleteOrganizationDialog } from "./ @Component({ selector: "app-org-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnInit, OnDestroy { selfHosted = false; diff --git a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts index 014b8e3a3ef..020a16dd932 100644 --- a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts @@ -29,6 +29,7 @@ import { TwoFactorVerifyComponent } from "../../../auth/settings/two-factor/two- @Component({ selector: "app-two-factor-setup", templateUrl: "../../../auth/settings/two-factor/two-factor-setup.component.html", + standalone: false, }) export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent implements OnInit { tabbedHeader = false; @@ -118,7 +119,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme return this.apiService.getTwoFactorOrganizationProviders(this.organizationId); } - protected filterProvider(type: TwoFactorProviderType) { + protected filterProvider(type: TwoFactorProviderType): boolean { return type !== TwoFactorProviderType.OrganizationDuo; } } diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts index 1db1fc8a06e..366df34b2b8 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts @@ -55,6 +55,7 @@ export enum PermissionMode { multi: true, }, ], + standalone: false, }) export class AccessSelectorComponent implements ControlValueAccessor, OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts index 3d43c63efb0..673d09ec0f0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts @@ -5,6 +5,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Pipe({ name: "userType", + standalone: false, }) export class UserTypePipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index 07bff3aba64..9806f99d90e 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -613,5 +613,5 @@ export function openCollectionDialog( dialogService: DialogService, config: DialogConfig>, ) { - return dialogService.open(CollectionDialogComponent, config); + return dialogService.open(CollectionDialogComponent, config); } diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts index 7ceaed28f80..4df6defe8ad 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts @@ -19,6 +19,7 @@ import { BaseAcceptComponent } from "../../../common/base.accept.component"; @Component({ selector: "app-accept-family-sponsorship", templateUrl: "accept-family-sponsorship.component.html", + standalone: false, }) export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 55e2595e0f7..cac0487d05d 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -3,25 +3,20 @@ import { DOCUMENT } from "@angular/common"; import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NavigationEnd, Router } from "@angular/router"; -import * as jq from "jquery"; +import { Router } from "@angular/router"; import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; -import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -29,11 +24,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; -import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KeyService, BiometricStateService } from "@bitwarden/key-management"; import { PolicyListService } from "./admin-console/core/policy-list.service"; @@ -56,6 +49,7 @@ const IdleTimeout = 60000 * 10; // 10 minutes @Component({ selector: "app-root", templateUrl: "app.component.html", + standalone: false, }) export class AppComponent implements OnDestroy, OnInit { private lastActivity: Date = null; @@ -69,8 +63,6 @@ export class AppComponent implements OnDestroy, OnInit { @Inject(DOCUMENT) private document: Document, private broadcasterService: BroadcasterService, private folderService: InternalFolderService, - private syncService: SyncService, - private passwordGenerationService: PasswordGenerationServiceAbstraction, private cipherService: CipherService, private authService: AuthService, private router: Router, @@ -85,17 +77,13 @@ export class AppComponent implements OnDestroy, OnInit { private notificationsService: NotificationsService, private stateService: StateService, private eventUploadService: EventUploadService, - private policyService: InternalPolicyService, protected policyListService: PolicyListService, - private keyConnectorService: KeyConnectorService, protected configService: ConfigService, private dialogService: DialogService, private biometricStateService: BiometricStateService, private stateEventRunnerService: StateEventRunnerService, private organizationService: InternalOrganizationServiceAbstraction, private accountService: AccountService, - private apiService: ApiService, - private appIdService: AppIdService, private processReloadService: ProcessReloadServiceAbstraction, private deviceTrustToastService: DeviceTrustToastService, ) { @@ -247,15 +235,6 @@ export class AppComponent implements OnDestroy, OnInit { }); }); - this.router.events.pipe(takeUntil(this.destroy$)).subscribe((event) => { - if (event instanceof NavigationEnd) { - const modals = Array.from(document.querySelectorAll(".modal")); - for (const modal of modals) { - (jq(modal) as any).modal("hide"); - } - } - }); - this.policyListService.addPolicies([ new TwoFactorAuthenticationPolicy(), new MasterPasswordPolicy(), diff --git a/apps/web/src/app/auth/core/services/change-password/index.ts b/apps/web/src/app/auth/core/services/change-password/index.ts new file mode 100644 index 00000000000..9b2aa1c0143 --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/index.ts @@ -0,0 +1 @@ +export * from "./web-change-password.service"; diff --git a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts new file mode 100644 index 00000000000..45abfe4720a --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts @@ -0,0 +1,63 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ChangePasswordService } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; +import { KeyService } from "@bitwarden/key-management"; +import { UserKeyRotationService } from "@bitwarden/web-vault/app/key-management/key-rotation/user-key-rotation.service"; + +import { WebChangePasswordService } from "./web-change-password.service"; + +describe("WebChangePasswordService", () => { + let keyService: MockProxy; + let masterPasswordApiService: MockProxy; + let masterPasswordService: MockProxy; + let userKeyRotationService: MockProxy; + + let sut: ChangePasswordService; + + const userId = "userId" as UserId; + const user: Account = { + id: userId, + email: "email", + emailVerified: false, + name: "name", + }; + + const currentPassword = "currentPassword"; + const newPassword = "newPassword"; + const newPasswordHint = "newPasswordHint"; + + beforeEach(() => { + keyService = mock(); + masterPasswordApiService = mock(); + masterPasswordService = mock(); + userKeyRotationService = mock(); + + sut = new WebChangePasswordService( + keyService, + masterPasswordApiService, + masterPasswordService, + userKeyRotationService, + ); + }); + + describe("rotateUserKeyMasterPasswordAndEncryptedData()", () => { + it("should call the method with the same name on the UserKeyRotationService with the correct arguments", async () => { + // Arrange & Act + await sut.rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword, + newPassword, + user, + newPasswordHint, + ); + + // Assert + expect( + userKeyRotationService.rotateUserKeyMasterPasswordAndEncryptedData, + ).toHaveBeenCalledWith(currentPassword, newPassword, user, newPasswordHint); + }); + }); +}); diff --git a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts new file mode 100644 index 00000000000..b75aef0f1fc --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts @@ -0,0 +1,34 @@ +import { ChangePasswordService, DefaultChangePasswordService } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { KeyService } from "@bitwarden/key-management"; +import { UserKeyRotationService } from "@bitwarden/web-vault/app/key-management/key-rotation/user-key-rotation.service"; + +export class WebChangePasswordService + extends DefaultChangePasswordService + implements ChangePasswordService +{ + constructor( + protected keyService: KeyService, + protected masterPasswordApiService: MasterPasswordApiService, + protected masterPasswordService: InternalMasterPasswordServiceAbstraction, + private userKeyRotationService: UserKeyRotationService, + ) { + super(keyService, masterPasswordApiService, masterPasswordService); + } + + override async rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword: string, + newPassword: string, + user: Account, + newPasswordHint: string, + ): Promise { + await this.userKeyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword, + newPassword, + user, + newPasswordHint, + ); + } +} diff --git a/apps/web/src/app/auth/core/services/index.ts b/apps/web/src/app/auth/core/services/index.ts index 11c8dd98872..5539e3b76ea 100644 --- a/apps/web/src/app/auth/core/services/index.ts +++ b/apps/web/src/app/auth/core/services/index.ts @@ -1,3 +1,4 @@ +export * from "./change-password"; export * from "./login"; export * from "./login-decryption-options"; export * from "./webauthn-login"; diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts index edce551342e..fe3b0837aa8 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts @@ -185,11 +185,11 @@ describe("WebRegistrationFinishService", () => { emailVerificationToken = "emailVerificationToken"; masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey; passwordInputResult = { - masterKey: masterKey, - serverMasterKeyHash: "serverMasterKeyHash", - localMasterKeyHash: "localMasterKeyHash", + newMasterKey: masterKey, + newServerMasterKeyHash: "newServerMasterKeyHash", + newLocalMasterKeyHash: "newLocalMasterKeyHash", kdfConfig: DEFAULT_KDF_CONFIG, - hint: "hint", + newPasswordHint: "newPasswordHint", newPassword: "newPassword", }; @@ -231,8 +231,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: emailVerificationToken, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -267,8 +267,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -308,8 +308,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -351,8 +351,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -396,8 +396,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], diff --git a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts b/apps/web/src/app/auth/guards/deep-link.guard.spec.ts index f9ced556e47..82ed004cf54 100644 --- a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts +++ b/apps/web/src/app/auth/guards/deep-link.guard.spec.ts @@ -13,16 +13,19 @@ import { deepLinkGuard } from "./deep-link.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class LockTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts index 8f3a5ca3c37..d4a381159ab 100644 --- a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts +++ b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts @@ -7,6 +7,7 @@ import { CreatePasskeyIcon } from "@bitwarden/angular/auth/icons/create-passkey. @Component({ selector: "app-login-via-webauthn", templateUrl: "login-via-webauthn.component.html", + standalone: false, }) export class LoginViaWebAuthnComponent extends BaseLoginViaWebAuthnComponent { protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon }; diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts index 197b4031998..838a3029711 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -14,6 +14,7 @@ import { OrganizationInvite } from "./organization-invite"; @Component({ templateUrl: "accept-organization.component.html", + standalone: false, }) export class AcceptOrganizationComponent extends BaseAcceptComponent { orgName$ = this.acceptOrganizationInviteService.orgName$; diff --git a/apps/web/src/app/auth/recover-delete.component.ts b/apps/web/src/app/auth/recover-delete.component.ts index 6b45421911d..7381d526879 100644 --- a/apps/web/src/app/auth/recover-delete.component.ts +++ b/apps/web/src/app/auth/recover-delete.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-recover-delete", templateUrl: "recover-delete.component.html", + standalone: false, }) export class RecoverDeleteComponent { protected recoverDeleteForm = new FormGroup({ diff --git a/apps/web/src/app/auth/recover-two-factor.component.ts b/apps/web/src/app/auth/recover-two-factor.component.ts index 106cd32a945..a69da0a66bf 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -16,6 +16,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-recover-two-factor", templateUrl: "recover-two-factor.component.html", + standalone: false, }) export class RecoverTwoFactorComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/apps/web/src/app/auth/set-password.component.ts b/apps/web/src/app/auth/set-password.component.ts index ccd329dd640..e297426f2c1 100644 --- a/apps/web/src/app/auth/set-password.component.ts +++ b/apps/web/src/app/auth/set-password.component.ts @@ -11,6 +11,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent { routerService = inject(RouterService); diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index d7ba8058b81..cfc01f17674 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -17,6 +17,7 @@ import { SetAccountVerifyDevicesDialogComponent } from "./set-account-verify-dev @Component({ selector: "app-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index ba2a34c7143..5d71333c0de 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -31,6 +31,7 @@ type ChangeAvatarDialogData = { @Component({ templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, + standalone: false, }) export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { profile: ProfileResponse; diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index caf7e0933b0..c86c8c2f4f7 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -17,6 +17,7 @@ import { KdfConfigService, KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-change-email", templateUrl: "change-email.component.html", + standalone: false, }) export class ChangeEmailComponent implements OnInit { tokenSent = false; diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts index a7c466d4ffc..da4d2dce9d7 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts @@ -12,6 +12,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-deauthorize-sessions", templateUrl: "deauthorize-sessions.component.html", + standalone: false, }) export class DeauthorizeSessionsComponent { deauthForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts index 07e6052e0a1..8a3575af5ba 100644 --- a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts @@ -11,6 +11,7 @@ import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; @Component({ templateUrl: "delete-account-dialog.component.html", + standalone: false, }) export class DeleteAccountDialogComponent { deleteForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index a87e571643d..dc3997f58bb 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -19,6 +19,7 @@ import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", + standalone: false, }) export class ProfileComponent implements OnInit, OnDestroy { loading = true; diff --git a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts index 7a746481563..33c307882c5 100644 --- a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts @@ -24,6 +24,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; > `, + standalone: false, }) export class SelectableAvatarComponent { @Input() id: string; diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index ffa5247ad08..1d95a498694 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -29,9 +29,13 @@ import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service"; +/** + * @deprecated use the auth `PasswordSettingsComponent` instead + */ @Component({ selector: "app-change-password", templateUrl: "change-password.component.html", + standalone: false, }) export class ChangePasswordComponent extends BaseChangePasswordComponent @@ -131,7 +135,7 @@ export class ChangePasswordComponent content: this.i18nService.t("updateEncryptionKeyWarning") + " " + - this.i18nService.t("updateEncryptionKeyExportWarning") + + this.i18nService.t("updateEncryptionKeyAccountExportWarning") + " " + this.i18nService.t("rotateEncKeyConfirmation"), type: "warning", diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts index 95afc167374..cd7a585f3b1 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts @@ -26,6 +26,7 @@ type EmergencyAccessConfirmDialogData = { @Component({ selector: "emergency-access-confirm", templateUrl: "emergency-access-confirm.component.html", + standalone: false, }) export class EmergencyAccessConfirmComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts index 1a6510ef011..2f3f3a20b04 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts @@ -36,6 +36,7 @@ export enum EmergencyAccessAddEditDialogResult { @Component({ selector: "emergency-access-add-edit", templateUrl: "emergency-access-add-edit.component.html", + standalone: false, }) export class EmergencyAccessAddEditComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index f55d731d7f2..23bf0c22bc7 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -42,6 +42,7 @@ import { @Component({ selector: "emergency-access", templateUrl: "emergency-access.component.html", + standalone: false, }) export class EmergencyAccessComponent implements OnInit { loaded = false; diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts index edb85dc0f1a..d683545db59 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts @@ -40,6 +40,7 @@ type EmergencyAccessTakeoverDialogData = { @Component({ selector: "emergency-access-takeover", templateUrl: "emergency-access-takeover.component.html", + standalone: false, }) export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts index 55ebf860cff..607e6e6a2c7 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts @@ -15,6 +15,7 @@ import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component" selector: "emergency-access-view", templateUrl: "emergency-access-view.component.html", providers: [{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }], + standalone: false, }) export class EmergencyAccessViewComponent implements OnInit { id: EmergencyAccessId | null = null; diff --git a/apps/web/src/app/auth/settings/security/api-key.component.ts b/apps/web/src/app/auth/settings/security/api-key.component.ts index 26a70476102..4f87c082881 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.ts +++ b/apps/web/src/app/auth/settings/security/api-key.component.ts @@ -23,6 +23,7 @@ export type ApiKeyDialogData = { @Component({ selector: "app-api-key", templateUrl: "api-key.component.html", + standalone: false, }) export class ApiKeyComponent { clientId: string; diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts index debe390d878..0bfc46eea96 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts @@ -16,6 +16,7 @@ import { KdfConfig, KdfType, KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-change-kdf-confirmation", templateUrl: "change-kdf-confirmation.component.html", + standalone: false, }) export class ChangeKdfConfirmationComponent { kdfConfig: KdfConfig; diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts index cbbef0e016b..a059ede77b4 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts @@ -21,6 +21,7 @@ import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.compon @Component({ selector: "app-change-kdf", templateUrl: "change-kdf.component.html", + standalone: false, }) export class ChangeKdfComponent implements OnInit, OnDestroy { kdfConfig: KdfConfig = DEFAULT_KDF_CONFIG; diff --git a/apps/web/src/app/auth/settings/security/device-management.component.spec.ts b/apps/web/src/app/auth/settings/security/device-management.component.spec.ts index 84c1dfcb63b..d86123f52be 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.spec.ts +++ b/apps/web/src/app/auth/settings/security/device-management.component.spec.ts @@ -9,7 +9,13 @@ import { DeviceType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { MessageListener } from "@bitwarden/common/platform/messaging"; -import { DialogService, ToastService, TableModule, PopoverModule } from "@bitwarden/components"; +import { + DialogService, + ToastService, + TableModule, + PopoverModule, + LayoutComponent, +} from "@bitwarden/components"; import { SharedModule } from "../../../shared"; import { VaultBannersService } from "../../../vault/individual-vault/vault-banners/services/vault-banners.service"; @@ -115,6 +121,12 @@ describe("DeviceManagementComponent", () => { showError: jest.fn(), }, }, + { + provide: LayoutComponent, + useValue: { + mainContent: jest.fn(), + }, + }, ], }).compileComponents(); diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html new file mode 100644 index 00000000000..94cf08b5871 --- /dev/null +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html @@ -0,0 +1,10 @@ +
+

{{ "changeMasterPassword" | i18n }}

+
+ +
+ {{ "loggedOutWarning" | i18n }} + +
+ + diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts new file mode 100644 index 00000000000..ee30543fba2 --- /dev/null +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { ChangePasswordComponent, InputPasswordFlow } from "@bitwarden/auth/angular"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { CalloutModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { WebauthnLoginSettingsModule } from "../../webauthn-login-settings"; + +@Component({ + standalone: true, + selector: "app-password-settings", + templateUrl: "password-settings.component.html", + imports: [CalloutModule, ChangePasswordComponent, I18nPipe, WebauthnLoginSettingsModule], +}) +export class PasswordSettingsComponent implements OnInit { + inputPasswordFlow = InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation; + + constructor( + private router: Router, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + ) {} + + async ngOnInit() { + const userHasMasterPassword = await firstValueFrom( + this.userDecryptionOptionsService.hasMasterPassword$, + ); + if (!userHasMasterPassword) { + await this.router.navigate(["/settings/security/two-factor"]); + return; + } + } +} diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index 70e33b242b3..98e743f57dc 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -13,6 +13,7 @@ import { ApiKeyComponent } from "./api-key.component"; @Component({ selector: "app-security-keys", templateUrl: "security-keys.component.html", + standalone: false, }) export class SecurityKeysComponent implements OnInit { showChangeKdf = true; diff --git a/apps/web/src/app/auth/settings/security/security-routing.module.ts b/apps/web/src/app/auth/settings/security/security-routing.module.ts index 6ed21605184..14d4aab8a36 100644 --- a/apps/web/src/app/auth/settings/security/security-routing.module.ts +++ b/apps/web/src/app/auth/settings/security/security-routing.module.ts @@ -1,10 +1,14 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; +import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + import { ChangePasswordComponent } from "../change-password.component"; import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component"; import { DeviceManagementComponent } from "./device-management.component"; +import { PasswordSettingsComponent } from "./password-settings/password-settings.component"; import { SecurityKeysComponent } from "./security-keys.component"; import { SecurityComponent } from "./security.component"; @@ -14,10 +18,31 @@ const routes: Routes = [ component: SecurityComponent, data: { titleId: "security" }, children: [ - { path: "", pathMatch: "full", redirectTo: "change-password" }, + { path: "", pathMatch: "full", redirectTo: "password" }, { path: "change-password", component: ChangePasswordComponent, + canActivate: [ + canAccessFeature( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + false, + "/settings/security/password", + false, + ), + ], + data: { titleId: "masterPassword" }, + }, + { + path: "password", + component: PasswordSettingsComponent, + canActivate: [ + canAccessFeature( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + true, + "/settings/security/change-password", + false, + ), + ], data: { titleId: "masterPassword" }, }, { diff --git a/apps/web/src/app/auth/settings/security/security.component.html b/apps/web/src/app/auth/settings/security/security.component.html index 6bd7c1daf36..355a33d4427 100644 --- a/apps/web/src/app/auth/settings/security/security.component.html +++ b/apps/web/src/app/auth/settings/security/security.component.html @@ -1,7 +1,7 @@ - {{ "masterPassword" | i18n }} + {{ "masterPassword" | i18n }} {{ "twoStepLogin" | i18n }} {{ "devices" | i18n }} diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index d643b565df2..41b1af17abb 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -1,14 +1,17 @@ import { Component, OnInit } from "@angular/core"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @Component({ selector: "app-security", templateUrl: "security.component.html", + standalone: false, }) export class SecurityComponent implements OnInit { showChangePassword = true; + changePasswordRoute = "change-password"; constructor( private userVerificationService: UserVerificationService, @@ -17,5 +20,12 @@ export class SecurityComponent implements OnInit { async ngOnInit() { this.showChangePassword = await this.userVerificationService.hasMasterPassword(); + + const changePasswordRefreshFlag = await this.configService.getFeatureFlag( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + ); + if (changePasswordRefreshFlag) { + this.changePasswordRoute = "password"; + } } } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts index d240dc467ae..88c9eea2cb0 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -273,7 +273,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { return this.apiService.getTwoFactorProviders(); } - protected filterProvider(type: TwoFactorProviderType) { + protected filterProvider(type: TwoFactorProviderType): boolean { return type === TwoFactorProviderType.OrganizationDuo; } diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts index 8e7e25896ab..22c3b4376c5 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts @@ -33,6 +33,7 @@ type Step = @Component({ templateUrl: "create-credential-dialog.component.html", + standalone: false, }) export class CreateCredentialDialogComponent implements OnInit { protected readonly NameMaxCharacters = 50; diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts index 8f6bf1778e8..ea766a302ca 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts @@ -26,6 +26,7 @@ export interface DeleteCredentialDialogParams { @Component({ templateUrl: "delete-credential-dialog.component.html", + standalone: false, }) export class DeleteCredentialDialogComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts index b402e53abf2..dd1ac45a9b6 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts @@ -23,6 +23,7 @@ export interface EnableEncryptionDialogParams { @Component({ templateUrl: "enable-encryption-dialog.component.html", + standalone: false, }) export class EnableEncryptionDialogComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts index 13c7993768c..94e926ac138 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts @@ -23,6 +23,7 @@ import { openEnableCredentialDialogComponent } from "./enable-encryption-dialog/ host: { "aria-live": "polite", }, + standalone: false, }) export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts b/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts index 282bce4c95d..77df374f3ed 100644 --- a/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts +++ b/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts @@ -23,6 +23,7 @@ import { */ @Component({ templateUrl: "user-verification-prompt.component.html", + standalone: false, }) export class UserVerificationPromptComponent extends BaseUserVerificationPrompt { constructor( diff --git a/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts b/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts index 94d319524f7..42f4b26fb36 100644 --- a/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts +++ b/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts @@ -23,5 +23,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), ]), ], + standalone: false, }) export class UserVerificationComponent extends BaseComponent {} diff --git a/apps/web/src/app/auth/update-password.component.ts b/apps/web/src/app/auth/update-password.component.ts index da62a6812f1..c975f7c4168 100644 --- a/apps/web/src/app/auth/update-password.component.ts +++ b/apps/web/src/app/auth/update-password.component.ts @@ -9,6 +9,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or @Component({ selector: "app-update-password", templateUrl: "update-password.component.html", + standalone: false, }) export class UpdatePasswordComponent extends BaseUpdatePasswordComponent { private routerService = inject(RouterService); diff --git a/apps/web/src/app/auth/update-temp-password.component.ts b/apps/web/src/app/auth/update-temp-password.component.ts index f7d952b97f4..ead10660b92 100644 --- a/apps/web/src/app/auth/update-temp-password.component.ts +++ b/apps/web/src/app/auth/update-temp-password.component.ts @@ -5,5 +5,6 @@ import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from " @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {} diff --git a/apps/web/src/app/auth/verify-email-token.component.ts b/apps/web/src/app/auth/verify-email-token.component.ts index 55569f44c10..9e44cc7a713 100644 --- a/apps/web/src/app/auth/verify-email-token.component.ts +++ b/apps/web/src/app/auth/verify-email-token.component.ts @@ -15,6 +15,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-email-token", templateUrl: "verify-email-token.component.html", + standalone: false, }) export class VerifyEmailTokenComponent implements OnInit { constructor( diff --git a/apps/web/src/app/auth/verify-recover-delete.component.ts b/apps/web/src/app/auth/verify-recover-delete.component.ts index 8d95dd01b77..a475fdfd3e5 100644 --- a/apps/web/src/app/auth/verify-recover-delete.component.ts +++ b/apps/web/src/app/auth/verify-recover-delete.component.ts @@ -14,6 +14,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-recover-delete", templateUrl: "verify-recover-delete.component.html", + standalone: false, }) export class VerifyRecoverDeleteComponent implements OnInit { email: string; diff --git a/apps/web/src/app/billing/individual/billing-history-view.component.ts b/apps/web/src/app/billing/individual/billing-history-view.component.ts index 89e6b9ca9c3..d615e01d0db 100644 --- a/apps/web/src/app/billing/individual/billing-history-view.component.ts +++ b/apps/web/src/app/billing/individual/billing-history-view.component.ts @@ -12,6 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ templateUrl: "billing-history-view.component.html", + standalone: false, }) export class BillingHistoryViewComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/billing/individual/premium/premium.component.ts b/apps/web/src/app/billing/individual/premium/premium.component.ts index 2934e69f0ad..974c22455ff 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium.component.ts @@ -25,6 +25,7 @@ import { TaxInfoComponent } from "../../shared/tax-info.component"; @Component({ templateUrl: "./premium.component.html", + standalone: false, }) export class PremiumComponent { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/apps/web/src/app/billing/individual/subscription.component.ts b/apps/web/src/app/billing/individual/subscription.component.ts index edd16ca81fe..2a08ec85127 100644 --- a/apps/web/src/app/billing/individual/subscription.component.ts +++ b/apps/web/src/app/billing/individual/subscription.component.ts @@ -9,6 +9,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ templateUrl: "subscription.component.html", + standalone: false, }) export class SubscriptionComponent implements OnInit { hasPremium$: Observable; diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 38f4436fb47..4d1fa97785b 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -28,6 +28,7 @@ import { UpdateLicenseDialogResult } from "../shared/update-license-types"; @Component({ templateUrl: "user-subscription.component.html", + standalone: false, }) export class UserSubscriptionComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts b/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts index 7e6c0d464c3..d0eb065b74b 100644 --- a/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts +++ b/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts @@ -1,4 +1,3 @@ -import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { AbstractControl, @@ -19,7 +18,10 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { OrgKey } from "@bitwarden/common/types/key"; import { + DialogRef, ButtonModule, + DialogConfig, + DIALOG_DATA, DialogModule, DialogService, FormFieldModule, diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.html b/apps/web/src/app/billing/members/free-bitwarden-families.component.html index ee21909beec..243cf612c73 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.html +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.html @@ -67,7 +67,9 @@ {{ "resendInvitation" | i18n }} -
+ +
+
`, + standalone: false, }) export class SubscriptionHiddenComponent { @Input() providerName: string; diff --git a/apps/web/src/app/billing/organizations/subscription-status.component.ts b/apps/web/src/app/billing/organizations/subscription-status.component.ts index a097bf674e2..0b59df3f707 100644 --- a/apps/web/src/app/billing/organizations/subscription-status.component.ts +++ b/apps/web/src/app/billing/organizations/subscription-status.component.ts @@ -26,6 +26,7 @@ type ComponentData = { @Component({ selector: "app-subscription-status", templateUrl: "subscription-status.component.html", + standalone: false, }) export class SubscriptionStatusComponent { @Input({ required: true }) organizationSubscriptionResponse: OrganizationSubscriptionResponse; diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index 7e493168ce2..80e66784ae8 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -36,6 +36,7 @@ interface RequestSponsorshipForm { @Component({ selector: "app-sponsored-families", templateUrl: "sponsored-families.component.html", + standalone: false, }) export class SponsoredFamiliesComponent implements OnInit, OnDestroy { loading = false; diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts index 33e6334c577..70320e7e62e 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts @@ -18,6 +18,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "[sponsoring-org-row]", templateUrl: "sponsoring-org-row.component.html", + standalone: false, }) export class SponsoringOrgRowComponent implements OnInit { @Input() sponsoringOrg: Organization = null; @@ -109,7 +110,7 @@ export class SponsoringOrgRowComponent implements OnInit { return; } - await this.apiService.deleteRevokeSponsorship(this.sponsoringOrg.id); + await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.sponsoringOrg.id); this.toastService.showToast({ variant: "success", title: null, diff --git a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts index ec6e251418b..cdf72168acf 100644 --- a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts +++ b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts @@ -35,6 +35,7 @@ export type PayPalConfig = { @Component({ templateUrl: "add-credit-dialog.component.html", + standalone: false, }) export class AddCreditDialogComponent implements OnInit { @ViewChild("ppButtonForm", { read: ElementRef, static: true }) ppButtonFormRef: ElementRef; diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts index 9d32becd1bb..94929c58656 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts @@ -39,6 +39,7 @@ export enum AdjustPaymentDialogResultType { @Component({ templateUrl: "./adjust-payment-dialog.component.html", + standalone: false, }) export class AdjustPaymentDialogComponent implements OnInit { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts index 6cd17218f02..1f9172eaf59 100644 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts @@ -31,6 +31,7 @@ export enum AdjustStorageDialogResultType { @Component({ templateUrl: "./adjust-storage-dialog.component.html", + standalone: false, }) export class AdjustStorageDialogComponent { protected formGroup = new FormGroup({ diff --git a/apps/web/src/app/billing/shared/billing-history.component.ts b/apps/web/src/app/billing/shared/billing-history.component.ts index 1994c53a375..745939f0d5e 100644 --- a/apps/web/src/app/billing/shared/billing-history.component.ts +++ b/apps/web/src/app/billing/shared/billing-history.component.ts @@ -11,6 +11,7 @@ import { @Component({ selector: "app-billing-history", templateUrl: "billing-history.component.html", + standalone: false, }) export class BillingHistoryComponent { @Input() diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.ts b/apps/web/src/app/billing/shared/offboarding-survey.component.ts index 62213c1fe94..9f21f2b8cd5 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.ts +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.ts @@ -49,6 +49,7 @@ export const openOffboardingSurvey = ( @Component({ selector: "app-cancel-subscription-form", templateUrl: "offboarding-survey.component.html", + standalone: false, }) export class OffboardingSurveyComponent { protected ResultType = OffboardingSurveyDialogResultType; diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index f7d4480c4d4..27c9caf7186 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -35,6 +35,7 @@ import { @Component({ templateUrl: "payment-method.component.html", + standalone: false, }) export class PaymentMethodComponent implements OnInit, OnDestroy { loading = false; diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts index 0d7698e00fd..75da10a7b09 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts @@ -17,6 +17,7 @@ import { AbstractSelfHostingLicenseUploaderComponent } from "../../shared/self-h @Component({ selector: "individual-self-hosting-license-uploader", templateUrl: "./self-hosting-license-uploader.component.html", + standalone: false, }) export class IndividualSelfHostingLicenseUploaderComponent extends AbstractSelfHostingLicenseUploaderComponent { /** diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts index 222aff3fec6..1850b5e526d 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts @@ -24,6 +24,7 @@ import { AbstractSelfHostingLicenseUploaderComponent } from "../../shared/self-h @Component({ selector: "organization-self-hosting-license-uploader", templateUrl: "./self-hosting-license-uploader.component.html", + standalone: false, }) export class OrganizationSelfHostingLicenseUploaderComponent extends AbstractSelfHostingLicenseUploaderComponent { /** diff --git a/apps/web/src/app/billing/shared/sm-subscribe.component.ts b/apps/web/src/app/billing/shared/sm-subscribe.component.ts index 23041ccb1ae..1ecf3648bd2 100644 --- a/apps/web/src/app/billing/shared/sm-subscribe.component.ts +++ b/apps/web/src/app/billing/shared/sm-subscribe.component.ts @@ -33,6 +33,7 @@ export const secretsManagerSubscribeFormFactory = ( @Component({ selector: "sm-subscribe", templateUrl: "sm-subscribe.component.html", + standalone: false, }) export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy { @Input() formGroup: FormGroup>; diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts index afc62b0ca31..11b5e7fd8df 100644 --- a/apps/web/src/app/billing/shared/update-license-dialog.component.ts +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts @@ -12,6 +12,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; @Component({ templateUrl: "update-license-dialog.component.html", + standalone: false, }) export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { constructor( diff --git a/apps/web/src/app/billing/shared/update-license.component.ts b/apps/web/src/app/billing/shared/update-license.component.ts index e580d420202..455b38386c6 100644 --- a/apps/web/src/app/billing/shared/update-license.component.ts +++ b/apps/web/src/app/billing/shared/update-license.component.ts @@ -15,6 +15,7 @@ import { UpdateLicenseDialogResult } from "./update-license-types"; @Component({ selector: "app-update-license", templateUrl: "update-license.component.html", + standalone: false, }) export class UpdateLicenseComponent implements OnInit { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html index 078c926891a..7f093842b6a 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html @@ -1,89 +1,100 @@ -
- -
- + + + + + + + +
+} diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 90df6e513c4..215035a0d16 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -47,11 +47,13 @@ export type InitiationPath = @Component({ selector: "app-complete-trial-initiation", templateUrl: "complete-trial-initiation.component.html", + standalone: false, }) export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; - InputPasswordFlow = InputPasswordFlow; + inputPasswordFlow = InputPasswordFlow.AccountRegistration; + initializing = true; /** Password Manager or Secrets Manager */ product: ProductType; @@ -202,6 +204,8 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { .subscribe(() => { this.orgInfoFormGroup.controls.name.markAsTouched(); }); + + this.initializing = false; } ngOnDestroy(): void { diff --git a/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts index e88c49c4f57..cbb1c84284c 100644 --- a/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts +++ b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts @@ -7,6 +7,7 @@ import { ProductType } from "@bitwarden/common/billing/enums"; @Component({ selector: "app-trial-confirmation-details", templateUrl: "confirmation-details.component.html", + standalone: false, }) export class ConfirmationDetailsComponent { @Input() email: string; diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts index e43eb4e6cda..0c6e084f5c4 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts @@ -7,6 +7,7 @@ import { VerticalStep } from "./vertical-step.component"; @Component({ selector: "app-vertical-step-content", templateUrl: "vertical-step-content.component.html", + standalone: false, }) export class VerticalStepContentComponent { @Output() onSelectStep = new EventEmitter(); diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts index 1ff900875df..b4b643b3889 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts @@ -5,6 +5,7 @@ import { Component, Input } from "@angular/core"; selector: "app-vertical-step", templateUrl: "vertical-step.component.html", providers: [{ provide: CdkStep, useExisting: VerticalStep }], + standalone: false, }) export class VerticalStep extends CdkStep { @Input() subLabel = ""; diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts index 04197827813..333224aac54 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts @@ -9,6 +9,7 @@ import { VerticalStep } from "./vertical-step.component"; selector: "app-vertical-stepper", templateUrl: "vertical-stepper.component.html", providers: [{ provide: CdkStepper, useExisting: VerticalStepperComponent }], + standalone: false, }) export class VerticalStepperComponent extends CdkStepper { readonly steps: QueryList; diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.ts b/apps/web/src/app/components/environment-selector/environment-selector.component.ts index b86c068911f..ba0f5097b5b 100644 --- a/apps/web/src/app/components/environment-selector/environment-selector.component.ts +++ b/apps/web/src/app/components/environment-selector/environment-selector.component.ts @@ -10,9 +10,13 @@ import { import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SharedModule } from "../../shared"; + @Component({ selector: "environment-selector", templateUrl: "environment-selector.component.html", + standalone: true, + imports: [SharedModule], }) export class EnvironmentSelectorComponent implements OnInit { constructor( diff --git a/apps/web/src/app/components/environment-selector/environment-selector.module.ts b/apps/web/src/app/components/environment-selector/environment-selector.module.ts deleted file mode 100644 index 1326d4c9cae..00000000000 --- a/apps/web/src/app/components/environment-selector/environment-selector.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { SharedModule } from "../../../app/shared"; - -import { EnvironmentSelectorComponent } from "./environment-selector.component"; - -@NgModule({ - imports: [SharedModule], - declarations: [EnvironmentSelectorComponent], - exports: [EnvironmentSelectorComponent], -}) -export class EnvironmentSelectorModule {} diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 06a91895eb8..e812edd8f32 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -26,7 +26,6 @@ import { WINDOW, } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; -import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service"; import { RegistrationFinishService as RegistrationFinishServiceAbstraction, LoginComponentService, @@ -35,6 +34,7 @@ import { LoginDecryptionOptionsService, TwoFactorAuthComponentService, TwoFactorAuthDuoComponentService, + ChangePasswordService, } from "@bitwarden/auth/angular"; import { InternalUserDecryptionOptionsServiceAbstraction, @@ -111,6 +111,7 @@ import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarde import { flagEnabled } from "../../utils/flags"; import { PolicyListService } from "../admin-console/core/policy-list.service"; import { + WebChangePasswordService, WebSetPasswordJitService, WebRegistrationFinishService, WebLoginComponentService, @@ -124,6 +125,7 @@ import { AcceptOrganizationInviteService } from "../auth/organization-invite/acc import { HtmlStorageService } from "../core/html-storage.service"; import { I18nService } from "../core/i18n.service"; import { WebFileDownloadService } from "../core/web-file-download.service"; +import { UserKeyRotationService } from "../key-management/key-rotation/user-key-rotation.service"; import { WebLockComponentService } from "../key-management/lock/services/web-lock-component.service"; import { WebProcessReloadService } from "../key-management/services/web-process-reload.service"; import { WebBiometricsService } from "../key-management/web-biometric.service"; @@ -136,7 +138,6 @@ import { WebStorageServiceProvider } from "../platform/web-storage-service.provi import { EventService } from "./event.service"; import { InitService } from "./init.service"; import { ENV_URLS } from "./injection-tokens"; -import { ModalService } from "./modal.service"; import { RouterService } from "./router.service"; import { WebPlatformUtilsService } from "./web-platform-utils.service"; @@ -195,11 +196,6 @@ const safeProviders: SafeProvider[] = [ useClass: WebPlatformUtilsService, useAngularDecorators: true, }), - safeProvider({ - provide: ModalServiceAbstraction, - useClass: ModalService, - useAngularDecorators: true, - }), safeProvider({ provide: FileDownloadService, useClass: WebFileDownloadService, @@ -380,6 +376,16 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultSshImportPromptService, deps: [DialogService, ToastService, PlatformUtilsService, I18nServiceAbstraction], }), + safeProvider({ + provide: ChangePasswordService, + useClass: WebChangePasswordService, + deps: [ + KeyServiceAbstraction, + MasterPasswordApiService, + InternalMasterPasswordServiceAbstraction, + UserKeyRotationService, + ], + }), ]; @NgModule({ diff --git a/apps/web/src/app/core/modal.service.ts b/apps/web/src/app/core/modal.service.ts deleted file mode 100644 index 14ea6044f36..00000000000 --- a/apps/web/src/app/core/modal.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Injectable, Injector } from "@angular/core"; -import * as jq from "jquery"; -import { first } from "rxjs/operators"; - -import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; -import { ModalService as BaseModalService } from "@bitwarden/angular/services/modal.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -@Injectable() -export class ModalService extends BaseModalService { - el: any = null; - modalOpen = false; - - constructor( - injector: Injector, - private messagingService: MessagingService, - ) { - super(injector); - } - - protected setupHandlers(modalRef: ModalRef) { - modalRef.onCreated.pipe(first()).subscribe(() => { - const modals = Array.from(document.querySelectorAll(".modal")); - if (modals.length > 0) { - this.el = jq(modals[0]); - this.el.modal("show"); - - this.el.on("show.bs.modal", () => { - modalRef.show(); - this.messagingService.send("modalShow"); - }); - this.el.on("shown.bs.modal", () => { - modalRef.shown(); - this.messagingService.send("modalShown"); - if (!Utils.isMobileBrowser) { - this.el.find("*[appAutoFocus]").focus(); - } - }); - this.el.on("hide.bs.modal", () => { - this.messagingService.send("modalClose"); - }); - this.el.on("hidden.bs.modal", () => { - modalRef.closed(); - this.messagingService.send("modalClosed"); - }); - } - }); - - modalRef.onClose.pipe(first()).subscribe(() => { - if (this.el != null) { - this.el.modal("hide"); - } - }); - } -} diff --git a/apps/web/src/app/dirt/reports/pages/breach-report.component.ts b/apps/web/src/app/dirt/reports/pages/breach-report.component.ts index e1da7be06f8..47bf0844468 100644 --- a/apps/web/src/app/dirt/reports/pages/breach-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/breach-report.component.ts @@ -11,6 +11,7 @@ import { BreachAccountResponse } from "@bitwarden/common/models/response/breach- @Component({ selector: "app-breach-report", templateUrl: "breach-report.component.html", + standalone: false, }) export class BreachReportComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts index 900ff3703f0..5710ea1176e 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts @@ -20,6 +20,7 @@ type ReportResult = CipherView & { exposedXTimes: number }; @Component({ selector: "app-exposed-passwords-report", templateUrl: "exposed-passwords-report.component.html", + standalone: false, }) export class ExposedPasswordsReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts index 5265326128e..95810625dac 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts @@ -21,6 +21,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-inactive-two-factor-report", templateUrl: "inactive-two-factor-report.component.html", + standalone: false, }) export class InactiveTwoFactorReportComponent extends CipherReportComponent implements OnInit { services = new Map(); diff --git a/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts index 4f0988082b4..b88987e1d25 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts @@ -36,6 +36,7 @@ import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts index 6dc202de0b3..1105e814245 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts @@ -35,6 +35,7 @@ import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponen RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts index 4e37f53ba61..7fcf3562437 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts @@ -35,6 +35,7 @@ import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts index 25e1314fceb..2e916da0294 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts @@ -35,6 +35,7 @@ import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponen RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts index ef9bd97008e..80be66e9ad2 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts @@ -36,6 +36,7 @@ import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/reports-home.component.ts b/apps/web/src/app/dirt/reports/pages/reports-home.component.ts index 604d66f6858..acc3efac58a 100644 --- a/apps/web/src/app/dirt/reports/pages/reports-home.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reports-home.component.ts @@ -12,6 +12,7 @@ import { ReportEntry, ReportVariant } from "../shared"; @Component({ selector: "app-reports-home", templateUrl: "reports-home.component.html", + standalone: false, }) export class ReportsHomeComponent implements OnInit { reports: ReportEntry[]; diff --git a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts index 6d70cc23875..3e9abc779ba 100644 --- a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts @@ -19,6 +19,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-reused-passwords-report", templateUrl: "reused-passwords-report.component.html", + standalone: false, }) export class ReusedPasswordsReportComponent extends CipherReportComponent implements OnInit { passwordUseMap: Map; diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts index 02d4117c684..d2cc792198e 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts @@ -18,6 +18,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-unsecured-websites-report", templateUrl: "unsecured-websites-report.component.html", + standalone: false, }) export class UnsecuredWebsitesReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts index 4144c9ac20f..1716a98190c 100644 --- a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts @@ -28,6 +28,7 @@ type ReportResult = CipherView & { score: number; reportValue: ReportScore; scor @Component({ selector: "app-weak-passwords-report", templateUrl: "weak-passwords-report.component.html", + standalone: false, }) export class WeakPasswordsReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.ts b/apps/web/src/app/dirt/reports/reports-layout.component.ts index 7bfe912c1ad..360898e6057 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.ts +++ b/apps/web/src/app/dirt/reports/reports-layout.component.ts @@ -6,6 +6,7 @@ import { filter } from "rxjs/operators"; @Component({ selector: "app-reports-layout", templateUrl: "reports-layout.component.html", + standalone: false, }) export class ReportsLayoutComponent implements OnDestroy { homepage = true; diff --git a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts index da42d92bf84..92e6ddb0028 100644 --- a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts +++ b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts @@ -9,6 +9,7 @@ import { ReportVariant } from "../models/report-variant"; @Component({ selector: "app-report-card", templateUrl: "report-card.component.html", + standalone: false, }) export class ReportCardComponent { @Input() title: string; diff --git a/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts b/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts index cd6b77f9c81..c81c99d50d5 100644 --- a/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts +++ b/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts @@ -7,6 +7,7 @@ import { ReportEntry } from "../models/report-entry"; @Component({ selector: "app-report-list", templateUrl: "report-list.component.html", + standalone: false, }) export class ReportListComponent { @Input() reports: ReportEntry[]; diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.ts b/apps/web/src/app/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.ts +++ b/apps/web/src/app/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/web/src/app/layouts/frontend-layout.component.ts b/apps/web/src/app/layouts/frontend-layout.component.ts index 609845f22cd..5ccb39b1dc9 100644 --- a/apps/web/src/app/layouts/frontend-layout.component.ts +++ b/apps/web/src/app/layouts/frontend-layout.component.ts @@ -4,9 +4,14 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { EnvironmentSelectorComponent } from "../components/environment-selector/environment-selector.component"; +import { SharedModule } from "../shared"; + @Component({ selector: "app-frontend-layout", templateUrl: "frontend-layout.component.html", + standalone: true, + imports: [SharedModule, EnvironmentSelectorComponent], }) export class FrontendLayoutComponent implements OnInit, OnDestroy { version: string; diff --git a/apps/web/src/app/layouts/header/web-header.component.ts b/apps/web/src/app/layouts/header/web-header.component.ts index e17d059160f..67d447723e1 100644 --- a/apps/web/src/app/layouts/header/web-header.component.ts +++ b/apps/web/src/app/layouts/header/web-header.component.ts @@ -17,6 +17,7 @@ import { UserId } from "@bitwarden/common/types/guid"; @Component({ selector: "app-header", templateUrl: "./web-header.component.html", + standalone: false, }) export class WebHeaderComponent { /** diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts index 737fa2d8d2e..9d4250087af 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts @@ -6,6 +6,7 @@ import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-s @Component({ selector: "navigation-product-switcher", templateUrl: "./navigation-switcher.component.html", + standalone: false, }) export class NavigationProductSwitcherComponent { constructor(private productSwitcherService: ProductSwitcherService) {} diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index fca9063c8cf..b1c1a0a906a 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -24,6 +24,7 @@ import { NavigationProductSwitcherComponent } from "./navigation-switcher.compon @Directive({ selector: "[mockOrgs]", + standalone: false, }) class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); @@ -40,6 +41,7 @@ class MockOrganizationService implements Partial { @Directive({ selector: "[mockProviders]", + standalone: false, }) class MockProviderService implements Partial { private static _providers = new BehaviorSubject([]); @@ -78,12 +80,14 @@ class MockPlatformUtilsService implements Partial { @Component({ selector: "story-layout", template: ``, + standalone: false, }) class StoryLayoutComponent {} @Component({ selector: "story-content", template: ``, + standalone: false, }) class StoryContentComponent {} diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts index 4a22f628570..5a6572e15be 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts @@ -9,6 +9,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Component({ selector: "product-switcher-content", templateUrl: "./product-switcher-content.component.html", + standalone: false, }) export class ProductSwitcherContentComponent { @ViewChild("menu") diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts index 834571e2cb4..5dd29815ef2 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts @@ -8,6 +8,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Component({ selector: "product-switcher", templateUrl: "./product-switcher.component.html", + standalone: false, }) export class ProductSwitcherComponent implements AfterViewInit { /** diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index ca27aae4581..4525105e579 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -24,6 +24,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Directive({ selector: "[mockOrgs]", + standalone: false, }) class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); @@ -40,6 +41,7 @@ class MockOrganizationService implements Partial { @Directive({ selector: "[mockProviders]", + standalone: false, }) class MockProviderService implements Partial { private static _providers = new BehaviorSubject([]); @@ -78,12 +80,14 @@ class MockPlatformUtilsService implements Partial { @Component({ selector: "story-layout", template: ``, + standalone: false, }) class StoryLayoutComponent {} @Component({ selector: "story-content", template: ``, + standalone: false, }) class StoryContentComponent {} diff --git a/apps/web/src/app/layouts/web-layout.component.ts b/apps/web/src/app/layouts/web-layout.component.ts index 840beaa2179..aa4de4cfee5 100644 --- a/apps/web/src/app/layouts/web-layout.component.ts +++ b/apps/web/src/app/layouts/web-layout.component.ts @@ -4,13 +4,12 @@ import { Component } from "@angular/core"; import { LayoutComponent } from "@bitwarden/components"; import { ProductSwitcherModule } from "./product-switcher/product-switcher.module"; -import { ToggleWidthComponent } from "./toggle-width.component"; @Component({ selector: "app-layout", templateUrl: "web-layout.component.html", standalone: true, - imports: [CommonModule, LayoutComponent, ProductSwitcherModule, ToggleWidthComponent], + imports: [CommonModule, LayoutComponent, ProductSwitcherModule], }) export class WebLayoutComponent { constructor() {} diff --git a/apps/web/src/app/settings/domain-rules.component.ts b/apps/web/src/app/settings/domain-rules.component.ts index 6dd27cbf19b..7656222cfd2 100644 --- a/apps/web/src/app/settings/domain-rules.component.ts +++ b/apps/web/src/app/settings/domain-rules.component.ts @@ -9,9 +9,14 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { HeaderModule } from "../layouts/header/header.module"; +import { SharedModule } from "../shared"; + @Component({ selector: "app-domain-rules", templateUrl: "domain-rules.component.html", + standalone: true, + imports: [SharedModule, HeaderModule], }) export class DomainRulesComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/settings/preferences.component.ts b/apps/web/src/app/settings/preferences.component.ts index 4d4e0c3d711..c9efd059271 100644 --- a/apps/web/src/app/settings/preferences.component.ts +++ b/apps/web/src/app/settings/preferences.component.ts @@ -14,6 +14,7 @@ import { tap, } from "rxjs"; +import { VaultTimeoutInputComponent } from "@bitwarden/auth/angular"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service"; @@ -34,9 +35,14 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { DialogService } from "@bitwarden/components"; +import { HeaderModule } from "../layouts/header/header.module"; +import { SharedModule } from "../shared"; + @Component({ selector: "app-preferences", templateUrl: "preferences.component.html", + standalone: true, + imports: [SharedModule, HeaderModule, VaultTimeoutInputComponent], }) export class PreferencesComponent implements OnInit, OnDestroy { // For use in template diff --git a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts index 7bb86c9f669..f9798ec7f0f 100644 --- a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts +++ b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts @@ -8,6 +8,7 @@ import { Component, Input } from "@angular/core"; host: { class: "tw-max-w-max", }, + standalone: false, }) export class OnboardingTaskComponent { @Input() diff --git a/apps/web/src/app/shared/components/onboarding/onboarding.component.ts b/apps/web/src/app/shared/components/onboarding/onboarding.component.ts index 23f9015b024..5ead9fcc10b 100644 --- a/apps/web/src/app/shared/components/onboarding/onboarding.component.ts +++ b/apps/web/src/app/shared/components/onboarding/onboarding.component.ts @@ -7,6 +7,7 @@ import { OnboardingTaskComponent } from "./onboarding-task.component"; @Component({ selector: "app-onboarding", templateUrl: "./onboarding.component.html", + standalone: false, }) export class OnboardingComponent { @ContentChildren(OnboardingTaskComponent) tasks: QueryList; diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index ab6c6cc5d72..e59633ee499 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -40,20 +40,18 @@ import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.comp import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component"; import { DynamicAvatarComponent } from "../components/dynamic-avatar.component"; +// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../dirt/reports/pages/organizations/exposed-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../dirt/reports/pages/organizations/inactive-two-factor-report.component"; +// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../dirt/reports/pages/organizations/reused-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../dirt/reports/pages/organizations/unsecured-websites-report.component"; +// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../dirt/reports/pages/organizations/weak-passwords-report.component"; import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; -import { FrontendLayoutComponent } from "../layouts/frontend-layout.component"; import { HeaderModule } from "../layouts/header/header.module"; -import { ProductSwitcherModule } from "../layouts/product-switcher/product-switcher.module"; -import { UserLayoutComponent } from "../layouts/user-layout.component"; -import { DomainRulesComponent } from "../settings/domain-rules.component"; -import { PreferencesComponent } from "../settings/preferences.component"; -/* eslint no-restricted-imports: "off" -- Temporarily disabled until Tools refactors these out of this module */ -/* eslint no-restricted-imports: "error" */ import { PremiumBadgeComponent } from "../vault/components/premium-badge.component"; import { FolderAddEditComponent } from "../vault/individual-vault/folder-add-edit.component"; import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module"; @@ -61,7 +59,6 @@ import { PipesModule } from "../vault/individual-vault/pipes/pipes.module"; import { PurgeVaultComponent } from "../vault/settings/purge-vault.component"; import { FreeBitwardenFamiliesComponent } from "./../billing/members/free-bitwarden-families.component"; -import { EnvironmentSelectorModule } from "./../components/environment-selector/environment-selector.module"; import { AccountFingerprintComponent } from "./components/account-fingerprint/account-fingerprint.component"; import { SharedModule } from "./shared.module"; @@ -70,11 +67,9 @@ import { SharedModule } from "./shared.module"; @NgModule({ imports: [ SharedModule, - ProductSwitcherModule, UserVerificationModule, ChangeKdfModule, DynamicAvatarComponent, - EnvironmentSelectorModule, AccountFingerprintComponent, OrganizationBadgeModule, PipesModule, @@ -85,7 +80,6 @@ import { SharedModule } from "./shared.module"; NavigationModule, HeaderModule, OrganizationLayoutComponent, - UserLayoutComponent, VerifyRecoverDeleteOrgComponent, VaultTimeoutInputComponent, ], @@ -96,14 +90,12 @@ import { SharedModule } from "./shared.module"; ChangeEmailComponent, DeauthorizeSessionsComponent, DeleteAccountDialogComponent, - DomainRulesComponent, EmergencyAccessAddEditComponent, EmergencyAccessComponent, EmergencyAccessConfirmComponent, EmergencyAccessTakeoverComponent, EmergencyAccessViewComponent, FolderAddEditComponent, - FrontendLayoutComponent, OrgEventsComponent, OrgExposedPasswordsReportComponent, OrgInactiveTwoFactorReportComponent, @@ -111,7 +103,6 @@ import { SharedModule } from "./shared.module"; OrgUnsecuredWebsitesReportComponent, OrgUserConfirmComponent, OrgWeakPasswordsReportComponent, - PreferencesComponent, PremiumBadgeComponent, ProfileComponent, ChangeAvatarDialogComponent, @@ -139,7 +130,6 @@ import { SharedModule } from "./shared.module"; ChangeEmailComponent, DeauthorizeSessionsComponent, DeleteAccountDialogComponent, - DomainRulesComponent, DynamicAvatarComponent, EmergencyAccessAddEditComponent, EmergencyAccessComponent, @@ -147,7 +137,6 @@ import { SharedModule } from "./shared.module"; EmergencyAccessTakeoverComponent, EmergencyAccessViewComponent, FolderAddEditComponent, - FrontendLayoutComponent, OrganizationLayoutComponent, OrgEventsComponent, OrgExposedPasswordsReportComponent, @@ -156,7 +145,6 @@ import { SharedModule } from "./shared.module"; OrgUnsecuredWebsitesReportComponent, OrgUserConfirmComponent, OrgWeakPasswordsReportComponent, - PreferencesComponent, PremiumBadgeComponent, ProfileComponent, ChangeAvatarDialogComponent, @@ -173,7 +161,6 @@ import { SharedModule } from "./shared.module"; SponsoringOrgRowComponent, UpdateTempPasswordComponent, UpdatePasswordComponent, - UserLayoutComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, HeaderModule, diff --git a/apps/web/src/app/vault/components/premium-badge.component.ts b/apps/web/src/app/vault/components/premium-badge.component.ts index 6deff43489d..ec444404aea 100644 --- a/apps/web/src/app/vault/components/premium-badge.component.ts +++ b/apps/web/src/app/vault/components/premium-badge.component.ts @@ -9,6 +9,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag {{ "premium" | i18n }} `, + standalone: false, }) export class PremiumBadgeComponent { constructor(private messagingService: MessagingService) {} diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 10c35f861b9..aa457e97093 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -481,9 +481,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { activeUserId, ); - updatedCipherView = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + updatedCipherView = await this.cipherService.decrypt(updatedCipher, activeUserId); } this.cipherFormComponent.patchCipher((currentCipher) => { @@ -520,9 +518,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { return; } const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - return await config.originalCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(config.originalCipher, activeUserId), - ); + return await this.cipherService.decrypt(config.originalCipher, activeUserId); } private updateTitle() { diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts index 317b02356ba..ac1774cd244 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts @@ -20,6 +20,7 @@ import { RowHeightClass } from "./vault-items.component"; @Component({ selector: "tr[appVaultCipherRow]", templateUrl: "vault-cipher-row.component.html", + standalone: false, }) export class VaultCipherRowComponent implements OnInit { protected RowHeightClass = RowHeightClass; diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts index d07ba46d136..06c78ea0351 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts @@ -18,6 +18,7 @@ import { RowHeightClass } from "./vault-items.component"; @Component({ selector: "tr[appVaultCollectionRow]", templateUrl: "vault-collection-row.component.html", + standalone: false, }) export class VaultCollectionRowComponent { protected RowHeightClass = RowHeightClass; diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.html b/apps/web/src/app/vault/components/vault-items/vault-items.component.html index f1f50beb582..7f3cc387122 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.html @@ -1,4 +1,4 @@ - + diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index d27f332f13f..1c63ac85d34 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -32,8 +32,7 @@ type ItemPermission = CollectionPermission | "NoAccess"; @Component({ selector: "app-vault-items", templateUrl: "vault-items.component.html", - // TODO: Improve change detection, see: https://bitwarden.atlassian.net/browse/TDL-220 - // changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class VaultItemsComponent { protected RowHeight = RowHeight; diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.module.ts b/apps/web/src/app/vault/components/vault-items/vault-items.module.ts index e54a9c1141f..ab4f8bddb16 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.module.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.module.ts @@ -3,7 +3,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { TableModule } from "@bitwarden/components"; +import { ScrollLayoutDirective, TableModule } from "@bitwarden/components"; import { CollectionNameBadgeComponent } from "../../../admin-console/organizations/collections"; import { GroupBadgeModule } from "../../../admin-console/organizations/collections/group-badge/group-badge.module"; @@ -26,6 +26,7 @@ import { VaultItemsComponent } from "./vault-items.component"; CollectionNameBadgeComponent, GroupBadgeModule, PipesModule, + ScrollLayoutDirective, ], declarations: [VaultItemsComponent, VaultCipherRowComponent, VaultCollectionRowComponent], exports: [VaultItemsComponent], diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index 55807ed855f..02c845ac944 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -2,7 +2,13 @@ // @ts-strict-ignore import { importProvidersFrom } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { + applicationConfig, + componentWrapperDecorator, + Meta, + moduleMetadata, + StoryObj, +} from "@storybook/angular"; import { BehaviorSubject, of } from "rxjs"; import { @@ -29,6 +35,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { LayoutComponent } from "@bitwarden/components"; import { GroupView } from "../../../admin-console/organizations/core"; import { PreloadedEnglishI18nModule } from "../../../core/tests"; @@ -48,8 +55,9 @@ export default { title: "Web/Vault/Items", component: VaultItemsComponent, decorators: [ + componentWrapperDecorator((story) => `${story}`), moduleMetadata({ - imports: [VaultItemsModule, RouterModule], + imports: [VaultItemsModule, RouterModule, LayoutComponent], providers: [ { provide: EnvironmentService, diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 1650b0f371f..43a44cf5066 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -53,6 +53,7 @@ export const openBulkDeleteDialog = ( @Component({ templateUrl: "bulk-delete-dialog.component.html", + standalone: false, }) export class BulkDeleteDialogComponent { cipherIds: string[]; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index d287c430d49..dc262b01334 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -47,6 +47,7 @@ export const openBulkMoveDialog = ( @Component({ templateUrl: "bulk-move-dialog.component.html", + standalone: false, }) export class BulkMoveDialogComponent implements OnInit { cipherIds: string[] = []; diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 6a3c5663d93..3050c00dd6c 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -23,6 +23,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-folder-add-edit", templateUrl: "folder-add-edit.component.html", + standalone: false, }) export class FolderAddEditComponent extends BaseFolderAddEditComponent { protected override componentName = "app-folder-add-edit"; diff --git a/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts b/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts index c1f935f2001..915bc00bacd 100644 --- a/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts +++ b/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts @@ -12,6 +12,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-org-badge", templateUrl: "organization-name-badge.component.html", + standalone: false, }) export class OrganizationNameBadgeComponent implements OnChanges { @Input() organizationId?: string; diff --git a/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts b/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts index 81d3a8de749..09bce96728a 100644 --- a/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts +++ b/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts @@ -5,6 +5,7 @@ import { GroupView } from "../../../admin-console/organizations/core"; @Pipe({ name: "groupNameFromId", pure: true, + standalone: false, }) export class GetGroupNameFromIdPipe implements PipeTransform { transform(value: string, groups: GroupView[]) { diff --git a/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts b/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts index 4d6c0b7d8d7..bf9dc82c527 100644 --- a/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts +++ b/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts @@ -5,6 +5,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga @Pipe({ name: "orgNameFromId", pure: true, + standalone: false, }) export class GetOrgNameFromIdPipe implements PipeTransform { transform(value: string, organizations: Organization[]) { diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index c7d91237578..e95ea669725 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -33,6 +33,7 @@ import { OrganizationFilter } from "../shared/models/vault-filter.type"; @Component({ selector: "app-organization-options", templateUrl: "organization-options.component.html", + standalone: false, }) export class OrganizationOptionsComponent implements OnInit, OnDestroy { protected actionPromise?: Promise; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts index 3f1e7755c8f..6b974296f21 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts @@ -38,6 +38,7 @@ import { OrganizationOptionsComponent } from "./organization-options.component"; @Component({ selector: "app-vault-filter", templateUrl: "vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent implements OnInit, OnDestroy { filters?: VaultFilterList; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts index 41329319805..1a0a96fa19c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts @@ -16,6 +16,7 @@ import { VaultFilter } from "../models/vault-filter.model"; @Component({ selector: "app-filter-section", templateUrl: "vault-filter-section.component.html", + standalone: false, }) export class VaultFilterSectionComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/vault/settings/purge-vault.component.ts b/apps/web/src/app/vault/settings/purge-vault.component.ts index 11e75c16720..0a25122788c 100644 --- a/apps/web/src/app/vault/settings/purge-vault.component.ts +++ b/apps/web/src/app/vault/settings/purge-vault.component.ts @@ -25,6 +25,7 @@ export interface PurgeVaultDialogData { @Component({ selector: "app-purge-vault", templateUrl: "purge-vault.component.html", + standalone: false, }) export class PurgeVaultComponent { organizationId: string = null; diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 7484835d60c..d526b2b76ff 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -98,8 +98,8 @@ "markAppAsCritical": { "message": "Mark app as critical" }, - "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "applicationsMarkedAsCriticalSuccess": { + "message": "Applications marked as critical" }, "application": { "message": "Application" @@ -140,9 +140,15 @@ "atRiskMembersDescription": { "message": "These members are logging into applications with weak, exposed, or reused passwords." }, + "atRiskMembersDescriptionNone": { + "message": "These are no members logging into applications with weak, exposed, or reused passwords." + }, "atRiskApplicationsDescription": { "message": "These applications have weak, exposed, or reused passwords." }, + "atRiskApplicationsDescriptionNone": { + "message": "These are no applications with weak, exposed, or reused passwords." + }, "atRiskMembersDescriptionWithApp": { "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", "placeholders": { @@ -152,6 +158,15 @@ } } }, + "atRiskMembersDescriptionWithAppNone": { + "message": "There are no at risk members for $APPNAME$.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -4534,8 +4549,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index b202a170d26..572d3968f3d 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -1,10 +1,6 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import "bootstrap"; -import "jquery"; -import "popper.js"; - import { AppModule } from "./app/app.module"; if (process.env.NODE_ENV === "production") { diff --git a/bitwarden_license/bit-cli/src/bw.ts b/bitwarden_license/bit-cli/src/bw.ts index ffbc186d9e0..2e4e945b9c8 100644 --- a/bitwarden_license/bit-cli/src/bw.ts +++ b/bitwarden_license/bit-cli/src/bw.ts @@ -1,3 +1,5 @@ +import "core-js/proposals/explicit-resource-management"; + import { program } from "commander"; import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs"; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts index b8333828693..d24d8386ecd 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts @@ -27,10 +27,12 @@ export type ApplicationHealthReportDetail = { applicationName: string; passwordCount: number; atRiskPasswordCount: number; + atRiskCipherIds: string[]; memberCount: number; atRiskMemberCount: number; memberDetails: MemberDetailsFlat[]; atRiskMemberDetails: MemberDetailsFlat[]; + cipher: CipherView; }; export type ApplicationHealthReportDetailWithCriticalFlag = ApplicationHealthReportDetail & { @@ -48,6 +50,7 @@ export type CipherHealthReportUriDetail = { exposedPasswordDetail: ExposedPasswordDetail; cipherMembers: MemberDetailsFlat[]; trimmedUri: string; + cipher: CipherView; }; /** diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index 8a6eb5000cd..e4fece801b6 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -354,11 +354,15 @@ export class RiskInsightsReportService { : newUriDetail.cipherMembers, atRiskMemberDetails: existingUriDetail ? existingUriDetail.atRiskMemberDetails : [], atRiskPasswordCount: existingUriDetail ? existingUriDetail.atRiskPasswordCount : 0, + atRiskCipherIds: existingUriDetail ? existingUriDetail.atRiskCipherIds : [], atRiskMemberCount: existingUriDetail ? existingUriDetail.atRiskMemberDetails.length : 0, + cipher: newUriDetail.cipher, } as ApplicationHealthReportDetail; if (isAtRisk) { reportDetail.atRiskPasswordCount = reportDetail.atRiskPasswordCount + 1; + reportDetail.atRiskCipherIds.push(newUriDetail.cipherId); + reportDetail.atRiskMemberDetails = this.getUniqueMembers( reportDetail.atRiskMemberDetails.concat(newUriDetail.cipherMembers), ); @@ -399,6 +403,7 @@ export class RiskInsightsReportService { exposedPasswordDetail: detail.exposedPasswordDetail, cipherMembers: detail.cipherMembers, trimmedUri: uri, + cipher: detail as CipherView, }; } diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts index b9b7460f7ed..970a476df22 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts @@ -24,6 +24,7 @@ export interface DomainAddEditDialogData { @Component({ templateUrl: "domain-add-edit-dialog.component.html", + standalone: false, }) export class DomainAddEditDialogComponent implements OnInit, OnDestroy { private componentDestroyed$: Subject = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts index 8fc9b6bba0f..1f644f55a9f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts @@ -35,6 +35,7 @@ import { @Component({ selector: "app-org-manage-domain-verification", templateUrl: "domain-verification.component.html", + standalone: false, }) export class DomainVerificationComponent implements OnInit, OnDestroy { private componentDestroyed$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts index 76bcd7383f3..de870cdbdcb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts @@ -24,6 +24,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-org-manage-scim", templateUrl: "scim.component.html", + standalone: false, }) export class ScimComponent implements OnInit { loading = true; diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts index c276f25663a..61e2133d059 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts @@ -21,5 +21,6 @@ export class ActivateAutofillPolicy extends BasePolicy { @Component({ selector: "policy-activate-autofill", templateUrl: "activate-autofill.component.html", + standalone: false, }) export class ActivateAutofillPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts index 354192ff860..1a478fb4393 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts @@ -19,6 +19,7 @@ export class AutomaticAppLoginPolicy extends BasePolicy { @Component({ selector: "policy-automatic-app-login", templateUrl: "automatic-app-login.component.html", + standalone: false, }) export class AutomaticAppLoginPolicyComponent extends BasePolicyComponent { data = this.formBuilder.group({ diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts index 848b1d173a1..c274e58ccac 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts @@ -16,5 +16,6 @@ export class DisablePersonalVaultExportPolicy extends BasePolicy { @Component({ selector: "policy-disable-personal-vault-export", templateUrl: "disable-personal-vault-export.component.html", + standalone: false, }) export class DisablePersonalVaultExportPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts index 9d09ead800e..a5b9ad47f6e 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts @@ -22,6 +22,7 @@ export class MaximumVaultTimeoutPolicy extends BasePolicy { @Component({ selector: "policy-maximum-timeout", templateUrl: "maximum-vault-timeout.component.html", + standalone: false, }) export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent { vaultTimeoutActionOptions: { name: string; value: string }[]; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts index 88738aa897c..51296c52281 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts @@ -19,6 +19,7 @@ interface AddOrganizationDialogData { @Component({ templateUrl: "add-organization.component.html", + standalone: false, }) export class AddOrganizationComponent implements OnInit { protected provider: Provider; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts index d22665b432f..9f3582f84bb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts @@ -8,6 +8,7 @@ import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; @Component({ selector: "app-create-organization", templateUrl: "create-organization.component.html", + standalone: false, }) export class CreateOrganizationComponent implements OnInit { @ViewChild(OrganizationPlansComponent, { static: true }) diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts index a3ef074b975..7bfac8f4b32 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts @@ -14,6 +14,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ selector: "app-accept-provider", templateUrl: "accept-provider.component.html", + standalone: false, }) export class AcceptProviderComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts index 67f2382cf91..e21837f7226 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts @@ -35,6 +35,7 @@ export enum AddEditMemberDialogResultType { @Component({ templateUrl: "add-edit-member-dialog.component.html", + standalone: false, }) export class AddEditMemberDialogComponent { editing = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts index 8271869f1b4..8bbc299269d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts @@ -29,6 +29,7 @@ type BulkConfirmDialogParams = { @Component({ templateUrl: "../../../../../../../../apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.html", + standalone: false, }) export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent { providerId: string; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts index c3f6409f296..e000d918414 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts @@ -19,6 +19,7 @@ type BulkRemoveDialogParams = { @Component({ templateUrl: "../../../../../../../../apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.html", + standalone: false, }) export class BulkRemoveDialogComponent extends BaseBulkRemoveComponent { providerId: string; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts index 87f29fd91e9..2ad2ecdccbd 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts @@ -19,6 +19,7 @@ import { EventExportService } from "@bitwarden/web-vault/app/tools/event-export" @Component({ selector: "provider-events", templateUrl: "events.component.html", + standalone: false, }) export class EventsComponent extends BaseEventsComponent implements OnInit { exportFileName = "provider-events"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.html index 66c42678442..f203b7a934a 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.html @@ -55,7 +55,7 @@ > {{ "providerUsersNeedConfirmed" | i18n }} - + diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts index 4a184d2dd16..9cbe8115008 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts @@ -45,6 +45,7 @@ class MembersTableDataSource extends PeopleTableDataSource { @Component({ templateUrl: "members.component.html", + standalone: false, }) export class MembersComponent extends BaseMembersComponent { accessEvents = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts index ac2fb333fae..f3d67abd4e1 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts @@ -10,6 +10,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-providers", templateUrl: "providers.component.html", + standalone: false, }) export class ProvidersComponent implements OnInit { providers: Provider[]; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 597acb0d4f0..01f1facfc15 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -4,7 +4,7 @@ import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { CardComponent, SearchModule } from "@bitwarden/components"; +import { CardComponent, ScrollLayoutDirective, SearchModule } from "@bitwarden/components"; import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component"; import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/payment/payment.component"; @@ -53,6 +53,7 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr ScrollingModule, VerifyBankAccountComponent, CardComponent, + ScrollLayoutDirective, PaymentComponent, ], declarations: [ diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts index e72e1c7c326..12dada12aa9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts @@ -20,6 +20,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "provider-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnDestroy, OnInit { selfHosted = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts index a41c7cba362..473380ff288 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts @@ -7,6 +7,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ selector: "app-setup-provider", templateUrl: "setup-provider.component.html", + standalone: false, }) export class SetupProviderComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index 0b6483b9f48..cb47b3fe28f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -23,6 +23,7 @@ import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/paymen @Component({ selector: "provider-setup", templateUrl: "setup.component.html", + standalone: false, }) export class SetupComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts index b27a7ddd0f4..5c0d0982fb5 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-recover-delete-provider", templateUrl: "verify-recover-delete-provider.component.html", + standalone: false, }) export class VerifyRecoverDeleteProviderComponent implements OnInit { name: string; diff --git a/bitwarden_license/bit-web/src/app/app.component.ts b/bitwarden_license/bit-web/src/app/app.component.ts index dd814f5c0d2..2d0dfd967a1 100644 --- a/bitwarden_license/bit-web/src/app/app.component.ts +++ b/bitwarden_license/bit-web/src/app/app.component.ts @@ -12,6 +12,7 @@ import { FreeFamiliesSponsorshipPolicy } from "./billing/policies/free-families- @Component({ selector: "app-root", templateUrl: "../../../../apps/web/src/app/app.component.html", + standalone: false, }) export class AppComponent extends BaseAppComponent implements OnInit { ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts index 03e09f8de4b..f0761757c65 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts @@ -51,6 +51,7 @@ const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha2 @Component({ selector: "app-org-manage-sso", templateUrl: "sso.component.html", + standalone: false, }) export class SsoComponent implements OnInit, OnDestroy { readonly ssoType = SsoType; diff --git a/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts index 521f8658890..53d2b0ab66c 100644 --- a/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts @@ -16,5 +16,6 @@ export class FreeFamiliesSponsorshipPolicy extends BasePolicy { @Component({ selector: "policy-personal-ownership", templateUrl: "free-families-sponsorship.component.html", + standalone: false, }) export class FreeFamiliesSponsorshipPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts index fa3a617b45f..d1a9d43a6fc 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts @@ -12,6 +12,7 @@ import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/ser @Component({ templateUrl: "./provider-billing-history.component.html", + standalone: false, }) export class ProviderBillingHistoryComponent { private providerId: string; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts index 4bb2c36ef15..f0eda893d67 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts @@ -27,6 +27,7 @@ export enum AddExistingOrganizationDialogResultType { @Component({ templateUrl: "./add-existing-organization-dialog.component.html", + standalone: false, }) export class AddExistingOrganizationDialogComponent implements OnInit { protected loading: boolean = true; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts index d71e18cd539..c7d82c3ec09 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts @@ -1,4 +1,3 @@ -import { BasePortalOutlet } from "@angular/cdk/portal"; import { Component, Inject, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; @@ -33,8 +32,7 @@ export const openCreateClientDialog = ( dialogService: DialogService, dialogConfig: DialogConfig< CreateClientDialogParams, - DialogRef, - BasePortalOutlet + DialogRef >, ) => dialogService.open( @@ -101,6 +99,7 @@ export class PlanCard { @Component({ templateUrl: "./create-client-dialog.component.html", + standalone: false, }) export class CreateClientDialogComponent implements OnInit { protected discountPercentage: number | null | undefined; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts index 45abeab1f4a..06d1f80c101 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts @@ -41,6 +41,7 @@ export const openManageClientNameDialog = ( @Component({ templateUrl: "manage-client-name-dialog.component.html", + standalone: false, }) export class ManageClientNameDialogComponent { protected ResultType = ManageClientNameDialogResultType; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts index ced48bfdbea..71e549b563b 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts @@ -36,6 +36,7 @@ export const openManageClientSubscriptionDialog = ( @Component({ templateUrl: "./manage-client-subscription-dialog.component.html", + standalone: false, }) export class ManageClientSubscriptionDialogComponent implements OnInit { protected loading = true; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts index f262ba1abd0..056339b6fb7 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts @@ -19,6 +19,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ templateUrl: "./setup-business-unit.component.html", + standalone: false, }) export class SetupBusinessUnitComponent extends BaseAcceptComponent { protected bitwardenLogo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts index c49509427b8..974dc9c460f 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts @@ -26,6 +26,7 @@ type ComponentData = { @Component({ selector: "app-provider-subscription-status", templateUrl: "provider-subscription-status.component.html", + standalone: false, }) export class ProviderSubscriptionStatusComponent { @Input({ required: true }) subscription: ProviderSubscriptionResponse; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index 657d16d9380..74368ef7839 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -26,6 +26,7 @@ import { @Component({ selector: "app-provider-subscription", templateUrl: "./provider-subscription.component.html", + standalone: false, }) export class ProviderSubscriptionComponent implements OnInit, OnDestroy { private providerId: string; diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts index 993d9c0a134..6df0f01bc8b 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts @@ -6,6 +6,7 @@ import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-con import { RiskInsightsComponent } from "./risk-insights.component"; const routes: Routes = [ + { path: "", pathMatch: "full", redirectTo: "risk-insights" }, { path: "risk-insights", canActivate: [organizationPermissionsGuard((org) => org.useRiskInsights)], diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html index c9d0ba11eee..6f8e738fdc3 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html @@ -56,16 +56,15 @@ [formControl]="searchControl" >
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts index f53272845f2..c586882a1e0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts @@ -26,6 +26,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { + IconButtonModule, Icons, NoItemsModule, SearchModule, @@ -53,6 +54,7 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" NoItemsModule, SharedModule, AppTableRowScrollableComponent, + IconButtonModule, ], }) export class AllApplicationsComponent implements OnInit { @@ -160,7 +162,7 @@ export class AllApplicationsComponent implements OnInit { this.toastService.showToast({ variant: "success", title: "", - message: this.i18nService.t("appsMarkedAsCritical"), + message: this.i18nService.t("applicationsMarkedAsCriticalSuccess"), }); } finally { this.selectedUrls.clear(); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html index b575d40076c..10dbb179519 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html @@ -2,6 +2,7 @@ + {{ "application" | i18n }} {{ "atRiskPasswords" | i18n }} {{ "totalPasswords" | i18n }} @@ -30,6 +31,9 @@ > + + + diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index 2d5693dad54..f759e483bd0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -1,10 +1,7 @@ -
- {{ "accessIntelligence" | i18n }} -

{{ "riskInsights" | i18n }}

-
+
{{ "reviewAtRiskPasswords" | i18n }}
{{ - "atRiskMembersDescription" | i18n + (dataService.atRiskMemberDetails.length > 0 + ? "atRiskMembersDescription" + : "atRiskMembersDescriptionNone" + ) | i18n }} -
-
{{ "email" | i18n }}
-
{{ "atRiskPasswords" | i18n }}
-
- -
-
{{ member.email }}
-
{{ member.atRiskPasswordCount }}
+ +
+
{{ "email" | i18n }}
+
+ {{ "atRiskPasswords" | i18n }} +
+ +
+
{{ member.email }}
+
{{ member.atRiskPasswordCount }}
+
+
@@ -95,7 +99,10 @@
{{ - "atRiskMembersDescriptionWithApp" | i18n: dataService.appAtRiskMembers.applicationName + (dataService.appAtRiskMembers.members.length > 0 + ? "atRiskMembersDescriptionWithApp" + : "atRiskMembersDescriptionWithAppNone" + ) | i18n: dataService.appAtRiskMembers.applicationName }}
@@ -114,17 +121,26 @@ {{ - "atRiskApplicationsDescription" | i18n + (dataService.atRiskAppDetails.length > 0 + ? "atRiskApplicationsDescription" + : "atRiskApplicationsDescriptionNone" + ) | i18n }} -
-
{{ "application" | i18n }}
-
{{ "atRiskPasswords" | i18n }}
-
- -
-
{{ app.applicationName }}
-
{{ app.atRiskPasswordCount }}
+ +
+
+ {{ "application" | i18n }} +
+
+ {{ "atRiskPasswords" | i18n }} +
+ +
+
{{ app.applicationName }}
+
{{ app.atRiskPasswordCount }}
+
+
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index 5aca124a46a..e47e1851099 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -32,7 +32,6 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod import { AllApplicationsComponent } from "./all-applications.component"; import { CriticalApplicationsComponent } from "./critical-applications.component"; -import { NotifiedMembersTableComponent } from "./notified-members-table.component"; import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component"; import { PasswordHealthMembersComponent } from "./password-health-members.component"; import { PasswordHealthComponent } from "./password-health.component"; @@ -59,7 +58,6 @@ export enum RiskInsightsTabType { PasswordHealthComponent, PasswordHealthMembersComponent, PasswordHealthMembersURIComponent, - NotifiedMembersTableComponent, TabsModule, DrawerComponent, DrawerBodyComponent, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts index 2d0a681460a..b563591f32f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts @@ -19,12 +19,14 @@ import { IntegrationsComponent } from "./integrations.component"; @Component({ selector: "app-header", template: "
", + standalone: false, }) class MockHeaderComponent {} @Component({ selector: "sm-new-menu", template: "
", + standalone: false, }) class MockNewMenuComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts index cdae129de4f..01a2edb5d5d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts @@ -6,6 +6,7 @@ import { Integration } from "@bitwarden/web-vault/app/admin-console/organization @Component({ selector: "sm-integrations", templateUrl: "./integrations.component.html", + standalone: false, }) export class IntegrationsComponent { private integrationsAndSdks: Integration[] = []; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts index 6a90eb2a78f..b50e586c337 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts @@ -3,6 +3,7 @@ import { Component, OnInit } from "@angular/core"; @Component({ selector: "sm-layout", templateUrl: "./layout.component.html", + standalone: false, }) export class LayoutComponent implements OnInit { ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts index 6594b71a14c..1eef528b639 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts @@ -34,6 +34,7 @@ import { CountService } from "../shared/counts/count.service"; @Component({ selector: "sm-navigation", templateUrl: "./navigation.component.html", + standalone: false, }) export class NavigationComponent implements OnInit, OnDestroy { protected readonly logo = SecretsManagerLogo; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 698448c2d06..a96f9a08919 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -88,6 +88,7 @@ type OrganizationTasks = { @Component({ selector: "sm-overview", templateUrl: "./overview.component.html", + standalone: false, }) export class OverviewComponent implements OnInit, OnDestroy { private destroy$: Subject = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts index bd68e58ea87..6b71c81f09e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts @@ -3,6 +3,7 @@ import { Component, Input } from "@angular/core"; @Component({ selector: "sm-section", templateUrl: "./section.component.html", + standalone: false, }) export class SectionComponent { @Input() open = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts index d6ca31399e1..8cdb1bb4d69 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts @@ -27,6 +27,7 @@ export interface ProjectDeleteOperation { @Component({ templateUrl: "./project-delete-dialog.component.html", + standalone: false, }) export class ProjectDeleteDialogComponent implements OnInit { formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts index c96887cc9ac..ec420d653cc 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -27,6 +27,7 @@ export interface ProjectOperation { @Component({ templateUrl: "./project-dialog.component.html", + standalone: false, }) export class ProjectDialogComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts index 159b90d5432..7523fa14a21 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -22,11 +22,13 @@ import { projectAccessGuard } from "./project-access.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts index 4a0c37cb4a9..13f80920558 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts @@ -27,6 +27,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-project-people", templateUrl: "./project-people.component.html", + standalone: false, }) export class ProjectPeopleComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts index f4950bf53f2..536739715ff 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts @@ -45,6 +45,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-project-secrets", templateUrl: "./project-secrets.component.html", + standalone: false, }) export class ProjectSecretsComponent implements OnInit { secrets$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts index d289f9f7b1f..fc3a489bce9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts @@ -25,6 +25,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-project-service-accounts", templateUrl: "./project-service-accounts.component.html", + standalone: false, }) export class ProjectServiceAccountsComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index 9e60cf0535c..2d008dd219a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -37,6 +37,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-project", templateUrl: "./project.component.html", + standalone: false, }) export class ProjectComponent implements OnInit, OnDestroy { protected project$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts index e09aaea6e2b..ea5294624af 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts @@ -40,6 +40,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-projects", templateUrl: "./projects.component.html", + standalone: false, }) export class ProjectsComponent implements OnInit { protected projects$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts index 9dfc80c9f88..6340cc42f3b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts @@ -20,6 +20,7 @@ export interface SecretDeleteOperation { @Component({ templateUrl: "./secret-delete.component.html", + standalone: false, }) export class SecretDeleteDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index 09a78e02c44..9172d44965d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -69,6 +69,7 @@ export interface SecretOperation { @Component({ templateUrl: "./secret-dialog.component.html", + standalone: false, }) export class SecretDialogComponent implements OnInit, OnDestroy { loading = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts index 7f11d69d59c..b719014a382 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts @@ -12,6 +12,7 @@ export interface SecretViewDialogParams { @Component({ templateUrl: "./secret-view-dialog.component.html", + standalone: false, }) export class SecretViewDialogComponent implements OnInit { protected loading = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts index b58173a1cc0..18ecd2e3b51 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts @@ -36,6 +36,7 @@ import { SecretService } from "./secret.service"; @Component({ selector: "sm-secrets", templateUrl: "./secrets.component.html", + standalone: false, }) export class SecretsComponent implements OnInit { protected secrets$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts index caca8c92aa8..a714729d96f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts @@ -8,6 +8,7 @@ import { AccessTokenView } from "../models/view/access-token.view"; @Component({ selector: "sm-access-list", templateUrl: "./access-list.component.html", + standalone: false, }) export class AccessListComponent { @Input() diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts index a4f0c077efd..b9643ce8fd8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts @@ -27,6 +27,7 @@ import { AccessTokenCreateDialogComponent } from "./dialogs/access-token-create- @Component({ selector: "sm-access-tokens", templateUrl: "./access-tokens.component.html", + standalone: false, }) export class AccessTokenComponent implements OnInit, OnDestroy { accessTokens$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts index 140e357edac..dfbe0a1511d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts @@ -17,6 +17,7 @@ export interface AccessTokenOperation { @Component({ templateUrl: "./access-token-create-dialog.component.html", + standalone: false, }) export class AccessTokenCreateDialogComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts index bfc120f33e2..0259b8d6e90 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts @@ -14,6 +14,7 @@ export interface AccessTokenDetails { @Component({ templateUrl: "./access-token-dialog.component.html", + standalone: false, }) export class AccessTokenDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts index 2273b42897c..891501874ff 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts @@ -33,6 +33,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic useExisting: ExpirationOptionsComponent, }, ], + standalone: false, }) export class ExpirationOptionsComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts index 96e3b58b633..e796c4758ea 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts @@ -24,6 +24,7 @@ class ServiceAccountConfig { @Component({ selector: "sm-service-account-config", templateUrl: "./config.component.html", + standalone: false, }) export class ServiceAccountConfigComponent implements OnInit, OnDestroy { identityUrl: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts index 6f30aacccca..5edc57d8c74 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts @@ -27,6 +27,7 @@ export interface ServiceAccountDeleteOperation { @Component({ templateUrl: "./service-account-delete-dialog.component.html", + standalone: false, }) export class ServiceAccountDeleteDialogComponent { formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index 241c736fb7a..815ea1dc60c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -26,6 +26,7 @@ export interface ServiceAccountOperation { @Component({ templateUrl: "./service-account-dialog.component.html", + standalone: false, }) export class ServiceAccountDialogComponent implements OnInit { protected formGroup = new FormGroup( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts index 6538ae49adb..ddaa0937e6f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts @@ -18,6 +18,7 @@ import { ServiceAccountEventLogApiService } from "./service-account-event-log-ap @Component({ selector: "sm-service-accounts-events", templateUrl: "./service-accounts-events.component.html", + standalone: false, }) export class ServiceAccountEventsComponent extends BaseEventsComponent diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts index 4301b5ae81a..e0bcad8d6e9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts @@ -22,11 +22,13 @@ import { serviceAccountAccessGuard } from "./service-account-access.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts index a57251c4f77..4449757167d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts @@ -28,6 +28,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-service-account-people", templateUrl: "./service-account-people.component.html", + standalone: false, }) export class ServiceAccountPeopleComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts index 34a48af3055..af334b22c63 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts @@ -25,6 +25,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-service-account-projects", templateUrl: "./service-account-projects.component.html", + standalone: false, }) export class ServiceAccountProjectsComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts index 74465e8ecef..5eb074e3e99 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts @@ -18,6 +18,7 @@ import { ServiceAccountService } from "./service-account.service"; @Component({ selector: "sm-service-account", templateUrl: "./service-account.component.html", + standalone: false, }) export class ServiceAccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts index cbffc80aa05..47d5dd63806 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts @@ -16,6 +16,7 @@ import { @Component({ selector: "sm-service-accounts-list", templateUrl: "./service-accounts-list.component.html", + standalone: false, }) export class ServiceAccountsListComponent implements OnDestroy { protected dataSource = new TableDataSource(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts index 7deaefae823..2813ece001f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts @@ -31,6 +31,7 @@ import { ServiceAccountService } from "./service-account.service"; @Component({ selector: "sm-service-accounts", templateUrl: "./service-accounts.component.html", + standalone: false, }) export class ServiceAccountsComponent implements OnInit { protected serviceAccounts$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts index 2576ca8e629..0bed0355a8c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts @@ -12,6 +12,7 @@ export interface SecretsManagerImportErrorDialogOperation { @Component({ templateUrl: "./sm-import-error-dialog.component.html", + standalone: false, }) export class SecretsManagerImportErrorDialogComponent { errorLines: SecretsManagerImportErrorLine[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts index 266962c8268..c2b726803c5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts @@ -29,6 +29,7 @@ type ExportFormat = { @Component({ selector: "sm-export", templateUrl: "./sm-export.component.html", + standalone: false, }) export class SecretsManagerExportComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts index 262f1a6b161..65075d12bf6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts @@ -21,6 +21,7 @@ import { SecretsManagerPortingApiService } from "../services/sm-porting-api.serv @Component({ selector: "sm-import", templateUrl: "./sm-import.component.html", + standalone: false, }) export class SecretsManagerImportComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts index 34de4e4860b..fba3ff03ee0 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts @@ -30,6 +30,7 @@ import { ApPermissionEnum } from "./models/enums/ap-permission.enum"; multi: true, }, ], + standalone: false, }) export class AccessPolicySelectorComponent implements ControlValueAccessor, OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts index 935ee1c8518..9d2a3715e16 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts @@ -25,6 +25,7 @@ export enum BulkConfirmationResult { @Component({ selector: "sm-bulk-confirmation-dialog", templateUrl: "./bulk-confirmation-dialog.component.html", + standalone: false, }) export class BulkConfirmationDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts index 0a00c7f0431..fc7890f1654 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts @@ -20,6 +20,7 @@ export class BulkOperationStatus { @Component({ templateUrl: "./bulk-status-dialog.component.html", + standalone: false, }) export class BulkStatusDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts index b0a481d077d..18823130d22 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts @@ -29,6 +29,7 @@ import { @Component({ selector: "sm-new-menu", templateUrl: "./new-menu.component.html", + standalone: false, }) export class NewMenuComponent implements OnInit, OnDestroy { private organizationId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts index 2eb9d6017b7..89baf969c34 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts @@ -12,6 +12,7 @@ import { Icon, Icons } from "@bitwarden/components"; @Component({ templateUrl: "./org-suspended.component.html", + standalone: false, }) export class OrgSuspendedComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts index 0ca5ba09074..9774172cd4b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts @@ -13,6 +13,7 @@ import { ProjectListView } from "../models/view/project-list.view"; @Component({ selector: "sm-projects-list", templateUrl: "./projects-list.component.html", + standalone: false, }) export class ProjectsListComponent { @Input() diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts index 37b9524238f..a7ee818a01f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts @@ -15,6 +15,7 @@ import { SecretService } from "../secrets/secret.service"; @Component({ selector: "sm-secrets-list", templateUrl: "./secrets-list.component.html", + standalone: false, }) export class SecretsListComponent implements OnDestroy { protected dataSource = new TableDataSource(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts index 7c4fff4d3b3..29f9a85250c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts @@ -15,6 +15,7 @@ export interface SecretHardDeleteOperation { @Component({ templateUrl: "./secret-hard-delete.component.html", + standalone: false, }) export class SecretHardDeleteDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts index 6b5efe5e133..712757445be 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts @@ -15,6 +15,7 @@ export interface SecretRestoreOperation { @Component({ templateUrl: "./secret-restore.component.html", + standalone: false, }) export class SecretRestoreDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts index 3a21dbe3b68..4392ae8b1bb 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "sm-trash", templateUrl: "./trash.component.html", + standalone: false, }) export class TrashComponent implements OnInit { secrets$: Observable; diff --git a/bitwarden_license/bit-web/src/main.ts b/bitwarden_license/bit-web/src/main.ts index b202a170d26..572d3968f3d 100644 --- a/bitwarden_license/bit-web/src/main.ts +++ b/bitwarden_license/bit-web/src/main.ts @@ -1,10 +1,6 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import "bootstrap"; -import "jquery"; -import "popper.js"; - import { AppModule } from "./app/app.module"; if (process.env.NODE_ENV === "production") { diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 5f39966468f..8ae90705f92 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -50,9 +50,7 @@ export class CollectionsComponent implements OnInit { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.cipherDomain = await this.loadCipher(activeUserId); this.collectionIds = this.loadCipherCollections(); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); this.collections = await this.loadCollections(); this.collections.forEach((c) => ((c as any).checked = false)); diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts index 16a249dda97..e6438b3e634 100644 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -57,6 +57,7 @@ export interface EnvironmentSelectorRouteData { transition("* => void", animate("100ms linear", style({ opacity: 0 }))), ]), ], + standalone: false, }) export class EnvironmentSelectorComponent implements OnInit, OnDestroy { @Output() onOpenSelfHostedSettings = new EventEmitter(); diff --git a/libs/angular/src/auth/components/two-factor-icon.component.ts b/libs/angular/src/auth/components/two-factor-icon.component.ts index c75078e413a..0e811595bea 100644 --- a/libs/angular/src/auth/components/two-factor-icon.component.ts +++ b/libs/angular/src/auth/components/two-factor-icon.component.ts @@ -10,6 +10,7 @@ import { WebAuthnIcon } from "../icons/webauthn.icon"; @Component({ selector: "auth-two-factor-icon", templateUrl: "./two-factor-icon.component.html", + standalone: false, }) export class TwoFactorIconComponent { @Input() provider: any; diff --git a/libs/angular/src/auth/components/user-verification.component.ts b/libs/angular/src/auth/components/user-verification.component.ts index 408d8403b88..6f5021340c7 100644 --- a/libs/angular/src/auth/components/user-verification.component.ts +++ b/libs/angular/src/auth/components/user-verification.component.ts @@ -22,6 +22,7 @@ import { KeyService } from "@bitwarden/key-management"; */ @Directive({ selector: "app-user-verification", + standalone: false, }) export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy { private _invalidSecret = false; diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts index 871895c2ede..a6f1db54241 100644 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts @@ -48,6 +48,7 @@ type PayPalConfig = { @Component({ templateUrl: "./add-account-credit-dialog.component.html", + standalone: false, }) export class AddAccountCreditDialogComponent implements OnInit { @ViewChild("payPalForm", { read: ElementRef, static: true }) payPalForm: ElementRef; diff --git a/libs/angular/src/billing/components/invoices/invoices.component.ts b/libs/angular/src/billing/components/invoices/invoices.component.ts index 8984c1afe65..fc3352048d6 100644 --- a/libs/angular/src/billing/components/invoices/invoices.component.ts +++ b/libs/angular/src/billing/components/invoices/invoices.component.ts @@ -11,6 +11,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil @Component({ selector: "app-invoices", templateUrl: "./invoices.component.html", + standalone: false, }) export class InvoicesComponent implements OnInit { @Input() startWith?: InvoicesResponse; diff --git a/libs/angular/src/billing/components/invoices/no-invoices.component.ts b/libs/angular/src/billing/components/invoices/no-invoices.component.ts index fffaaf65cfe..987650d1c83 100644 --- a/libs/angular/src/billing/components/invoices/no-invoices.component.ts +++ b/libs/angular/src/billing/components/invoices/no-invoices.component.ts @@ -30,6 +30,7 @@ const partnerTrustIcon = svgIcon`

{{ "noInvoicesToList" | i18n }}

`, + standalone: false, }) export class NoInvoicesComponent { icon = partnerTrustIcon; diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts index e966b4e0a75..7088d8edfcc 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -11,6 +11,7 @@ import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/model @Component({ selector: "app-manage-tax-information", templateUrl: "./manage-tax-information.component.html", + standalone: false, }) export class ManageTaxInformationComponent implements OnInit, OnDestroy { @Input() startWith: TaxInformation; diff --git a/libs/angular/src/billing/directives/not-premium.directive.ts b/libs/angular/src/billing/directives/not-premium.directive.ts index 5a1c636c009..41d62bb773e 100644 --- a/libs/angular/src/billing/directives/not-premium.directive.ts +++ b/libs/angular/src/billing/directives/not-premium.directive.ts @@ -9,6 +9,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs */ @Directive({ selector: "[appNotPremium]", + standalone: false, }) export class NotPremiumDirective implements OnInit { constructor( diff --git a/libs/angular/src/components/callout.component.ts b/libs/angular/src/components/callout.component.ts index 1e5285cc3a9..215de49f676 100644 --- a/libs/angular/src/components/callout.component.ts +++ b/libs/angular/src/components/callout.component.ts @@ -12,6 +12,7 @@ import { CalloutTypes } from "@bitwarden/components"; @Component({ selector: "app-callout", templateUrl: "callout.component.html", + standalone: false, }) export class DeprecatedCalloutComponent implements OnInit { @Input() type: CalloutTypes = "info"; diff --git a/libs/angular/src/components/modal/dynamic-modal.component.ts b/libs/angular/src/components/modal/dynamic-modal.component.ts index ccbfa19868f..77491193916 100644 --- a/libs/angular/src/components/modal/dynamic-modal.component.ts +++ b/libs/angular/src/components/modal/dynamic-modal.component.ts @@ -18,6 +18,7 @@ import { ModalRef } from "./modal.ref"; @Component({ selector: "app-modal", template: "", + standalone: false, }) export class DynamicModalComponent implements AfterViewInit, OnDestroy { componentRef: ComponentRef; diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index e785441b8e4..198cc7dc3a5 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -76,9 +76,7 @@ export class ShareComponent implements OnInit, OnDestroy { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - this.cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipherDomain, activeUserId); } filterCollections() { @@ -105,9 +103,7 @@ export class ShareComponent implements OnInit, OnDestroy { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - const cipherView = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipherDomain, activeUserId); const orgs = await firstValueFrom(this.organizations$); const orgName = orgs.find((o) => o.id === this.organizationId)?.name ?? this.i18nService.t("organization"); diff --git a/libs/angular/src/directives/a11y-invalid.directive.ts b/libs/angular/src/directives/a11y-invalid.directive.ts index 60580fcb60e..032c08d5332 100644 --- a/libs/angular/src/directives/a11y-invalid.directive.ts +++ b/libs/angular/src/directives/a11y-invalid.directive.ts @@ -6,6 +6,7 @@ import { Subscription } from "rxjs"; @Directive({ selector: "[appA11yInvalid]", + standalone: false, }) export class A11yInvalidDirective implements OnDestroy, OnInit { private sub: Subscription; diff --git a/libs/angular/src/directives/api-action.directive.ts b/libs/angular/src/directives/api-action.directive.ts index a07ab7d0413..85ba8a7489c 100644 --- a/libs/angular/src/directives/api-action.directive.ts +++ b/libs/angular/src/directives/api-action.directive.ts @@ -15,6 +15,7 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid */ @Directive({ selector: "[appApiAction]", + standalone: false, }) export class ApiActionDirective implements OnChanges { @Input() appApiAction: Promise; diff --git a/libs/angular/src/directives/box-row.directive.ts b/libs/angular/src/directives/box-row.directive.ts index 81fb93596f2..d36dcb7ff88 100644 --- a/libs/angular/src/directives/box-row.directive.ts +++ b/libs/angular/src/directives/box-row.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, HostListener, OnInit } from "@angular/core"; @Directive({ selector: "[appBoxRow]", + standalone: false, }) export class BoxRowDirective implements OnInit { el: HTMLElement = null; diff --git a/libs/angular/src/directives/copy-text.directive.ts b/libs/angular/src/directives/copy-text.directive.ts index de26973e2c9..0f9018e19ad 100644 --- a/libs/angular/src/directives/copy-text.directive.ts +++ b/libs/angular/src/directives/copy-text.directive.ts @@ -7,6 +7,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Directive({ selector: "[appCopyText]", + standalone: false, }) export class CopyTextDirective { constructor( diff --git a/libs/angular/src/directives/fallback-src.directive.ts b/libs/angular/src/directives/fallback-src.directive.ts index 600782f3404..f1225245912 100644 --- a/libs/angular/src/directives/fallback-src.directive.ts +++ b/libs/angular/src/directives/fallback-src.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, HostListener, Input } from "@angular/core"; @Directive({ selector: "[appFallbackSrc]", + standalone: false, }) export class FallbackSrcDirective { @Input("appFallbackSrc") appFallbackSrc: string; diff --git a/libs/angular/src/directives/if-feature.directive.ts b/libs/angular/src/directives/if-feature.directive.ts index 0186592d2d0..aa10c9e8081 100644 --- a/libs/angular/src/directives/if-feature.directive.ts +++ b/libs/angular/src/directives/if-feature.directive.ts @@ -14,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" */ @Directive({ selector: "[appIfFeature]", + standalone: false, }) export class IfFeatureDirective implements OnInit { /** diff --git a/libs/angular/src/directives/input-strip-spaces.directive.ts b/libs/angular/src/directives/input-strip-spaces.directive.ts index 3b8fee851c0..1718aaa49d6 100644 --- a/libs/angular/src/directives/input-strip-spaces.directive.ts +++ b/libs/angular/src/directives/input-strip-spaces.directive.ts @@ -5,6 +5,7 @@ import { NgControl } from "@angular/forms"; @Directive({ selector: "input[appInputStripSpaces]", + standalone: false, }) export class InputStripSpacesDirective { constructor( diff --git a/libs/angular/src/directives/input-verbatim.directive.ts b/libs/angular/src/directives/input-verbatim.directive.ts index deecae624f1..7bd18b12659 100644 --- a/libs/angular/src/directives/input-verbatim.directive.ts +++ b/libs/angular/src/directives/input-verbatim.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; @Directive({ selector: "[appInputVerbatim]", + standalone: false, }) export class InputVerbatimDirective implements OnInit { @Input() set appInputVerbatim(condition: boolean | string) { diff --git a/libs/angular/src/directives/launch-click.directive.ts b/libs/angular/src/directives/launch-click.directive.ts index e748afabf49..b270dbba5e3 100644 --- a/libs/angular/src/directives/launch-click.directive.ts +++ b/libs/angular/src/directives/launch-click.directive.ts @@ -5,6 +5,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Directive({ selector: "[appLaunchClick]", + standalone: false, }) export class LaunchClickDirective { constructor(private platformUtilsService: PlatformUtilsService) {} diff --git a/libs/angular/src/directives/stop-click.directive.ts b/libs/angular/src/directives/stop-click.directive.ts index 0e88dde33a4..58ae8082aa7 100644 --- a/libs/angular/src/directives/stop-click.directive.ts +++ b/libs/angular/src/directives/stop-click.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostListener } from "@angular/core"; @Directive({ selector: "[appStopClick]", + standalone: false, }) export class StopClickDirective { @HostListener("click", ["$event"]) onClick($event: MouseEvent) { diff --git a/libs/angular/src/directives/stop-prop.directive.ts b/libs/angular/src/directives/stop-prop.directive.ts index 8393e799038..6ea9bc87bec 100644 --- a/libs/angular/src/directives/stop-prop.directive.ts +++ b/libs/angular/src/directives/stop-prop.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostListener } from "@angular/core"; @Directive({ selector: "[appStopProp]", + standalone: false, }) export class StopPropDirective { @HostListener("click", ["$event"]) onClick($event: MouseEvent) { diff --git a/libs/angular/src/directives/true-false-value.directive.ts b/libs/angular/src/directives/true-false-value.directive.ts index dab88567a5c..5d25ac2a385 100644 --- a/libs/angular/src/directives/true-false-value.directive.ts +++ b/libs/angular/src/directives/true-false-value.directive.ts @@ -11,6 +11,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; multi: true, }, ], + standalone: false, }) export class TrueFalseValueDirective implements ControlValueAccessor { @Input() trueValue: boolean | string = true; diff --git a/libs/angular/src/pipes/color-password-count.pipe.ts b/libs/angular/src/pipes/color-password-count.pipe.ts index ed94e6dadff..61be6db2e2c 100644 --- a/libs/angular/src/pipes/color-password-count.pipe.ts +++ b/libs/angular/src/pipes/color-password-count.pipe.ts @@ -7,7 +7,10 @@ import { ColorPasswordPipe } from "./color-password.pipe"; /* An updated pipe that extends ColourPasswordPipe to include a character count */ -@Pipe({ name: "colorPasswordCount" }) +@Pipe({ + name: "colorPasswordCount", + standalone: false, +}) export class ColorPasswordCountPipe extends ColorPasswordPipe { transform(password: string) { const template = (character: string, type: string, index: number) => diff --git a/libs/angular/src/pipes/color-password.pipe.ts b/libs/angular/src/pipes/color-password.pipe.ts index 8407eddd9ca..6bb7bd4120d 100644 --- a/libs/angular/src/pipes/color-password.pipe.ts +++ b/libs/angular/src/pipes/color-password.pipe.ts @@ -6,7 +6,10 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; An updated pipe that sanitizes HTML, highlights numbers and special characters (in different colors each) and handles Unicode / Emoji characters correctly. */ -@Pipe({ name: "colorPassword" }) +@Pipe({ + name: "colorPassword", + standalone: false, +}) export class ColorPasswordPipe implements PipeTransform { transform(password: string) { const template = (character: string, type: string) => diff --git a/libs/angular/src/pipes/credit-card-number.pipe.ts b/libs/angular/src/pipes/credit-card-number.pipe.ts index 11500ef9510..80718612d35 100644 --- a/libs/angular/src/pipes/credit-card-number.pipe.ts +++ b/libs/angular/src/pipes/credit-card-number.pipe.ts @@ -28,7 +28,10 @@ const numberFormats: Record = { Other: [{ cardLength: 16, blocks: [4, 4, 4, 4] }], }; -@Pipe({ name: "creditCardNumber" }) +@Pipe({ + name: "creditCardNumber", + standalone: false, +}) export class CreditCardNumberPipe implements PipeTransform { transform(creditCardNumber: string, brand: string): string { let rules = numberFormats[brand]; diff --git a/libs/angular/src/pipes/search-ciphers.pipe.ts b/libs/angular/src/pipes/search-ciphers.pipe.ts index 04349e9842a..cbb595280af 100644 --- a/libs/angular/src/pipes/search-ciphers.pipe.ts +++ b/libs/angular/src/pipes/search-ciphers.pipe.ts @@ -4,6 +4,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @Pipe({ name: "searchCiphers", + standalone: false, }) export class SearchCiphersPipe implements PipeTransform { transform(ciphers: CipherView[], searchText: string, deleted = false): CipherView[] { diff --git a/libs/angular/src/pipes/search.pipe.ts b/libs/angular/src/pipes/search.pipe.ts index 9f640a5b48e..2c652bc6382 100644 --- a/libs/angular/src/pipes/search.pipe.ts +++ b/libs/angular/src/pipes/search.pipe.ts @@ -6,6 +6,7 @@ type PropertyValueFunction = (item: T) => { toString: () => string }; @Pipe({ name: "search", + standalone: false, }) export class SearchPipe implements PipeTransform { transform( diff --git a/libs/angular/src/pipes/user-name.pipe.ts b/libs/angular/src/pipes/user-name.pipe.ts index 29b884a3115..4c295c7ef13 100644 --- a/libs/angular/src/pipes/user-name.pipe.ts +++ b/libs/angular/src/pipes/user-name.pipe.ts @@ -9,6 +9,7 @@ export interface User { @Pipe({ name: "userName", + standalone: false, }) export class UserNamePipe implements PipeTransform { transform(user?: User): string { diff --git a/libs/angular/src/pipes/user-type.pipe.ts b/libs/angular/src/pipes/user-type.pipe.ts index 109abc3eeff..d9b5faa2ea9 100644 --- a/libs/angular/src/pipes/user-type.pipe.ts +++ b/libs/angular/src/pipes/user-type.pipe.ts @@ -5,6 +5,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Pipe({ name: "userType", + standalone: false, }) export class UserTypePipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/libs/angular/src/platform/pipes/ellipsis.pipe.ts b/libs/angular/src/platform/pipes/ellipsis.pipe.ts index dd271f94627..a7050b2f037 100644 --- a/libs/angular/src/platform/pipes/ellipsis.pipe.ts +++ b/libs/angular/src/platform/pipes/ellipsis.pipe.ts @@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "ellipsis", + standalone: false, }) /** * @deprecated Use the tailwind class 'tw-truncate' instead diff --git a/libs/angular/src/platform/pipes/fingerprint.pipe.ts b/libs/angular/src/platform/pipes/fingerprint.pipe.ts index 8f1a07cfd6b..90289ee212b 100644 --- a/libs/angular/src/platform/pipes/fingerprint.pipe.ts +++ b/libs/angular/src/platform/pipes/fingerprint.pipe.ts @@ -5,6 +5,7 @@ import { KeyService } from "@bitwarden/key-management"; @Pipe({ name: "fingerprint", + standalone: false, }) export class FingerprintPipe { constructor(private keyService: KeyService) {} diff --git a/libs/angular/src/platform/pipes/i18n.pipe.ts b/libs/angular/src/platform/pipes/i18n.pipe.ts index a6fdbc78255..a1c6122ba16 100644 --- a/libs/angular/src/platform/pipes/i18n.pipe.ts +++ b/libs/angular/src/platform/pipes/i18n.pipe.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic */ @Pipe({ name: "i18n", + standalone: false, }) export class I18nPipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 470115ae3f0..a8638efba18 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -27,6 +27,8 @@ import { TwoFactorAuthComponentService, TwoFactorAuthEmailComponentService, TwoFactorAuthWebAuthnComponentService, + ChangePasswordService, + DefaultChangePasswordService, } from "@bitwarden/auth/angular"; import { AuthRequestApiService, @@ -263,6 +265,7 @@ import { InternalSendService, SendService as SendServiceAbstraction, } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -281,6 +284,7 @@ import { DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -509,6 +513,7 @@ const safeProviders: SafeProvider[] = [ stateProvider: StateProvider, accountService: AccountServiceAbstraction, logService: LogService, + cipherEncryptionService: CipherEncryptionService, ) => new CipherService( keyService, @@ -525,6 +530,7 @@ const safeProviders: SafeProvider[] = [ stateProvider, accountService, logService, + cipherEncryptionService, ), deps: [ KeyService, @@ -541,6 +547,7 @@ const safeProviders: SafeProvider[] = [ StateProvider, AccountServiceAbstraction, LogService, + CipherEncryptionService, ], }), safeProvider({ @@ -1079,7 +1086,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: OrganizationSponsorshipApiServiceAbstraction, useClass: OrganizationSponsorshipApiService, - deps: [ApiServiceAbstraction], + deps: [ApiServiceAbstraction, PlatformUtilsServiceAbstraction], }), safeProvider({ provide: OrganizationBillingApiServiceAbstraction, @@ -1528,6 +1535,20 @@ const safeProviders: SafeProvider[] = [ useClass: MasterPasswordApiService, deps: [ApiServiceAbstraction, LogService], }), + safeProvider({ + provide: CipherEncryptionService, + useClass: DefaultCipherEncryptionService, + deps: [SdkService, LogService], + }), + safeProvider({ + provide: ChangePasswordService, + useClass: DefaultChangePasswordService, + deps: [ + KeyService, + MasterPasswordApiServiceAbstraction, + InternalMasterPasswordServiceAbstraction, + ], + }), ]; @NgModule({ diff --git a/libs/angular/src/tools/password-strength/password-strength.component.ts b/libs/angular/src/tools/password-strength/password-strength.component.ts index d23225b7c0c..ca9892d9c6c 100644 --- a/libs/angular/src/tools/password-strength/password-strength.component.ts +++ b/libs/angular/src/tools/password-strength/password-strength.component.ts @@ -16,6 +16,7 @@ export interface PasswordColorText { @Component({ selector: "app-password-strength", templateUrl: "password-strength.component.html", + standalone: false, }) export class PasswordStrengthComponent implements OnChanges { @Input() showText = false; diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index b9defa8383d..b04adc1fdfb 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -269,9 +269,7 @@ export class AddEditComponent implements OnInit, OnDestroy { if (this.cipher == null) { if (this.editMode) { const cipher = await this.loadCipher(activeUserId); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); // Adjust Cipher Name if Cloning if (this.cloneMode) { diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 9e9450c587e..e4b01d3aac1 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -9,13 +9,13 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -56,6 +56,7 @@ export class AttachmentsComponent implements OnInit { protected billingAccountProfileStateService: BillingAccountProfileStateService, protected accountService: AccountService, protected toastService: ToastService, + protected configService: ConfigService, ) {} async ngOnInit() { @@ -88,9 +89,7 @@ export class AttachmentsComponent implements OnInit { const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.formPromise = this.saveCipherAttachment(files[0], activeUserId); this.cipherDomain = await this.formPromise; - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); this.toastService.showToast({ variant: "success", title: null, @@ -130,9 +129,7 @@ export class AttachmentsComponent implements OnInit { const updatedCipher = await this.deletePromises[attachment.id]; const cipher = new Cipher(updatedCipher); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); this.toastService.showToast({ variant: "success", @@ -197,12 +194,14 @@ export class AttachmentsComponent implements OnInit { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipherDomain.id as CipherId, + attachment, + response, + activeUserId, + ); + this.fileDownloadService.download({ fileName: attachment.fileName, blobData: decBuf, @@ -228,9 +227,7 @@ export class AttachmentsComponent implements OnInit { protected async init() { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.cipherDomain = await this.loadCipher(activeUserId); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); const canAccessPremium = await firstValueFrom( this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), @@ -276,15 +273,17 @@ export class AttachmentsComponent implements OnInit { try { // 2. Resave - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getUserId), ); + + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipherDomain.id as CipherId, + attachment, + response, + activeUserId, + ); + this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( this.cipherDomain, attachment.fileName, @@ -292,9 +291,7 @@ export class AttachmentsComponent implements OnInit { activeUserId, admin, ); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); // 3. Delete old this.deletePromises[attachment.id] = this.deleteCipherAttachment( diff --git a/libs/angular/src/vault/components/icon.component.ts b/libs/angular/src/vault/components/icon.component.ts index 248378bf5ee..fd178db23b6 100644 --- a/libs/angular/src/vault/components/icon.component.ts +++ b/libs/angular/src/vault/components/icon.component.ts @@ -19,6 +19,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; selector: "app-vault-icon", templateUrl: "icon.component.html", changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class IconComponent { /** diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index 4df9f4bd24d..acb89b82191 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -42,9 +42,7 @@ export class PasswordHistoryComponent implements OnInit { protected async init() { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipher = await this.cipherService.get(this.cipherId, activeUserId); - const decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(cipher, activeUserId); this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; } } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 9d5a8fe9e62..8915cb6b671 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -34,13 +34,13 @@ import { EventType } from "@bitwarden/common/enums"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { CollectionId, UserId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -137,6 +137,7 @@ export class ViewComponent implements OnDestroy, OnInit { private billingAccountProfileStateService: BillingAccountProfileStateService, protected toastService: ToastService, private cipherAuthorizationService: CipherAuthorizationService, + protected configService: ConfigService, ) {} ngOnInit() { @@ -458,19 +459,19 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipher.id as CipherId, + attachment, + response, + activeUserId, + ); + this.fileDownloadService.download({ fileName: attachment.fileName, blobData: decBuf, }); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { + } catch { this.toastService.showToast({ variant: "error", title: null, diff --git a/libs/auth/src/angular/change-password/change-password.component.html b/libs/auth/src/angular/change-password/change-password.component.html new file mode 100644 index 00000000000..fff873225be --- /dev/null +++ b/libs/auth/src/angular/change-password/change-password.component.html @@ -0,0 +1,20 @@ +@if (initializing) { + + {{ "loading" | i18n }} +} @else { + + +} diff --git a/libs/auth/src/angular/change-password/change-password.component.ts b/libs/auth/src/angular/change-password/change-password.component.ts new file mode 100644 index 00000000000..51c4d03d16f --- /dev/null +++ b/libs/auth/src/angular/change-password/change-password.component.ts @@ -0,0 +1,110 @@ +import { Component, Input, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { SyncService } from "@bitwarden/common/platform/sync"; +import { UserId } from "@bitwarden/common/types/guid"; +import { ToastService } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { + InputPasswordComponent, + InputPasswordFlow, +} from "../input-password/input-password.component"; +import { PasswordInputResult } from "../input-password/password-input-result"; + +import { ChangePasswordService } from "./change-password.service.abstraction"; + +@Component({ + standalone: true, + selector: "auth-change-password", + templateUrl: "change-password.component.html", + imports: [InputPasswordComponent, I18nPipe], +}) +export class ChangePasswordComponent implements OnInit { + @Input() inputPasswordFlow: InputPasswordFlow = InputPasswordFlow.ChangePassword; + + activeAccount: Account | null = null; + email?: string; + userId?: UserId; + masterPasswordPolicyOptions?: MasterPasswordPolicyOptions; + initializing = true; + submitting = false; + + constructor( + private accountService: AccountService, + private changePasswordService: ChangePasswordService, + private i18nService: I18nService, + private messagingService: MessagingService, + private policyService: PolicyService, + private toastService: ToastService, + private syncService: SyncService, + ) {} + + async ngOnInit() { + this.activeAccount = await firstValueFrom(this.accountService.activeAccount$); + this.userId = this.activeAccount?.id; + this.email = this.activeAccount?.email; + + if (!this.userId) { + throw new Error("userId not found"); + } + + this.masterPasswordPolicyOptions = await firstValueFrom( + this.policyService.masterPasswordPolicyOptions$(this.userId), + ); + + this.initializing = false; + } + + async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { + this.submitting = true; + + try { + if (passwordInputResult.rotateUserKey) { + if (this.activeAccount == null) { + throw new Error("activeAccount not found"); + } + + if (passwordInputResult.currentPassword == null) { + throw new Error("currentPassword not found"); + } + + await this.syncService.fullSync(true); + + await this.changePasswordService.rotateUserKeyMasterPasswordAndEncryptedData( + passwordInputResult.currentPassword, + passwordInputResult.newPassword, + this.activeAccount, + passwordInputResult.newPasswordHint, + ); + } else { + if (!this.userId) { + throw new Error("userId not found"); + } + + await this.changePasswordService.changePassword(passwordInputResult, this.userId); + + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("masterPasswordChanged"), + message: this.i18nService.t("masterPasswordChangedDesc"), + }); + + this.messagingService.send("logout"); + } + } catch { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("errorOccurred"), + }); + } finally { + this.submitting = false; + } + } +} diff --git a/libs/auth/src/angular/change-password/change-password.service.abstraction.ts b/libs/auth/src/angular/change-password/change-password.service.abstraction.ts new file mode 100644 index 00000000000..b036db439f8 --- /dev/null +++ b/libs/auth/src/angular/change-password/change-password.service.abstraction.ts @@ -0,0 +1,36 @@ +import { PasswordInputResult } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +export abstract class ChangePasswordService { + /** + * Creates a new user key and re-encrypts all required data with it. + * - does so by calling the underlying method on the `UserKeyRotationService` + * - implemented in Web only + * + * @param currentPassword the current password + * @param newPassword the new password + * @param user the user account + * @param newPasswordHint the new password hint + * @throws if called from a non-Web client + */ + abstract rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword: string, + newPassword: string, + user: Account, + newPasswordHint: string, + ): Promise; + + /** + * Changes the user's password and re-encrypts the user key with the `newMasterKey`. + * - Specifically, this method uses credentials from the `passwordInputResult` to: + * 1. Decrypt the user key with the `currentMasterKey` + * 2. Re-encrypt that user key with the `newMasterKey`, resulting in a `newMasterKeyEncryptedUserKey` + * 3. Build a `PasswordRequest` object that gets POSTed to `"/accounts/password"` + * + * @param passwordInputResult credentials object received from the `InputPasswordComponent` + * @param userId the `userId` + * @throws if the `userId`, `currentMasterKey`, or `currentServerMasterKeyHash` is not found + */ + abstract changePassword(passwordInputResult: PasswordInputResult, userId: UserId): Promise; +} diff --git a/libs/auth/src/angular/change-password/default-change-password.service.spec.ts b/libs/auth/src/angular/change-password/default-change-password.service.spec.ts new file mode 100644 index 00000000000..ab993859d70 --- /dev/null +++ b/libs/auth/src/angular/change-password/default-change-password.service.spec.ts @@ -0,0 +1,177 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { MasterKey, UserKey } from "@bitwarden/common/types/key"; +import { KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; + +import { PasswordInputResult } from "../input-password/password-input-result"; + +import { ChangePasswordService } from "./change-password.service.abstraction"; +import { DefaultChangePasswordService } from "./default-change-password.service"; + +describe("DefaultChangePasswordService", () => { + let keyService: MockProxy; + let masterPasswordApiService: MockProxy; + let masterPasswordService: MockProxy; + + let sut: ChangePasswordService; + + const userId = "userId" as UserId; + + const user: Account = { + id: userId, + email: "email", + emailVerified: false, + name: "name", + }; + + const passwordInputResult: PasswordInputResult = { + currentMasterKey: new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey, + currentServerMasterKeyHash: "currentServerMasterKeyHash", + + newPassword: "newPassword", + newPasswordHint: "newPasswordHint", + newMasterKey: new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey, + newServerMasterKeyHash: "newServerMasterKeyHash", + newLocalMasterKeyHash: "newLocalMasterKeyHash", + + kdfConfig: new PBKDF2KdfConfig(), + }; + + const decryptedUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; + const newMasterKeyEncryptedUserKey: [UserKey, EncString] = [ + decryptedUserKey, + { encryptedString: "newMasterKeyEncryptedUserKey" } as EncString, + ]; + + beforeEach(() => { + keyService = mock(); + masterPasswordApiService = mock(); + masterPasswordService = mock(); + + sut = new DefaultChangePasswordService( + keyService, + masterPasswordApiService, + masterPasswordService, + ); + + masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(decryptedUserKey); + keyService.encryptUserKeyWithMasterKey.mockResolvedValue(newMasterKeyEncryptedUserKey); + }); + + describe("changePassword()", () => { + it("should call the postPassword() API method with a the correct PasswordRequest credentials", async () => { + // Act + await sut.changePassword(passwordInputResult, userId); + + // Assert + expect(masterPasswordApiService.postPassword).toHaveBeenCalledWith( + expect.objectContaining({ + masterPasswordHash: passwordInputResult.currentServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, + newMasterPasswordHash: passwordInputResult.newServerMasterKeyHash, + key: newMasterKeyEncryptedUserKey[1].encryptedString, + }), + ); + }); + + it("should call decryptUserKeyWithMasterKey and encryptUserKeyWithMasterKey", async () => { + // Act + await sut.changePassword(passwordInputResult, userId); + + // Assert + expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith( + passwordInputResult.currentMasterKey, + userId, + ); + expect(keyService.encryptUserKeyWithMasterKey).toHaveBeenCalledWith( + passwordInputResult.newMasterKey, + decryptedUserKey, + ); + }); + + it("should throw if a userId was not found", async () => { + // Arrange + const userId: null = null; + + // Act + const testFn = sut.changePassword(passwordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow("userId not found"); + }); + + it("should throw if a currentMasterKey was not found", async () => { + // Arrange + const incorrectPasswordInputResult = { ...passwordInputResult }; + incorrectPasswordInputResult.currentMasterKey = null; + + // Act + const testFn = sut.changePassword(incorrectPasswordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow( + "currentMasterKey or currentServerMasterKeyHash not found", + ); + }); + + it("should throw if a currentServerMasterKeyHash was not found", async () => { + // Arrange + const incorrectPasswordInputResult = { ...passwordInputResult }; + incorrectPasswordInputResult.currentServerMasterKeyHash = null; + + // Act + const testFn = sut.changePassword(incorrectPasswordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow( + "currentMasterKey or currentServerMasterKeyHash not found", + ); + }); + + it("should throw an error if user key decryption fails", async () => { + // Arrange + masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(null); + + // Act + const testFn = sut.changePassword(passwordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow("Could not decrypt user key"); + }); + + it("should throw an error if postPassword() fails", async () => { + // Arrange + masterPasswordApiService.postPassword.mockRejectedValueOnce(new Error("error")); + + // Act + const testFn = sut.changePassword(passwordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow("Could not change password"); + expect(masterPasswordApiService.postPassword).toHaveBeenCalled(); + }); + }); + + describe("rotateUserKeyMasterPasswordAndEncryptedData()", () => { + it("should throw an error (the method is only implemented in Web)", async () => { + // Act + const testFn = sut.rotateUserKeyMasterPasswordAndEncryptedData( + "currentPassword", + "newPassword", + user, + "newPasswordHint", + ); + + // Assert + await expect(testFn).rejects.toThrow( + "rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web", + ); + }); + }); +}); diff --git a/libs/auth/src/angular/change-password/default-change-password.service.ts b/libs/auth/src/angular/change-password/default-change-password.service.ts new file mode 100644 index 00000000000..315f979aad9 --- /dev/null +++ b/libs/auth/src/angular/change-password/default-change-password.service.ts @@ -0,0 +1,59 @@ +import { PasswordInputResult, ChangePasswordService } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; +import { KeyService } from "@bitwarden/key-management"; + +export class DefaultChangePasswordService implements ChangePasswordService { + constructor( + protected keyService: KeyService, + protected masterPasswordApiService: MasterPasswordApiService, + protected masterPasswordService: InternalMasterPasswordServiceAbstraction, + ) {} + + async rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword: string, + newPassword: string, + user: Account, + hint: string, + ): Promise { + throw new Error("rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web"); + } + + async changePassword(passwordInputResult: PasswordInputResult, userId: UserId) { + if (!userId) { + throw new Error("userId not found"); + } + if (!passwordInputResult.currentMasterKey || !passwordInputResult.currentServerMasterKeyHash) { + throw new Error("currentMasterKey or currentServerMasterKeyHash not found"); + } + + const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( + passwordInputResult.currentMasterKey, + userId, + ); + + if (decryptedUserKey == null) { + throw new Error("Could not decrypt user key"); + } + + const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey( + passwordInputResult.newMasterKey, + decryptedUserKey, + ); + + const request = new PasswordRequest(); + request.masterPasswordHash = passwordInputResult.currentServerMasterKeyHash; + request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash; + request.masterPasswordHint = passwordInputResult.newPasswordHint; + request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string; + + try { + await this.masterPasswordApiService.postPassword(request); + } catch { + throw new Error("Could not change password"); + } + } +} diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index 91d34a34838..f4f6cc71a42 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -8,6 +8,11 @@ export * from "./anon-layout/anon-layout-wrapper.component"; export * from "./anon-layout/anon-layout-wrapper-data.service"; export * from "./anon-layout/default-anon-layout-wrapper-data.service"; +// change password +export * from "./change-password/change-password.component"; +export * from "./change-password/change-password.service.abstraction"; +export * from "./change-password/default-change-password.service"; + // fingerprint dialog export * from "./fingerprint-dialog/fingerprint-dialog.component"; diff --git a/libs/auth/src/angular/input-password/input-password.component.html b/libs/auth/src/angular/input-password/input-password.component.html index 39995f9f44f..8955a7b40b1 100644 --- a/libs/auth/src/angular/input-password/input-password.component.html +++ b/libs/auth/src/angular/input-password/input-password.component.html @@ -6,8 +6,8 @@ {{ "currentMasterPass" | i18n }} @@ -58,12 +58,12 @@
- {{ "confirmMasterPassword" | i18n }} + {{ "confirmNewMasterPass" | i18n }} `, + template: ` + + + + + `, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -31,6 +41,14 @@ class StoryDialogComponent { }, }); } + + openDrawer() { + this.dialogService.openDrawer(StoryDialogContentComponent, { + data: { + animal: "panda", + }, + }); + } } @Component({ @@ -65,25 +83,37 @@ export default { title: "Component Library/Dialogs/Service", component: StoryDialogComponent, decorators: [ + positionFixedWrapperDecorator(), moduleMetadata({ declarations: [StoryDialogContentComponent], imports: [ SharedModule, ButtonModule, + NoopAnimationsModule, DialogModule, IconButtonModule, DialogCloseDirective, DialogComponent, DialogTitleContainerDirective, + RouterTestingModule, + LayoutComponent, ], + providers: [DialogService], + }), + applicationConfig({ providers: [ - DialogService, { provide: I18nService, useFactory: () => { return new I18nMockService({ close: "Close", - loading: "Loading", + search: "Search", + skipToContent: "Skip to content", + submenu: "submenu", + toggleCollapse: "toggle collapse", + toggleSideNavigation: "Toggle side navigation", + yes: "Yes", + no: "No", }); }, }, @@ -100,4 +130,21 @@ export default { type Story = StoryObj; -export const Default: Story = {}; +export const Default: Story = { + play: async (context) => { + const canvas = context.canvasElement; + + const button = getAllByRole(canvas, "button")[0]; + await userEvent.click(button); + }, +}; + +/** Drawers must be a descendant of `bit-layout`. */ +export const Drawer: Story = { + play: async (context) => { + const canvas = context.canvasElement; + + const button = getAllByRole(canvas, "button")[1]; + await userEvent.click(button); + }, +}; diff --git a/libs/components/src/dialog/dialog.service.ts b/libs/components/src/dialog/dialog.service.ts index 83aaaff470e..409bf0a5b55 100644 --- a/libs/components/src/dialog/dialog.service.ts +++ b/libs/components/src/dialog/dialog.service.ts @@ -1,31 +1,25 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { - DEFAULT_DIALOG_CONFIG, - Dialog, - DialogConfig, - DialogRef, - DIALOG_SCROLL_STRATEGY, + Dialog as CdkDialog, + DialogConfig as CdkDialogConfig, + DialogRef as CdkDialogRefBase, + DIALOG_DATA, + DialogCloseOptions, } from "@angular/cdk/dialog"; -import { ComponentType, Overlay, OverlayContainer, ScrollStrategy } from "@angular/cdk/overlay"; -import { - Inject, - Injectable, - Injector, - OnDestroy, - Optional, - SkipSelf, - TemplateRef, -} from "@angular/core"; +import { ComponentType, ScrollStrategy } from "@angular/cdk/overlay"; +import { ComponentPortal, Portal } from "@angular/cdk/portal"; +import { Injectable, Injector, TemplateRef, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router } from "@angular/router"; -import { filter, firstValueFrom, Subject, switchMap, takeUntil } from "rxjs"; +import { filter, firstValueFrom, map, Observable, Subject, switchMap } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DrawerService } from "../drawer/drawer.service"; + import { SimpleConfigurableDialogComponent } from "./simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component"; -import { SimpleDialogOptions, Translation } from "./simple-dialog/types"; +import { SimpleDialogOptions } from "./simple-dialog/types"; /** * The default `BlockScrollStrategy` does not work well with virtual scrolling. @@ -48,61 +42,163 @@ class CustomBlockScrollStrategy implements ScrollStrategy { detach() {} } +export abstract class DialogRef + implements Pick, "close" | "closed" | "disableClose" | "componentInstance"> +{ + abstract readonly isDrawer?: boolean; + + // --- From CdkDialogRef --- + abstract close(result?: R, options?: DialogCloseOptions): void; + abstract readonly closed: Observable; + abstract disableClose: boolean | undefined; + /** + * @deprecated + * Does not work with drawer dialogs. + **/ + abstract componentInstance: C | null; +} + +export type DialogConfig = Pick< + CdkDialogConfig, + "data" | "disableClose" | "ariaModal" | "positionStrategy" | "height" | "width" +>; + +class DrawerDialogRef implements DialogRef { + readonly isDrawer = true; + + private _closed = new Subject(); + closed = this._closed.asObservable(); + disableClose = false; + + /** The portal containing the drawer */ + portal?: Portal; + + constructor(private drawerService: DrawerService) {} + + close(result?: R, _options?: DialogCloseOptions): void { + if (this.disableClose) { + return; + } + this.drawerService.close(this.portal!); + this._closed.next(result); + this._closed.complete(); + } + + componentInstance: C | null = null; +} + +/** + * DialogRef that delegates functionality to the CDK implementation + **/ +export class CdkDialogRef implements DialogRef { + readonly isDrawer = false; + + /** This is not available until after construction, @see DialogService.open. */ + cdkDialogRefBase!: CdkDialogRefBase; + + // --- Delegated to CdkDialogRefBase --- + + close(result?: R, options?: DialogCloseOptions): void { + this.cdkDialogRefBase.close(result, options); + } + + get closed(): Observable { + return this.cdkDialogRefBase.closed; + } + + get disableClose(): boolean | undefined { + return this.cdkDialogRefBase.disableClose; + } + set disableClose(value: boolean | undefined) { + this.cdkDialogRefBase.disableClose = value; + } + + // Delegate the `componentInstance` property to the CDK DialogRef + get componentInstance(): C | null { + return this.cdkDialogRefBase.componentInstance; + } +} + @Injectable() -export class DialogService extends Dialog implements OnDestroy { - private _destroy$ = new Subject(); +export class DialogService { + private dialog = inject(CdkDialog); + private drawerService = inject(DrawerService); + private injector = inject(Injector); + private router = inject(Router, { optional: true }); + private authService = inject(AuthService, { optional: true }); + private i18nService = inject(I18nService); private backDropClasses = ["tw-fixed", "tw-bg-black", "tw-bg-opacity-30", "tw-inset-0"]; - private defaultScrollStrategy = new CustomBlockScrollStrategy(); + private activeDrawer: DrawerDialogRef | null = null; - constructor( - /** Parent class constructor */ - _overlay: Overlay, - _injector: Injector, - @Optional() @Inject(DEFAULT_DIALOG_CONFIG) _defaultOptions: DialogConfig, - @Optional() @SkipSelf() _parentDialog: Dialog, - _overlayContainer: OverlayContainer, - @Inject(DIALOG_SCROLL_STRATEGY) scrollStrategy: any, - - /** Not in parent class */ - @Optional() router: Router, - @Optional() authService: AuthService, - - protected i18nService: I18nService, - ) { - super(_overlay, _injector, _defaultOptions, _parentDialog, _overlayContainer, scrollStrategy); - + constructor() { + /** + * TODO: This logic should exist outside of `libs/components`. + * @see https://bitwarden.atlassian.net/browse/CL-657 + **/ /** Close all open dialogs if the vault locks */ - if (router && authService) { - router.events + if (this.router && this.authService) { + this.router.events .pipe( filter((event) => event instanceof NavigationEnd), - switchMap(() => authService.getAuthStatus()), + switchMap(() => this.authService!.getAuthStatus()), filter((v) => v !== AuthenticationStatus.Unlocked), - takeUntil(this._destroy$), + takeUntilDestroyed(), ) .subscribe(() => this.closeAll()); } } - override ngOnDestroy(): void { - this._destroy$.next(); - this._destroy$.complete(); - super.ngOnDestroy(); - } - - override open( + open( componentOrTemplateRef: ComponentType | TemplateRef, config?: DialogConfig>, ): DialogRef { - config = { + /** + * This is a bit circular in nature: + * We need the DialogRef instance for the DI injector that is passed *to* `Dialog.open`, + * but we get the base CDK DialogRef instance *from* `Dialog.open`. + * + * To break the circle, we define CDKDialogRef as a wrapper for the CDKDialogRefBase. + * This allows us to create the class instance and provide the base instance later, almost like "deferred inheritance". + **/ + const ref = new CdkDialogRef(); + const injector = this.createInjector({ + data: config?.data, + dialogRef: ref, + }); + + // Merge the custom config with the default config + const _config = { backdropClass: this.backDropClasses, scrollStrategy: this.defaultScrollStrategy, + injector, ...config, }; - return super.open(componentOrTemplateRef, config); + ref.cdkDialogRefBase = this.dialog.open(componentOrTemplateRef, _config); + return ref; + } + + /** Opens a dialog in the side drawer */ + openDrawer( + component: ComponentType, + config?: DialogConfig>, + ): DialogRef { + this.activeDrawer?.close(); + /** + * This is also circular. When creating the DrawerDialogRef, we do not yet have a portal instance to provide to the injector. + * Similar to `this.open`, we get around this with mutability. + */ + this.activeDrawer = new DrawerDialogRef(this.drawerService); + const portal = new ComponentPortal( + component, + null, + this.createInjector({ data: config?.data, dialogRef: this.activeDrawer }), + ); + this.activeDrawer.portal = portal; + this.drawerService.open(portal); + return this.activeDrawer; } /** @@ -113,8 +209,7 @@ export class DialogService extends Dialog implements OnDestroy { */ async openSimpleDialog(simpleDialogOptions: SimpleDialogOptions): Promise { const dialogRef = this.openSimpleDialogRef(simpleDialogOptions); - - return firstValueFrom(dialogRef.closed); + return firstValueFrom(dialogRef.closed.pipe(map((v: boolean | undefined) => !!v))); } /** @@ -134,20 +229,29 @@ export class DialogService extends Dialog implements OnDestroy { }); } - protected translate(translation: string | Translation, defaultKey?: string): string { - if (translation == null && defaultKey == null) { - return null; - } + /** Close all open dialogs */ + closeAll(): void { + return this.dialog.closeAll(); + } - if (translation == null) { - return this.i18nService.t(defaultKey); - } - - // Translation interface use implies we must localize. - if (typeof translation === "object") { - return this.i18nService.t(translation.key, ...(translation.placeholders ?? [])); - } - - return translation; + /** The injector that is passed to the opened dialog */ + private createInjector(opts: { data: unknown; dialogRef: DialogRef }): Injector { + return Injector.create({ + providers: [ + { + provide: DIALOG_DATA, + useValue: opts.data, + }, + { + provide: DialogRef, + useValue: opts.dialogRef, + }, + { + provide: CdkDialogRefBase, + useValue: opts.dialogRef, + }, + ], + parent: this.injector, + }); } } diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html index 01f05985127..eaf7fc2beec 100644 --- a/libs/components/src/dialog/dialog/dialog.component.html +++ b/libs/components/src/dialog/dialog/dialog.component.html @@ -1,12 +1,22 @@ +@let isDrawer = dialogRef?.isDrawer;
+ @let showHeaderBorder = !isDrawer || background === "alt" || bodyHasScrolledFrom().top;
-

} -

+
+ @let showFooterBorder = !isDrawer || background === "alt" || bodyHasScrolledFrom().bottom;
diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index 504dbd3a1ea..8cf9cd18c78 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -1,14 +1,18 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { CdkTrapFocus } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { CdkScrollable } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { Component, HostBinding, Input } from "@angular/core"; +import { Component, HostBinding, Input, inject, viewChild } from "@angular/core"; import { I18nPipe } from "@bitwarden/ui-common"; import { BitIconButtonComponent } from "../../icon-button/icon-button.component"; import { TypographyDirective } from "../../typography/typography.directive"; +import { hasScrolledFrom } from "../../utils/has-scrolled-from"; import { fadeIn } from "../animations"; +import { DialogRef } from "../dialog.service"; import { DialogCloseDirective } from "../directives/dialog-close.directive"; import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; @@ -17,6 +21,9 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai templateUrl: "./dialog.component.html", animations: [fadeIn], standalone: true, + host: { + "(keydown.esc)": "handleEsc($event)", + }, imports: [ CommonModule, DialogTitleContainerDirective, @@ -24,9 +31,15 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai BitIconButtonComponent, DialogCloseDirective, I18nPipe, + CdkTrapFocus, + CdkScrollable, ], }) export class DialogComponent { + protected dialogRef = inject(DialogRef, { optional: true }); + private scrollableBody = viewChild.required(CdkScrollable); + protected bodyHasScrolledFrom = hasScrolledFrom(this.scrollableBody); + /** Background color */ @Input() background: "default" | "alt" = "default"; @@ -64,21 +77,31 @@ export class DialogComponent { @HostBinding("class") get classes() { // `tw-max-h-[90vh]` is needed to prevent dialogs from overlapping the desktop header - return ["tw-flex", "tw-flex-col", "tw-w-screen", "tw-p-4", "tw-max-h-[90vh]"].concat( - this.width, - ); + return ["tw-flex", "tw-flex-col", "tw-w-screen"] + .concat( + this.width, + this.dialogRef?.isDrawer + ? ["tw-min-h-screen", "md:tw-w-[23rem]"] + : ["tw-p-4", "tw-w-screen", "tw-max-h-[90vh]"], + ) + .flat(); + } + + handleEsc(event: Event) { + this.dialogRef?.close(); + event.stopPropagation(); } get width() { switch (this.dialogSize) { case "small": { - return "tw-max-w-sm"; + return "md:tw-max-w-sm"; } case "large": { - return "tw-max-w-3xl"; + return "md:tw-max-w-3xl"; } default: { - return "tw-max-w-xl"; + return "md:tw-max-w-xl"; } } } diff --git a/libs/components/src/dialog/dialogs.mdx b/libs/components/src/dialog/dialogs.mdx index 63df0bfc131..3f44f31a5eb 100644 --- a/libs/components/src/dialog/dialogs.mdx +++ b/libs/components/src/dialog/dialogs.mdx @@ -22,6 +22,9 @@ For alerts or simple confirmation actions, like speedbumps, use the Dialogs's should be used sparingly as they do call extra attention to themselves and can be interruptive if overused. +For non-blocking, supplementary content, open dialogs as a +[Drawer](?path=/story/component-library-dialogs-service--drawer) (requires `bit-layout`). + ## Placement Dialogs should be centered vertically and horizontally on screen. Dialogs height should expand to diff --git a/libs/components/src/dialog/index.ts b/libs/components/src/dialog/index.ts index 0ab9a5d9e67..fb4c2721b81 100644 --- a/libs/components/src/dialog/index.ts +++ b/libs/components/src/dialog/index.ts @@ -1,4 +1,4 @@ export * from "./dialog.module"; export * from "./simple-dialog/types"; export * from "./dialog.service"; -export { DialogConfig, DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +export { DIALOG_DATA } from "@angular/cdk/dialog"; diff --git a/libs/components/src/drawer/drawer-body.component.ts b/libs/components/src/drawer/drawer-body.component.ts index 9bd2adcffbc..563987dd8b0 100644 --- a/libs/components/src/drawer/drawer-body.component.ts +++ b/libs/components/src/drawer/drawer-body.component.ts @@ -1,7 +1,7 @@ import { CdkScrollable } from "@angular/cdk/scrolling"; -import { ChangeDetectionStrategy, Component, Signal, inject } from "@angular/core"; -import { toSignal } from "@angular/core/rxjs-interop"; -import { map } from "rxjs"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; + +import { hasScrolledFrom } from "../utils/has-scrolled-from"; /** * Body container for `bit-drawer` @@ -14,7 +14,7 @@ import { map } from "rxjs"; host: { class: "tw-p-4 tw-pt-0 tw-block tw-overflow-auto tw-border-solid tw-border tw-border-transparent tw-transition-colors tw-duration-200", - "[class.tw-border-t-secondary-300]": "isScrolled()", + "[class.tw-border-t-secondary-300]": "this.hasScrolledFrom().top", }, hostDirectives: [ { @@ -24,13 +24,5 @@ import { map } from "rxjs"; template: ` `, }) export class DrawerBodyComponent { - private scrollable = inject(CdkScrollable); - - /** TODO: share this utility with browser popup header? */ - protected isScrolled: Signal = toSignal( - this.scrollable - .elementScrolled() - .pipe(map(() => this.scrollable.measureScrollOffset("top") > 0)), - { initialValue: false }, - ); + protected hasScrolledFrom = hasScrolledFrom(); } diff --git a/libs/components/src/drawer/drawer.component.ts b/libs/components/src/drawer/drawer.component.ts index ccabb6f0b6e..351be57e07b 100644 --- a/libs/components/src/drawer/drawer.component.ts +++ b/libs/components/src/drawer/drawer.component.ts @@ -10,7 +10,7 @@ import { viewChild, } from "@angular/core"; -import { DrawerHostDirective } from "./drawer-host.directive"; +import { DrawerService } from "./drawer.service"; /** * A drawer is a panel of supplementary content that is adjacent to the page's main content. @@ -25,7 +25,7 @@ import { DrawerHostDirective } from "./drawer-host.directive"; templateUrl: "drawer.component.html", }) export class DrawerComponent { - private drawerHost = inject(DrawerHostDirective); + private drawerHost = inject(DrawerService); private portal = viewChild.required(CdkPortal); /** diff --git a/libs/components/src/drawer/drawer.mdx b/libs/components/src/drawer/drawer.mdx index 57d618cfe95..bc99fa290d6 100644 --- a/libs/components/src/drawer/drawer.mdx +++ b/libs/components/src/drawer/drawer.mdx @@ -12,6 +12,8 @@ import { DrawerComponent } from "@bitwarden/components"; # Drawer +**Note: `bit-drawer` is deprecated. Use `bit-dialog` and `DialogService.openDrawer(...)` instead.** + A drawer is a panel of supplementary content that is adjacent to the page's main content. diff --git a/libs/components/src/drawer/drawer.service.ts b/libs/components/src/drawer/drawer.service.ts new file mode 100644 index 00000000000..dd8575efee8 --- /dev/null +++ b/libs/components/src/drawer/drawer.service.ts @@ -0,0 +1,20 @@ +import { Portal } from "@angular/cdk/portal"; +import { Injectable, signal } from "@angular/core"; + +@Injectable({ providedIn: "root" }) +export class DrawerService { + private _portal = signal | undefined>(undefined); + + /** The portal to display */ + portal = this._portal.asReadonly(); + + open(portal: Portal) { + this._portal.set(portal); + } + + close(portal: Portal) { + if (portal === this.portal()) { + this._portal.set(undefined); + } + } +} diff --git a/libs/components/src/form-field/form-field.component.html b/libs/components/src/form-field/form-field.component.html index bd099859608..02d7c37cadf 100644 --- a/libs/components/src/form-field/form-field.component.html +++ b/libs/components/src/form-field/form-field.component.html @@ -56,7 +56,8 @@
diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 4f7c2a67483..e810aaec8cb 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -91,7 +91,7 @@ export class BitFormFieldComponent implements AfterContentChecked { protected defaultContentIsFocused = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { - this.defaultContentIsFocused.set(target.matches(".default-content *:focus-visible")); + this.defaultContentIsFocused.set(target.matches("[data-default-content] *:focus-visible")); } @HostListener("focusout") onFocusOut() { diff --git a/libs/components/src/input/autofocus.directive.ts b/libs/components/src/input/autofocus.directive.ts index 46eb1b15b16..3fd06156f39 100644 --- a/libs/components/src/input/autofocus.directive.ts +++ b/libs/components/src/input/autofocus.directive.ts @@ -19,6 +19,7 @@ import { FocusableElement } from "../shared/focusable-element"; */ @Directive({ selector: "[appAutofocus], [bitAutofocus]", + standalone: false, }) export class AutofocusDirective implements AfterContentChecked { @Input() set appAutofocus(condition: boolean | string) { diff --git a/libs/components/src/item/item-content.component.ts b/libs/components/src/item/item-content.component.ts index 2a6e06291fd..76fa3996210 100644 --- a/libs/components/src/item/item-content.component.ts +++ b/libs/components/src/item/item-content.component.ts @@ -25,7 +25,8 @@ import { TypographyModule } from "../typography"; * y-axis padding should be kept in sync with `item-action.component.ts`'s `top` and `bottom` units. * we want this to be the same height as the `item-action`'s `:after` element */ - "fvw-target tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 bit-compact:tw-py-1.5 bit-compact:tw-px-2 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", + "tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 bit-compact:tw-py-1.5 bit-compact:tw-px-2 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", + "data-fvw-target": "", }, changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/libs/components/src/item/item.component.html b/libs/components/src/item/item.component.html index 812d6b0315b..2863bb2891b 100644 --- a/libs/components/src/item/item.component.html +++ b/libs/components/src/item/item.component.html @@ -1,4 +1,4 @@ - + diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index 1ef4a4af1fa..0c45f98139e 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -19,7 +19,7 @@ import { ItemActionComponent } from "./item-action.component"; providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }], host: { class: - "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-cursor-pointer [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", + "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has([data-item-main-content]_button:hover,[data-item-main-content]_a:hover)]:tw-cursor-pointer [&:has([data-item-main-content]_button:hover,[data-item-main-content]_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", }, }) export class ItemComponent extends A11yRowDirective { @@ -29,7 +29,7 @@ export class ItemComponent extends A11yRowDirective { protected focusVisibleWithin = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { - this.focusVisibleWithin.set(target.matches(".fvw-target:focus-visible")); + this.focusVisibleWithin.set(target.matches("[data-fvw-target]:focus-visible")); } @HostListener("focusout") onFocusOut() { diff --git a/libs/components/src/layout/index.ts b/libs/components/src/layout/index.ts index 6994a4f639f..a257a4dde85 100644 --- a/libs/components/src/layout/index.ts +++ b/libs/components/src/layout/index.ts @@ -1 +1,2 @@ export * from "./layout.component"; +export * from "./scroll-layout.directive"; diff --git a/libs/components/src/layout/layout.component.html b/libs/components/src/layout/layout.component.html index 33b8de81572..19280c99756 100644 --- a/libs/components/src/layout/layout.component.html +++ b/libs/components/src/layout/layout.component.html @@ -1,43 +1,52 @@ -
+@let mainContentId = "main-content";
- -
- +
+ + +
+ - - @if ( - { - open: sideNavService.open$ | async, - }; - as data - ) { -
- @if (data.open) { -
- } -
- } -
- + + @if ( + { + open: sideNavService.open$ | async, + }; + as data + ) { +
+ @if (data.open) { +
+ } +
+ } +
+
+
+ +
diff --git a/libs/components/src/layout/layout.component.ts b/libs/components/src/layout/layout.component.ts index 7bf8a6ad173..6ee7dd8222e 100644 --- a/libs/components/src/layout/layout.component.ts +++ b/libs/components/src/layout/layout.component.ts @@ -1,9 +1,10 @@ +import { A11yModule, CdkTrapFocus } from "@angular/cdk/a11y"; import { PortalModule } from "@angular/cdk/portal"; import { CommonModule } from "@angular/common"; -import { Component, inject } from "@angular/core"; +import { Component, ElementRef, inject, viewChild } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { DrawerHostDirective } from "../drawer/drawer-host.directive"; +import { DrawerService } from "../drawer/drawer.service"; import { LinkModule } from "../link"; import { SideNavService } from "../navigation/side-nav.service"; import { SharedModule } from "../shared"; @@ -12,16 +13,23 @@ import { SharedModule } from "../shared"; selector: "bit-layout", templateUrl: "layout.component.html", standalone: true, - imports: [CommonModule, SharedModule, LinkModule, RouterModule, PortalModule], - hostDirectives: [DrawerHostDirective], + imports: [ + CommonModule, + SharedModule, + LinkModule, + RouterModule, + PortalModule, + A11yModule, + CdkTrapFocus, + ], }) export class LayoutComponent { - protected mainContentId = "main-content"; - protected sideNavService = inject(SideNavService); - protected drawerPortal = inject(DrawerHostDirective).portal; + protected drawerPortal = inject(DrawerService).portal; - focusMainContent() { - document.getElementById(this.mainContentId)?.focus(); + private mainContent = viewChild.required>("main"); + + protected focusMainContent() { + this.mainContent().nativeElement.focus(); } } diff --git a/libs/components/src/layout/scroll-layout.directive.ts b/libs/components/src/layout/scroll-layout.directive.ts new file mode 100644 index 00000000000..d3ea2bf557b --- /dev/null +++ b/libs/components/src/layout/scroll-layout.directive.ts @@ -0,0 +1,35 @@ +import { Directionality } from "@angular/cdk/bidi"; +import { CdkVirtualScrollable, ScrollDispatcher, VIRTUAL_SCROLLABLE } from "@angular/cdk/scrolling"; +import { Directive, ElementRef, NgZone, Optional } from "@angular/core"; + +@Directive({ + selector: "cdk-virtual-scroll-viewport[bitScrollLayout]", + standalone: true, + providers: [{ provide: VIRTUAL_SCROLLABLE, useExisting: ScrollLayoutDirective }], +}) +export class ScrollLayoutDirective extends CdkVirtualScrollable { + private mainRef: ElementRef; + + constructor(scrollDispatcher: ScrollDispatcher, ngZone: NgZone, @Optional() dir: Directionality) { + const mainEl = document.querySelector("main")!; + if (!mainEl) { + // eslint-disable-next-line no-console + console.error("HTML main element must be an ancestor of [bitScrollLayout]"); + } + const mainRef = new ElementRef(mainEl); + super(mainRef, scrollDispatcher, ngZone, dir); + this.mainRef = mainRef; + } + + override getElementRef(): ElementRef { + return this.mainRef; + } + + override measureBoundingClientRectWithScrollOffset( + from: "left" | "top" | "right" | "bottom", + ): number { + return ( + this.mainRef.nativeElement.getBoundingClientRect()[from] - this.measureScrollOffset(from) + ); + } +} diff --git a/libs/components/src/menu/menu-trigger-for.directive.ts b/libs/components/src/menu/menu-trigger-for.directive.ts index 96d430c5e6a..bc174d14d23 100644 --- a/libs/components/src/menu/menu-trigger-for.directive.ts +++ b/libs/components/src/menu/menu-trigger-for.directive.ts @@ -16,11 +16,7 @@ import { filter, mergeWith } from "rxjs/operators"; import { MenuComponent } from "./menu.component"; -@Directive({ - selector: "[bitMenuTriggerFor]", - exportAs: "menuTrigger", - standalone: true, -}) +@Directive({ selector: "[bitMenuTriggerFor]", exportAs: "menuTrigger", standalone: true }) export class MenuTriggerForDirective implements OnDestroy { @HostBinding("attr.aria-expanded") isOpen = false; @HostBinding("attr.aria-haspopup") get hasPopup(): "menu" | "dialog" { @@ -42,18 +38,10 @@ export class MenuTriggerForDirective implements OnDestroy { .position() .flexibleConnectedTo(this.elementRef) .withPositions([ - { - originX: "start", - originY: "bottom", - overlayX: "start", - overlayY: "top", - }, - { - originX: "end", - originY: "bottom", - overlayX: "end", - overlayY: "top", - }, + { originX: "start", originY: "bottom", overlayX: "start", overlayY: "top" }, + { originX: "end", originY: "bottom", overlayX: "end", overlayY: "top" }, + { originX: "start", originY: "top", overlayX: "start", overlayY: "bottom" }, + { originX: "end", originY: "top", overlayX: "end", overlayY: "bottom" }, ]) .withLockedPosition(true) .withFlexibleDimensions(false) diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index c3cf559389f..595ddf5a99f 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -70,10 +70,11 @@ - + @@ -90,72 +151,6 @@ class KitchenSinkDialog { - - - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

- - What did foo say to bar? - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-
-
`, }) export class KitchenSinkMainComponent { @@ -168,7 +163,7 @@ export class KitchenSinkMainComponent { } openDrawer() { - this.drawerOpen.set(true); + this.dialogService.openDrawer(KitchenSinkDialog); } navItems = [ diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index f57a9de4e68..d318e1b5f0e 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -14,7 +14,6 @@ import { import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService } from "../../dialog"; import { LayoutComponent } from "../../layout"; import { I18nMockService } from "../../utils/i18n-mock.service"; import { positionFixedWrapperDecorator } from "../storybook-decorators"; @@ -39,8 +38,20 @@ export default { KitchenSinkTable, KitchenSinkToggleList, ], + }), + applicationConfig({ providers: [ - DialogService, + provideNoopAnimations(), + importProvidersFrom( + RouterModule.forRoot( + [ + { path: "", redirectTo: "bitwarden", pathMatch: "full" }, + { path: "bitwarden", component: KitchenSinkMainComponent }, + { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, + ], + { useHash: true }, + ), + ), { provide: I18nService, useFactory: () => { @@ -58,21 +69,6 @@ export default { }, ], }), - applicationConfig({ - providers: [ - provideNoopAnimations(), - importProvidersFrom( - RouterModule.forRoot( - [ - { path: "", redirectTo: "bitwarden", pathMatch: "full" }, - { path: "bitwarden", component: KitchenSinkMainComponent }, - { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, - ], - { useHash: true }, - ), - ), - ], - }), ], } as Meta; diff --git a/libs/components/src/table/table-scroll.component.html b/libs/components/src/table/table-scroll.component.html index 8f2c88ba3ad..523912cd7ac 100644 --- a/libs/components/src/table/table-scroll.component.html +++ b/libs/components/src/table/table-scroll.component.html @@ -1,5 +1,5 @@ diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index 34cd8c5d9ca..e83dbbecc60 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -2,7 +2,6 @@ // @ts-strict-ignore import { CdkVirtualScrollViewport, - CdkVirtualScrollableWindow, CdkFixedSizeVirtualScroll, CdkVirtualForOf, } from "@angular/cdk/scrolling"; @@ -21,6 +20,8 @@ import { TrackByFunction, } from "@angular/core"; +import { ScrollLayoutDirective } from "../layout"; + import { RowDirective } from "./row.directive"; import { TableComponent } from "./table.component"; @@ -54,10 +55,10 @@ export class BitRowDef { imports: [ CommonModule, CdkVirtualScrollViewport, - CdkVirtualScrollableWindow, CdkFixedSizeVirtualScroll, CdkVirtualForOf, RowDirective, + ScrollLayoutDirective, ], }) export class TableScrollComponent diff --git a/libs/components/src/table/table.mdx b/libs/components/src/table/table.mdx index 8d784190ed9..59bf5b773a3 100644 --- a/libs/components/src/table/table.mdx +++ b/libs/components/src/table/table.mdx @@ -142,7 +142,7 @@ dataSource.filter = (data) => data.orgType === "family"; Rudimentary string filtering is supported out of the box with `TableDataSource.simpleStringFilter`. It works by converting each entry into a string of it's properties. The provided string is then -compared against the filter value using a simple `indexOf` check. For convienence, you can also just +compared against the filter value using a simple `indexOf` check. For convenience, you can also just pass a string directly. ```ts @@ -153,7 +153,7 @@ dataSource.filter = "search value"; ### Virtual Scrolling -It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount +It's heavily advised to use virtual scrolling if you expect the table to have any significant amount of data. This is done by using the `bit-table-scroll` component instead of the `bit-table` component. This component behaves slightly different from the `bit-table` component. Instead of using the `*ngFor` directive to render the rows, you provide a `bitRowDef` template that will be @@ -178,6 +178,14 @@ height and align vertically. ``` +#### Deprecated approach + +Before `bit-table-scroll` was introduced, virtual scroll in tables was implemented manually via +constructs from Angular CDK. This included wrapping the table with a `cdk-virtual-scroll-viewport` +and targeting with `bit-layout`'s scroll container with the `bitScrollLayout` directive. + +This pattern is deprecated in favor of `bit-table-scroll`. + ## Accessibility - Always include a row or column header with your table; this allows assistive technology to better diff --git a/libs/components/src/table/table.stories.ts b/libs/components/src/table/table.stories.ts index e8ab24ee8b7..d696e6077dd 100644 --- a/libs/components/src/table/table.stories.ts +++ b/libs/components/src/table/table.stories.ts @@ -1,6 +1,13 @@ +import { RouterTestingModule } from "@angular/router/testing"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + import { countries } from "../form/countries"; +import { LayoutComponent } from "../layout"; +import { mockLayoutI18n } from "../layout/mocks"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; +import { I18nMockService } from "../utils"; import { TableDataSource } from "./table-data-source"; import { TableModule } from "./table.module"; @@ -8,8 +15,17 @@ import { TableModule } from "./table.module"; export default { title: "Component Library/Table", decorators: [ + positionFixedWrapperDecorator(), moduleMetadata({ - imports: [TableModule], + imports: [TableModule, LayoutComponent, RouterTestingModule], + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService(mockLayoutI18n); + }, + }, + ], }), ], argTypes: { @@ -116,18 +132,20 @@ export const Scrollable: Story = { trackBy: (index: number, item: any) => item.id, }, template: ` - - - Id - Name - Other - - - {{ row.id }} - {{ row.name }} - {{ row.other }} - - + + + + Id + Name + Other + + + {{ row.id }} + {{ row.name }} + {{ row.other }} + + + `, }), }; @@ -144,17 +162,19 @@ export const Filterable: Story = { sortFn: (a: any, b: any) => a.id - b.id, }, template: ` - - - - Name - Value - - - {{ row.name }} - {{ row.value }} - - + + + + + Name + Value + + + {{ row.name }} + {{ row.value }} + + + `, }), }; diff --git a/libs/components/src/utils/has-scrolled-from.ts b/libs/components/src/utils/has-scrolled-from.ts new file mode 100644 index 00000000000..44c73465bdd --- /dev/null +++ b/libs/components/src/utils/has-scrolled-from.ts @@ -0,0 +1,41 @@ +import { CdkScrollable } from "@angular/cdk/scrolling"; +import { Signal, inject, signal } from "@angular/core"; +import { toObservable, toSignal } from "@angular/core/rxjs-interop"; +import { map, startWith, switchMap } from "rxjs"; + +export type ScrollState = { + /** `true` when the scrollbar is not at the top-most position */ + top: boolean; + + /** `true` when the scrollbar is not at the bottom-most position */ + bottom: boolean; +}; + +/** + * Check if a `CdkScrollable` instance has been scrolled + * @param scrollable The instance to check, defaults to the one provided by the current injector + * @returns {Signal} + */ +export const hasScrolledFrom = (scrollable?: Signal): Signal => { + const _scrollable = scrollable ?? signal(inject(CdkScrollable)); + const scrollable$ = toObservable(_scrollable); + + const scrollState$ = scrollable$.pipe( + switchMap((_scrollable) => + _scrollable.elementScrolled().pipe( + startWith(null), + map(() => ({ + top: _scrollable.measureScrollOffset("top") > 0, + bottom: _scrollable.measureScrollOffset("bottom") > 0, + })), + ), + ), + ); + + return toSignal(scrollState$, { + initialValue: { + top: false, + bottom: false, + }, + }); +}; diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 2c6354f5b5a..2fee88852b5 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -49,7 +49,6 @@ import { ButtonModule, CalloutModule, CardComponent, - ContainerComponent, DialogService, FormFieldModule, IconButtonModule, @@ -119,7 +118,6 @@ const safeProviders: SafeProvider[] = [ ImportLastPassComponent, RadioButtonModule, CardComponent, - ContainerComponent, SectionHeaderComponent, SectionComponent, LinkModule, diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index 9284718a063..af29d8263c6 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -118,9 +118,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); - const view = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const view = await this.cipherService.decrypt(cipher, activeUserId); this.cleanupCipher(view); this.result.ciphers.push(view); } diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index ae408af421b..6ed4caa3f8d 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -10,7 +10,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -172,6 +172,8 @@ describe("VaultExportService", () => { let apiService: MockProxy; let fetchMock: jest.Mock; + const userId = "" as UserId; + beforeEach(() => { cryptoFunctionService = mock(); cipherService = mock(); @@ -185,7 +187,6 @@ describe("VaultExportService", () => { keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); - const userId = "" as UserId; const accountInfo: AccountInfo = { email: "", emailVerified: true, @@ -338,7 +339,9 @@ describe("VaultExportService", () => { cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); + cipherService.getDecryptedAttachmentBuffer.mockRejectedValue( + new Error("Error decrypting attachment"), + ); global.fetch = jest.fn(() => Promise.resolve({ @@ -356,13 +359,17 @@ describe("VaultExportService", () => { it("contains attachments with folders", async () => { const cipherData = new CipherData(); cipherData.id = "mock-id"; + const cipherRecord: Record = { + ["mock-id" as CipherId]: cipherData, + }; const cipherView = new CipherView(new Cipher(cipherData)); const attachmentView = new AttachmentView(new Attachment(new AttachmentData())); attachmentView.fileName = "mock-file-name"; cipherView.attachments = [attachmentView]; + cipherService.ciphers$.mockReturnValue(of(cipherRecord)); cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); + cipherService.getDecryptedAttachmentBuffer.mockResolvedValue(new Uint8Array(255)); global.fetch = jest.fn(() => Promise.resolve({ status: 200, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index 8b66580d4cd..537585aac7e 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -12,14 +12,12 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { Folder } from "@bitwarden/common/vault/models/domain/folder"; -import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; @@ -118,8 +116,19 @@ export class IndividualVaultExportService const cipherFolder = attachmentsFolder.folder(cipher.id); for (const attachment of cipher.attachments) { const response = await this.downloadAttachment(cipher.id, attachment.id); - const decBuf = await this.decryptAttachment(cipher, attachment, response); - cipherFolder.file(attachment.fileName, decBuf); + + try { + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachment, + response, + activeUserId, + ); + + cipherFolder.file(attachment.fileName, decBuf); + } catch { + throw new Error("Error decrypting attachment"); + } } } @@ -146,23 +155,6 @@ export class IndividualVaultExportService return response; } - private async decryptAttachment( - cipher: CipherView, - attachment: AttachmentView, - response: Response, - ) { - try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(cipher.organizationId); - return await this.encryptService.decryptFileData(encBuf, key); - } catch { - throw new Error("Error decrypting attachment"); - } - } - private async getDecryptedExport( activeUserId: UserId, format: "json" | "csv", diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index fc46915c15d..4f30f299062 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -155,12 +155,9 @@ export class OrganizationVaultExportService .forEach(async (c) => { const cipher = new Cipher(new CipherData(c)); exportPromises.push( - this.cipherService - .getKeyForCipherKeyDecryption(cipher, activeUserId) - .then((key) => cipher.decrypt(key)) - .then((decCipher) => { - decCiphers.push(decCipher); - }), + this.cipherService.decrypt(cipher, activeUserId).then((decCipher) => { + decCiphers.push(decCipher); + }), ); }); } diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index 71599c19ae0..0512b56e20d 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -80,7 +80,6 @@ import { ExportScopeCalloutComponent } from "./export-scope-callout.component"; CalloutModule, RadioButtonModule, ExportScopeCalloutComponent, - UserVerificationDialogComponent, PasswordStrengthV2Component, GeneratorServicesModule, ], diff --git a/libs/tools/generator/components/src/catchall-settings.component.ts b/libs/tools/generator/components/src/catchall-settings.component.ts index 92d0909e661..3bf586c5a29 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.ts +++ b/libs/tools/generator/components/src/catchall-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-catchall-settings", templateUrl: "catchall-settings.component.html", + standalone: false, }) export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { /** Instantiates the component diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index 4a83f2a9a92..0b48b4cf0f7 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -67,6 +67,7 @@ const NONE_SELECTED = "none"; @Component({ selector: "tools-credential-generator", templateUrl: "credential-generator.component.html", + standalone: false, }) export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestroy { private readonly destroyed = new Subject(); diff --git a/libs/tools/generator/components/src/forwarder-settings.component.ts b/libs/tools/generator/components/src/forwarder-settings.component.ts index 8a5311fb7f3..689cc7e258c 100644 --- a/libs/tools/generator/components/src/forwarder-settings.component.ts +++ b/libs/tools/generator/components/src/forwarder-settings.component.ts @@ -33,6 +33,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-forwarder-settings", templateUrl: "forwarder-settings.component.html", + standalone: false, }) export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/passphrase-settings.component.ts b/libs/tools/generator/components/src/passphrase-settings.component.ts index 0509ceab60f..405914977c5 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.ts +++ b/libs/tools/generator/components/src/passphrase-settings.component.ts @@ -33,6 +33,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-passphrase-settings", templateUrl: "passphrase-settings.component.html", + standalone: false, }) export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index e4e173829a6..9643c857473 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -53,6 +53,7 @@ import { GeneratorHistoryService } from "@bitwarden/generator-history"; @Component({ selector: "tools-password-generator", templateUrl: "password-generator.component.html", + standalone: false, }) export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy { constructor( diff --git a/libs/tools/generator/components/src/password-settings.component.ts b/libs/tools/generator/components/src/password-settings.component.ts index e4005c6fda7..346e9549cd8 100644 --- a/libs/tools/generator/components/src/password-settings.component.ts +++ b/libs/tools/generator/components/src/password-settings.component.ts @@ -37,6 +37,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-password-settings", templateUrl: "password-settings.component.html", + standalone: false, }) export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/subaddress-settings.component.ts b/libs/tools/generator/components/src/subaddress-settings.component.ts index 8bde260693c..b09ecc86f9e 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.ts +++ b/libs/tools/generator/components/src/subaddress-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-subaddress-settings", templateUrl: "subaddress-settings.component.html", + standalone: false, }) export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index 0c06b89adb4..de48a9bd6b1 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -67,6 +67,7 @@ const NONE_SELECTED = "none"; @Component({ selector: "tools-username-generator", templateUrl: "username-generator.component.html", + standalone: false, }) export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the username generator diff --git a/libs/tools/generator/components/src/username-settings.component.ts b/libs/tools/generator/components/src/username-settings.component.ts index 7e59ef9c379..ea3cfbd35fb 100644 --- a/libs/tools/generator/components/src/username-settings.component.ts +++ b/libs/tools/generator/components/src/username-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-username-settings", templateUrl: "username-settings.component.html", + standalone: false, }) export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts index 3149307bdd5..13c00a6bb78 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts @@ -25,10 +25,8 @@ import { AsyncActionsModule, BitSubmitDirective, ButtonComponent, - CardComponent, FormFieldModule, ItemModule, - SectionComponent, SelectModule, ToastService, TypographyModule, @@ -52,8 +50,6 @@ import { SendDetailsComponent } from "./send-details/send-details.component"; ], imports: [ AsyncActionsModule, - CardComponent, - SectionComponent, TypographyModule, ItemModule, FormFieldModule, diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 7090502ef14..137b220014a 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -36,7 +36,7 @@ import { CipherFormGenerationService, NudgeStatus, PasswordRepromptService, - VaultNudgesService, + NudgesService, } from "@bitwarden/vault"; // FIXME: remove `/apps` import from `/libs` // FIXME: remove `src` and fix import @@ -144,7 +144,7 @@ export default { ], providers: [ { - provide: VaultNudgesService, + provide: NudgesService, useValue: { showNudge$: new BehaviorSubject({ hasBadgeDismissed: true, diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html index e1eedb36e85..63d809cb8ba 100644 --- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html +++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html @@ -1,6 +1,7 @@ -

{{ "additionalOptions" | i18n }}

@@ -30,7 +31,7 @@ {{ "addField" | i18n }} -
+ { get: cipherServiceGet, saveAttachmentWithServer, getKeyForCipherKeyDecryption: () => Promise.resolve(null), + decrypt: jest.fn().mockResolvedValue(cipherView), }, }, { diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index 29a80c826c6..aa9769ec392 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -137,9 +137,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { this.organization = await this.getOrganization(); this.cipherDomain = await this.getCipher(this.cipherId); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, this.activeUserId); // Update the initial state of the submit button if (this.submitBtn) { @@ -210,9 +208,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { ); // re-decrypt the cipher to update the attachments - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, this.activeUserId); // Reset reactive form and input element this.fileInput.nativeElement.value = ""; diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html index bdfdc6ff189..5042dce8608 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html @@ -1,4 +1,4 @@ - +

{{ "autofillOptions" | i18n }} @@ -38,4 +38,4 @@ - +

diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts index ac670a39335..ccbc792648e 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts @@ -22,7 +22,6 @@ import { FormFieldModule, IconButtonModule, LinkModule, - SectionComponent, SectionHeaderComponent, SelectModule, TypographyModule, @@ -43,7 +42,6 @@ interface UriField { standalone: true, imports: [ DragDropModule, - SectionComponent, SectionHeaderComponent, TypographyModule, JslibModule, diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html index 485f8f79856..7fda078b066 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ getSectionHeading() }} @@ -71,4 +71,4 @@ > - +

diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index cb00c7d24f5..8086d2bf0c4 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -16,7 +16,6 @@ import { CardComponent, FormFieldModule, IconButtonModule, - SectionComponent, SectionHeaderComponent, SelectModule, TypographyModule, @@ -30,7 +29,6 @@ import { CipherFormContainer } from "../../cipher-form-container"; standalone: true, imports: [ CardComponent, - SectionComponent, TypographyModule, FormFieldModule, ReactiveFormsModule, diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.ts b/libs/vault/src/cipher-form/components/cipher-form.component.ts index 8b99b60bc16..08dc71c9886 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.ts @@ -26,10 +26,8 @@ import { AsyncActionsModule, BitSubmitDirective, ButtonComponent, - CardComponent, FormFieldModule, ItemModule, - SectionComponent, SelectModule, ToastService, TypographyModule, @@ -63,8 +61,6 @@ import { SshKeySectionComponent } from "./sshkey-section/sshkey-section.componen ], imports: [ AsyncActionsModule, - CardComponent, - SectionComponent, TypographyModule, ItemModule, FormFieldModule, @@ -275,11 +271,6 @@ export class CipherFormComponent implements AfterViewInit, OnInit, OnChanges, Ci if (this.updatedCipherView.id === cachedCipher.id) { this.updatedCipherView = cachedCipher; } - - // `id` is null when a cipher is being added - if (this.updatedCipherView.id === null) { - this.updatedCipherView = cachedCipher; - } } constructor( diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html index 0dc5e3f6ac0..98cc6489bbd 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html @@ -1,4 +1,8 @@ - +

{{ "customFields" | i18n }}

@@ -116,4 +120,4 @@ - +
diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts index 49e9e109b74..5d43f52788a 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts @@ -37,7 +37,6 @@ import { FormFieldModule, IconButtonModule, LinkModule, - SectionComponent, SectionHeaderComponent, SelectModule, TypographyModule, @@ -79,7 +78,6 @@ export type CustomField = { FormsModule, FormFieldModule, ReactiveFormsModule, - SectionComponent, SectionHeaderComponent, TypographyModule, CardComponent, diff --git a/libs/vault/src/cipher-form/components/identity/identity.component.html b/libs/vault/src/cipher-form/components/identity/identity.component.html index dad8dca78a6..7f49bc21a10 100644 --- a/libs/vault/src/cipher-form/components/identity/identity.component.html +++ b/libs/vault/src/cipher-form/components/identity/identity.component.html @@ -1,5 +1,5 @@
- +

{{ "personalDetails" | i18n }}

@@ -48,8 +48,8 @@ - - +
+

{{ "identification" | i18n }}

@@ -87,8 +87,8 @@ - - +
+

{{ "contactInfo" | i18n }}

@@ -106,8 +106,8 @@ - - +
+

{{ "address" | i18n }}

@@ -155,5 +155,5 @@ - +
diff --git a/libs/vault/src/cipher-form/components/identity/identity.component.ts b/libs/vault/src/cipher-form/components/identity/identity.component.ts index f0c73002e97..3cc8e73697f 100644 --- a/libs/vault/src/cipher-form/components/identity/identity.component.ts +++ b/libs/vault/src/cipher-form/components/identity/identity.component.ts @@ -14,7 +14,6 @@ import { CardComponent, FormFieldModule, IconButtonModule, - SectionComponent, SectionHeaderComponent, SelectModule, TypographyModule, @@ -31,7 +30,6 @@ import { CipherFormContainer } from "../../cipher-form-container"; ButtonModule, JslibModule, ReactiveFormsModule, - SectionComponent, SectionHeaderComponent, CardComponent, FormFieldModule, diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html index 40a8954b05a..5fd3e08f22d 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ "itemDetails" | i18n }}

diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts index dcbc4e8c92f..82615368b91 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts @@ -19,7 +19,6 @@ import { CardComponent, FormFieldModule, IconButtonModule, - SectionComponent, SectionHeaderComponent, SelectItemView, SelectModule, @@ -38,7 +37,6 @@ import { CipherFormContainer } from "../../cipher-form-container"; standalone: true, imports: [ CardComponent, - SectionComponent, TypographyModule, FormFieldModule, ReactiveFormsModule, diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html index e31be492f93..585f11c2ffe 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ "loginCredentials" | i18n }} @@ -127,6 +127,6 @@ > - +

diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts index 1d6ca760d3b..c6b8433dcfa 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts @@ -20,7 +20,6 @@ import { IconButtonModule, LinkModule, PopoverModule, - SectionComponent, SectionHeaderComponent, ToastService, TypographyModule, @@ -36,7 +35,6 @@ import { AutofillOptionsComponent } from "../autofill-options/autofill-options.c templateUrl: "./login-details-section.component.html", standalone: true, imports: [ - SectionComponent, ReactiveFormsModule, SectionHeaderComponent, TypographyModule, diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts index 169d3b69a9b..4d5bb49c337 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts @@ -8,7 +8,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/sdk-internal"; -import { VaultNudgesService, VaultNudgeType } from "../../../services/vault-nudges.service"; +import { NudgesService, NudgeType } from "../../../services/nudges.service"; import { NewItemNudgeComponent } from "./new-item-nudge.component"; @@ -18,19 +18,19 @@ describe("NewItemNudgeComponent", () => { let i18nService: MockProxy; let accountService: MockProxy; - let vaultNudgesService: MockProxy; + let nudgesService: MockProxy; beforeEach(async () => { i18nService = mock({ t: (key: string) => key }); accountService = mock(); - vaultNudgesService = mock(); + nudgesService = mock(); await TestBed.configureTestingModule({ imports: [NewItemNudgeComponent, CommonModule], providers: [ { provide: I18nService, useValue: i18nService }, { provide: AccountService, useValue: accountService }, - { provide: VaultNudgesService, useValue: vaultNudgesService }, + { provide: NudgesService, useValue: nudgesService }, ], }).compileComponents(); }); @@ -58,7 +58,7 @@ describe("NewItemNudgeComponent", () => { expect(component.nudgeBody).toBe( "newLoginNudgeBodyOne newLoginNudgeBodyBold newLoginNudgeBodyTwo", ); - expect(component.dismissalNudgeType).toBe(VaultNudgeType.newLoginItemStatus); + expect(component.dismissalNudgeType).toBe(NudgeType.NewLoginItemStatus); }); it("should set nudge title and body for CipherType.Card type", async () => { @@ -71,7 +71,7 @@ describe("NewItemNudgeComponent", () => { expect(component.showNewItemSpotlight).toBe(true); expect(component.nudgeTitle).toBe("newCardNudgeTitle"); expect(component.nudgeBody).toBe("newCardNudgeBody"); - expect(component.dismissalNudgeType).toBe(VaultNudgeType.newCardItemStatus); + expect(component.dismissalNudgeType).toBe(NudgeType.NewCardItemStatus); }); it("should not show anything if spotlight has been dismissed", async () => { @@ -82,22 +82,19 @@ describe("NewItemNudgeComponent", () => { await component.ngOnInit(); expect(component.showNewItemSpotlight).toBe(false); - expect(component.dismissalNudgeType).toBe(VaultNudgeType.newIdentityItemStatus); + expect(component.dismissalNudgeType).toBe(NudgeType.NewIdentityItemStatus); }); it("should set showNewItemSpotlight to false when user dismisses spotlight", async () => { component.showNewItemSpotlight = true; - component.dismissalNudgeType = VaultNudgeType.newLoginItemStatus; + component.dismissalNudgeType = NudgeType.NewLoginItemStatus; component.activeUserId = "test-user-id" as UserId; - const dismissSpy = jest.spyOn(vaultNudgesService, "dismissNudge").mockResolvedValue(); + const dismissSpy = jest.spyOn(nudgesService, "dismissNudge").mockResolvedValue(); await component.dismissNewItemSpotlight(); expect(component.showNewItemSpotlight).toBe(false); - expect(dismissSpy).toHaveBeenCalledWith( - VaultNudgeType.newLoginItemStatus, - component.activeUserId, - ); + expect(dismissSpy).toHaveBeenCalledWith(NudgeType.NewLoginItemStatus, component.activeUserId); }); }); diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts index 3029927320e..9657b7571c5 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts @@ -9,7 +9,7 @@ import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/sdk-internal"; import { SpotlightComponent } from "../../../components/spotlight/spotlight.component"; -import { VaultNudgesService, VaultNudgeType } from "../../../services/vault-nudges.service"; +import { NudgesService, NudgeType } from "../../../services/nudges.service"; @Component({ selector: "vault-new-item-nudge", @@ -23,12 +23,12 @@ export class NewItemNudgeComponent implements OnInit { showNewItemSpotlight: boolean = false; nudgeTitle: string = ""; nudgeBody: string = ""; - dismissalNudgeType: VaultNudgeType | null = null; + dismissalNudgeType: NudgeType | null = null; constructor( private i18nService: I18nService, private accountService: AccountService, - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, ) {} async ngOnInit() { @@ -39,25 +39,25 @@ export class NewItemNudgeComponent implements OnInit { const nudgeBodyOne = this.i18nService.t("newLoginNudgeBodyOne"); const nudgeBodyBold = this.i18nService.t("newLoginNudgeBodyBold"); const nudgeBodyTwo = this.i18nService.t("newLoginNudgeBodyTwo"); - this.dismissalNudgeType = VaultNudgeType.newLoginItemStatus; + this.dismissalNudgeType = NudgeType.NewLoginItemStatus; this.nudgeTitle = this.i18nService.t("newLoginNudgeTitle"); this.nudgeBody = `${nudgeBodyOne} ${nudgeBodyBold} ${nudgeBodyTwo}`; break; } case CipherType.Card: - this.dismissalNudgeType = VaultNudgeType.newCardItemStatus; + this.dismissalNudgeType = NudgeType.NewCardItemStatus; this.nudgeTitle = this.i18nService.t("newCardNudgeTitle"); this.nudgeBody = this.i18nService.t("newCardNudgeBody"); break; case CipherType.Identity: - this.dismissalNudgeType = VaultNudgeType.newIdentityItemStatus; + this.dismissalNudgeType = NudgeType.NewIdentityItemStatus; this.nudgeTitle = this.i18nService.t("newIdentityNudgeTitle"); this.nudgeBody = this.i18nService.t("newIdentityNudgeBody"); break; case CipherType.SecureNote: - this.dismissalNudgeType = VaultNudgeType.newNoteItemStatus; + this.dismissalNudgeType = NudgeType.NewNoteItemStatus; this.nudgeTitle = this.i18nService.t("newNoteNudgeTitle"); this.nudgeBody = this.i18nService.t("newNoteNudgeBody"); break; @@ -66,7 +66,7 @@ export class NewItemNudgeComponent implements OnInit { const sshPartOne = this.i18nService.t("newSshNudgeBodyOne"); const sshPartTwo = this.i18nService.t("newSshNudgeBodyTwo"); - this.dismissalNudgeType = VaultNudgeType.newSshItemStatus; + this.dismissalNudgeType = NudgeType.NewSshItemStatus; this.nudgeTitle = this.i18nService.t("newSshNudgeTitle"); this.nudgeBody = `${sshPartOne}
${sshPartTwo}`; break; @@ -75,23 +75,19 @@ export class NewItemNudgeComponent implements OnInit { throw new Error("Unsupported cipher type"); } this.showNewItemSpotlight = await this.checkHasSpotlightDismissed( - this.dismissalNudgeType as VaultNudgeType, + this.dismissalNudgeType as NudgeType, this.activeUserId, ); } async dismissNewItemSpotlight() { if (this.dismissalNudgeType && this.activeUserId) { - await this.vaultNudgesService.dismissNudge( - this.dismissalNudgeType, - this.activeUserId as UserId, - ); + await this.nudgesService.dismissNudge(this.dismissalNudgeType, this.activeUserId as UserId); this.showNewItemSpotlight = false; } } - async checkHasSpotlightDismissed(nudgeType: VaultNudgeType, userId: UserId): Promise { - return !(await firstValueFrom(this.vaultNudgesService.showNudge$(nudgeType, userId))) - .hasSpotlightDismissed; + async checkHasSpotlightDismissed(nudgeType: NudgeType, userId: UserId): Promise { + return await firstValueFrom(this.nudgesService.showNudgeSpotlight$(nudgeType, userId)); } } diff --git a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html index 4e1c0c5cfd9..b919ed69f0d 100644 --- a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html +++ b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html @@ -1,4 +1,4 @@ - +

{{ "typeSshKey" | i18n }} @@ -35,4 +35,4 @@ - +

diff --git a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts index 500bb886f7a..fcf2ba0d9f7 100644 --- a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts +++ b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts @@ -16,7 +16,6 @@ import { CardComponent, FormFieldModule, IconButtonModule, - SectionComponent, SectionHeaderComponent, SelectModule, TypographyModule, @@ -32,7 +31,6 @@ import { CipherFormContainer } from "../../cipher-form-container"; standalone: true, imports: [ CardComponent, - SectionComponent, TypographyModule, FormFieldModule, ReactiveFormsModule, diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index 98286e4bbb2..68eac4f0da2 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -3,7 +3,6 @@ import { inject, Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -21,13 +20,10 @@ function isSetEqual(a: Set, b: Set) { export class DefaultCipherFormService implements CipherFormService { private cipherService: CipherService = inject(CipherService); private accountService: AccountService = inject(AccountService); - private apiService: ApiService = inject(ApiService); async decryptCipher(cipher: Cipher): Promise { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return await this.cipherService.decrypt(cipher, activeUserId); } async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise { @@ -46,9 +42,7 @@ export class DefaultCipherFormService implements CipherFormService { // Creating a new cipher if (cipher.id == null) { savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin); - return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), - ); + return await this.cipherService.decrypt(savedCipher, activeUserId); } if (config.originalCipher == null) { @@ -100,8 +94,6 @@ export class DefaultCipherFormService implements CipherFormService { return null; } - return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), - ); + return await this.cipherService.decrypt(savedCipher, activeUserId); } } diff --git a/libs/vault/src/cipher-view/additional-options/additional-options.component.html b/libs/vault/src/cipher-view/additional-options/additional-options.component.html index cc74d4e3a68..aa6d339dcd7 100644 --- a/libs/vault/src/cipher-view/additional-options/additional-options.component.html +++ b/libs/vault/src/cipher-view/additional-options/additional-options.component.html @@ -1,4 +1,4 @@ - +

{{ "additionalOptions" | i18n }}

@@ -18,4 +18,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/additional-options/additional-options.component.ts b/libs/vault/src/cipher-view/additional-options/additional-options.component.ts index 9f8f034d59b..0f2c99800f8 100644 --- a/libs/vault/src/cipher-view/additional-options/additional-options.component.ts +++ b/libs/vault/src/cipher-view/additional-options/additional-options.component.ts @@ -6,7 +6,6 @@ import { IconButtonModule, CardComponent, InputModule, - SectionComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, @@ -22,7 +21,6 @@ import { CardComponent, IconButtonModule, InputModule, - SectionComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html index a794946cb89..67ded3f8358 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html @@ -1,4 +1,4 @@ - +

{{ "attachments" | i18n }}

@@ -21,4 +21,4 @@ - +
diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts index f2249175f86..04e69fcccd6 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts @@ -15,7 +15,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ItemModule, IconButtonModule, - SectionComponent, SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; @@ -32,7 +31,6 @@ import { DownloadAttachmentComponent } from "../../components/download-attachmen JslibModule, ItemModule, IconButtonModule, - SectionComponent, SectionHeaderComponent, TypographyModule, DownloadAttachmentComponent, diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html index aa3e05b9aab..22049b2a72e 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html @@ -1,4 +1,4 @@ - +

{{ "autofillOptions" | i18n }}

@@ -41,4 +41,4 @@ - +
diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts index 9b12139b00e..bab37324993 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts @@ -14,7 +14,6 @@ import { CardComponent, FormFieldModule, IconButtonModule, - SectionComponent, SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; @@ -27,7 +26,6 @@ import { CommonModule, JslibModule, CardComponent, - SectionComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.html b/libs/vault/src/cipher-view/card-details/card-details-view.component.html index ff61addd7db..9d2fa45ba9e 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.html +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.html @@ -1,4 +1,4 @@ - +

{{ setSectionTitle }}

@@ -93,4 +93,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.ts b/libs/vault/src/cipher-view/card-details/card-details-view.component.ts index 95117adaa78..2d1b2800c79 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.ts +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.ts @@ -9,8 +9,6 @@ import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { - CardComponent, - SectionComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, @@ -26,8 +24,6 @@ import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only- imports: [ CommonModule, JslibModule, - CardComponent, - SectionComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index 2492ed0cd81..7c60d35965f 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -1,4 +1,4 @@ - +

{{ "customFields" | i18n }}

@@ -115,4 +115,4 @@ - +
diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts index a8f4e1417b4..4d20eceb285 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts @@ -17,7 +17,6 @@ import { IconButtonModule, FormFieldModule, InputModule, - SectionComponent, SectionHeaderComponent, TypographyModule, CheckboxModule, @@ -37,7 +36,6 @@ import { VaultAutosizeReadOnlyTextArea } from "../../directives/readonly-textare IconButtonModule, FormFieldModule, InputModule, - SectionComponent, SectionHeaderComponent, TypographyModule, CheckboxModule, diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html index 5ba535d0436..32bf1befb66 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html @@ -1,4 +1,4 @@ - +

{{ "itemDetails" | i18n }}

@@ -80,4 +80,4 @@ - +
diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts index 10452272895..1335df74bb9 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts @@ -11,7 +11,6 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CardComponent, FormFieldModule, - SectionComponent, SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; @@ -26,7 +25,6 @@ import { OrgIconDirective } from "../../components/org-icon.directive"; CommonModule, JslibModule, CardComponent, - SectionComponent, SectionHeaderComponent, TypographyModule, OrgIconDirective, diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index dc1168b7f01..256aec34b50 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -1,4 +1,4 @@ - +

{{ "loginCredentials" | i18n }}

@@ -164,4 +164,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index d99ac438f27..5f7d0b32201 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -23,7 +23,6 @@ import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstraction import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FormFieldModule, - SectionComponent, SectionHeaderComponent, TypographyModule, LinkModule, @@ -47,7 +46,6 @@ type TotpCodeValues = { imports: [ CommonModule, JslibModule, - SectionComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html index 20390c0a285..f7c28ceb3f0 100644 --- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html +++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html @@ -1,4 +1,4 @@ - +

{{ "typeSshKey" | i18n }}

@@ -66,4 +66,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts index d597f4d9408..7ac0f8a6726 100644 --- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts +++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts @@ -7,15 +7,12 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; import { CardComponent, - SectionComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, IconButtonModule, } from "@bitwarden/components"; -import { OrgIconDirective } from "../../components/org-icon.directive"; - @Component({ selector: "app-sshkey-view", templateUrl: "sshkey-view.component.html", @@ -24,10 +21,8 @@ import { OrgIconDirective } from "../../components/org-icon.directive"; CommonModule, JslibModule, CardComponent, - SectionComponent, SectionHeaderComponent, TypographyModule, - OrgIconDirective, FormFieldModule, IconButtonModule, ], diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html index d2154abd098..1b0a1f48f05 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html @@ -1,4 +1,4 @@ - +

{{ "personalDetails" | i18n }}

@@ -64,9 +64,9 @@ > - +
- +

{{ "identification" | i18n }}

@@ -153,9 +153,9 @@ > - +
- +

{{ "contactInfo" | i18n }}

@@ -212,4 +212,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts index 80bf78abe6e..3b710812b36 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts @@ -6,7 +6,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FormFieldModule, IconButtonModule, - SectionComponent, SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; @@ -20,7 +19,6 @@ import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only- imports: [ NgIf, JslibModule, - SectionComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts index f621ca63101..8a4e962707d 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts @@ -6,15 +6,16 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { StateProvider } from "@bitwarden/common/platform/state"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; import { PasswordRepromptService } from "../../services/password-reprompt.service"; @@ -51,6 +52,21 @@ describe("DownloadAttachmentComponent", () => { }, } as CipherView; + const ciphers$ = new BehaviorSubject({ + "5555-444-3333": { + id: "5555-444-3333", + attachments: [ + { + id: "222-3333-4444", + fileName: "encrypted-filename", + key: "encrypted-key", + }, + ], + }, + }); + + const getFeatureFlag = jest.fn().mockResolvedValue(false); + beforeEach(async () => { showToast.mockClear(); getAttachmentData.mockClear(); @@ -60,13 +76,22 @@ describe("DownloadAttachmentComponent", () => { imports: [DownloadAttachmentComponent], providers: [ { provide: EncryptService, useValue: mock() }, - { provide: KeyService, useValue: mock() }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: StateProvider, useValue: { activeUserId$ } }, { provide: ToastService, useValue: { showToast } }, { provide: ApiService, useValue: { getAttachmentData } }, { provide: FileDownloadService, useValue: { download } }, { provide: PasswordRepromptService, useValue: mock() }, + { + provide: ConfigService, + useValue: { + getFeatureFlag, + }, + }, + { + provide: CipherService, + useValue: { ciphers$: () => ciphers$, getDecryptedAttachmentBuffer: jest.fn() }, + }, ], }).compileComponents(); }); @@ -128,10 +153,12 @@ describe("DownloadAttachmentComponent", () => { }); }); - it("shows an error toast when EncArrayBuffer fails", async () => { + it("shows an error toast when getDecryptedAttachmentBuffer fails", async () => { getAttachmentData.mockResolvedValue({ url: "https://www.downloadattachement.com" }); fetchMock.mockResolvedValue({ status: 200 }); - EncArrayBuffer.fromResponse = jest.fn().mockRejectedValue({}); + + const cipherService = TestBed.inject(CipherService) as jest.Mocked; + cipherService.getDecryptedAttachmentBuffer.mockRejectedValue(new Error()); await component.download(); diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts index e64777ebb8e..f06d6db582a 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -2,23 +2,19 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NEVER, switchMap } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { EmergencyAccessId, OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey } from "@bitwarden/common/types/key"; +import { CipherId, EmergencyAccessId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AsyncActionsModule, IconButtonModule, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; @Component({ standalone: true, @@ -42,29 +38,14 @@ export class DownloadAttachmentComponent { /** When owners/admins can mange all items and when accessing from the admin console, use the admin endpoint */ @Input() admin?: boolean = false; - /** The organization key if the cipher is associated with one */ - private orgKey: OrgKey | null = null; - constructor( private i18nService: I18nService, private apiService: ApiService, private fileDownloadService: FileDownloadService, private toastService: ToastService, - private encryptService: EncryptService, private stateProvider: StateProvider, - private keyService: KeyService, - ) { - this.stateProvider.activeUserId$ - .pipe( - switchMap((userId) => (userId !== null ? this.keyService.orgKeys$(userId) : NEVER)), - takeUntilDestroyed(), - ) - .subscribe((data: Record | null) => { - if (data) { - this.orgKey = data[this.cipher.organizationId as OrganizationId]; - } - }); - } + private cipherService: CipherService, + ) {} /** Download the attachment */ download = async () => { @@ -100,9 +81,15 @@ export class DownloadAttachmentComponent { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = this.attachment.key != null ? this.attachment.key : this.orgKey; - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const userId = await firstValueFrom(this.stateProvider.activeUserId$); + + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipher.id as CipherId, + this.attachment, + response, + userId, + ); + this.fileDownloadService.download({ fileName: this.attachment.fileName, blobData: decBuf, diff --git a/libs/vault/src/components/spotlight/spotlight.component.html b/libs/vault/src/components/spotlight/spotlight.component.html index 0c6a37914d8..3aa9a09a7e1 100644 --- a/libs/vault/src/components/spotlight/spotlight.component.html +++ b/libs/vault/src/components/spotlight/spotlight.component.html @@ -4,7 +4,13 @@

{{ title }}

-

+

+