mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-30 22:41:33 +01:00
Merge branch 'main' into PM-12036
This commit is contained in:
commit
ced9b7c137
@ -1,29 +0,0 @@
|
|||||||
**/build
|
|
||||||
**/dist
|
|
||||||
**/coverage
|
|
||||||
.angular
|
|
||||||
storybook-static
|
|
||||||
|
|
||||||
**/node_modules
|
|
||||||
|
|
||||||
**/webpack.*.js
|
|
||||||
**/jest.config.js
|
|
||||||
|
|
||||||
apps/browser/config/config.js
|
|
||||||
apps/browser/src/auth/scripts/duo.js
|
|
||||||
apps/browser/webpack/manifest.js
|
|
||||||
|
|
||||||
apps/desktop/desktop_native
|
|
||||||
apps/desktop/src/auth/scripts/duo.js
|
|
||||||
|
|
||||||
apps/web/config.js
|
|
||||||
apps/web/scripts/*.js
|
|
||||||
apps/web/tailwind.config.js
|
|
||||||
|
|
||||||
apps/cli/config/config.js
|
|
||||||
|
|
||||||
tailwind.config.js
|
|
||||||
libs/components/tailwind.config.base.js
|
|
||||||
libs/components/tailwind.config.js
|
|
||||||
|
|
||||||
scripts/*.js
|
|
258
.eslintrc.json
258
.eslintrc.json
@ -1,258 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"webextensions": true
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.js"],
|
|
||||||
"plugins": ["@typescript-eslint", "rxjs", "rxjs-angular", "import"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"project": ["./tsconfig.eslint.json"],
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaVersion": 2020
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@angular-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
"plugin:rxjs/recommended",
|
|
||||||
"prettier",
|
|
||||||
"plugin:storybook/recommended"
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"import/parsers": {
|
|
||||||
"@typescript-eslint/parser": [".ts"]
|
|
||||||
},
|
|
||||||
"import/resolver": {
|
|
||||||
"typescript": {
|
|
||||||
"alwaysTryTypes": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/component-class-suffix": 0,
|
|
||||||
"@angular-eslint/contextual-lifecycle": 0,
|
|
||||||
"@angular-eslint/directive-class-suffix": 0,
|
|
||||||
"@angular-eslint/no-empty-lifecycle-method": 0,
|
|
||||||
"@angular-eslint/no-host-metadata-property": 0,
|
|
||||||
"@angular-eslint/no-input-rename": 0,
|
|
||||||
"@angular-eslint/no-inputs-metadata-property": 0,
|
|
||||||
"@angular-eslint/no-output-native": 0,
|
|
||||||
"@angular-eslint/no-output-on-prefix": 0,
|
|
||||||
"@angular-eslint/no-output-rename": 0,
|
|
||||||
"@angular-eslint/no-outputs-metadata-property": 0,
|
|
||||||
"@angular-eslint/use-lifecycle-interface": "error",
|
|
||||||
"@angular-eslint/use-pipe-transform-interface": 0,
|
|
||||||
"@typescript-eslint/explicit-member-accessibility": [
|
|
||||||
"error",
|
|
||||||
{ "accessibility": "no-public" }
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled
|
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
|
||||||
"@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }],
|
|
||||||
"@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }],
|
|
||||||
"@typescript-eslint/no-unused-expressions": ["error", { "allowTernary": true }],
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
|
||||||
"no-console": "error",
|
|
||||||
"import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package.
|
|
||||||
"import/order": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"alphabetize": {
|
|
||||||
"order": "asc"
|
|
||||||
},
|
|
||||||
"newlines-between": "always",
|
|
||||||
"pathGroups": [
|
|
||||||
{
|
|
||||||
"pattern": "@bitwarden/**",
|
|
||||||
"group": "external",
|
|
||||||
"position": "after"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pattern": "src/**/*",
|
|
||||||
"group": "parent",
|
|
||||||
"position": "before"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pathGroupsExcludedImportTypes": ["builtin"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rxjs-angular/prefer-takeuntil": ["error", { "alias": ["takeUntilDestroyed"] }],
|
|
||||||
"rxjs/no-exposed-subjects": ["error", { "allowProtected": true }],
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"message": "Calling `svgIcon` directly is not allowed",
|
|
||||||
"selector": "CallExpression[callee.name='svgIcon']"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message": "Accessing FormGroup using `get` is not allowed, use `.value` instead",
|
|
||||||
"selector": "ChainExpression[expression.object.callee.property.name='get'][expression.property.name='value']"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"curly": ["error", "all"],
|
|
||||||
"import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway
|
|
||||||
"import/no-restricted-paths": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"zones": [
|
|
||||||
{
|
|
||||||
"target": ["libs/**/*"],
|
|
||||||
"from": ["apps/**/*"],
|
|
||||||
"message": "Libs should not import app-specific code."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// avoid specific frameworks or large dependencies in common
|
|
||||||
"target": "./libs/common/**/*",
|
|
||||||
"from": [
|
|
||||||
// Angular
|
|
||||||
"./libs/angular/**/*",
|
|
||||||
"./node_modules/@angular*/**/*",
|
|
||||||
|
|
||||||
// Node
|
|
||||||
"./libs/node/**/*",
|
|
||||||
|
|
||||||
//Generator
|
|
||||||
"./libs/tools/generator/components/**/*",
|
|
||||||
"./libs/tools/generator/core/**/*",
|
|
||||||
"./libs/tools/generator/extensions/**/*",
|
|
||||||
|
|
||||||
// Import/export
|
|
||||||
"./libs/importer/**/*",
|
|
||||||
"./libs/tools/export/vault-export/vault-export-core/**/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// avoid import of unexported state objects
|
|
||||||
"target": [
|
|
||||||
"!(libs)/**/*",
|
|
||||||
"libs/!(common)/**/*",
|
|
||||||
"libs/common/!(src)/**/*",
|
|
||||||
"libs/common/src/!(platform)/**/*",
|
|
||||||
"libs/common/src/platform/!(state)/**/*"
|
|
||||||
],
|
|
||||||
"from": ["./libs/common/src/platform/state/**/*"],
|
|
||||||
// allow module index import
|
|
||||||
"except": ["**/state/index.ts"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.html"],
|
|
||||||
"parser": "@angular-eslint/template-parser",
|
|
||||||
"plugins": ["@angular-eslint/template", "tailwindcss"],
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/template/button-has-type": "error",
|
|
||||||
"tailwindcss/no-custom-classname": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
// uses negative lookahead to whitelist any class that doesn't start with "tw-"
|
|
||||||
// in other words: classnames that start with tw- must be valid TailwindCSS classes
|
|
||||||
"whitelist": ["(?!(tw)\\-).*"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tailwindcss/enforces-negative-arbitrary-values": "error",
|
|
||||||
"tailwindcss/enforces-shorthand": "error",
|
|
||||||
"tailwindcss/no-contradicting-classname": "error"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["apps/browser/src/**/*.ts", "libs/**/*.ts"],
|
|
||||||
"excludedFiles": [
|
|
||||||
"apps/browser/src/autofill/{content,notification}/**/*.ts",
|
|
||||||
"apps/browser/src/**/background/**/*.ts", // It's okay to have long lived listeners in the background
|
|
||||||
"apps/browser/src/platform/background.ts"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"message": "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead",
|
|
||||||
// This selector covers events like chrome.storage.onChange & chrome.runtime.onMessage
|
|
||||||
"selector": "CallExpression > [object.object.object.name='chrome'][property.name='addListener']"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message": "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead",
|
|
||||||
// This selector covers events like chrome.storage.local.onChange
|
|
||||||
"selector": "CallExpression > [object.object.object.object.name='chrome'][property.name='addListener']"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["**/*.ts"],
|
|
||||||
"excludedFiles": ["**/platform/**/*.ts"],
|
|
||||||
"rules": {
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"patterns": [
|
|
||||||
"**/platform/**/internal", // General internal pattern
|
|
||||||
// All features that have been converted to barrel files
|
|
||||||
"**/platform/messaging/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["**/src/**/*.ts"],
|
|
||||||
"excludedFiles": ["**/platform/**/*.ts"],
|
|
||||||
"rules": {
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"patterns": [
|
|
||||||
"**/platform/**/internal", // General internal pattern
|
|
||||||
// All features that have been converted to barrel files
|
|
||||||
"**/platform/messaging/**",
|
|
||||||
"**/src/**/*" // Prevent relative imports across libs.
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["bitwarden_license/bit-common/src/**/*.ts"],
|
|
||||||
"rules": {
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{ "patterns": ["@bitwarden/bit-common/*", "**/src/**/*"] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["apps/**/*.ts"],
|
|
||||||
"rules": {
|
|
||||||
// Catches static imports
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"patterns": [
|
|
||||||
"biwarden_license/**",
|
|
||||||
"@bitwarden/bit-common/*",
|
|
||||||
"@bitwarden/bit-web/*",
|
|
||||||
"**/src/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
// Catches dynamic imports, e.g. in routing modules where modules are lazy-loaded
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"message": "Don't import Bitwarden licensed code into OSS code.",
|
|
||||||
"selector": "ImportExpression > Literal.source[value=/.*(bitwarden_license|bit-common|bit-web).*/]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
25
.github/renovate.json
vendored
25
.github/renovate.json
vendored
@ -69,6 +69,26 @@
|
|||||||
"commitMessagePrefix": "[deps] Auth:",
|
"commitMessagePrefix": "[deps] Auth:",
|
||||||
"reviewers": ["team:team-auth-dev"]
|
"reviewers": ["team:team-auth-dev"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"matchPackageNames": [
|
||||||
|
"@angular-eslint/schematics",
|
||||||
|
"angular-eslint",
|
||||||
|
"eslint-config-prettier",
|
||||||
|
"eslint-import-resolver-typescript",
|
||||||
|
"eslint-plugin-import",
|
||||||
|
"eslint-plugin-rxjs-angular",
|
||||||
|
"eslint-plugin-rxjs",
|
||||||
|
"eslint-plugin-storybook",
|
||||||
|
"eslint-plugin-tailwindcss",
|
||||||
|
"eslint",
|
||||||
|
"husky",
|
||||||
|
"lint-staged",
|
||||||
|
"typescript-eslint"
|
||||||
|
],
|
||||||
|
"description": "Architecture owned dependencies",
|
||||||
|
"commitMessagePrefix": "[deps] Architecture:",
|
||||||
|
"reviewers": ["team:dept-architecture"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"matchPackageNames": [
|
"matchPackageNames": [
|
||||||
"@angular-eslint/eslint-plugin-template",
|
"@angular-eslint/eslint-plugin-template",
|
||||||
@ -88,9 +108,8 @@
|
|||||||
"husky",
|
"husky",
|
||||||
"lint-staged"
|
"lint-staged"
|
||||||
],
|
],
|
||||||
"description": "Architecture owned dependencies",
|
"groupName": "Linting minor-patch",
|
||||||
"commitMessagePrefix": "[deps] Architecture:",
|
"matchUpdateTypes": ["minor", "patch"]
|
||||||
"reviewers": ["team:dept-architecture"]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchPackageNames": [
|
"matchPackageNames": [
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { dirname, join } from "path";
|
import { dirname, join } from "path";
|
||||||
|
|
||||||
import { StorybookConfig } from "@storybook/angular";
|
import { StorybookConfig } from "@storybook/angular";
|
||||||
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
const config: StorybookConfig = {
|
||||||
stories: [
|
stories: [
|
||||||
@ -29,6 +30,8 @@ const config: StorybookConfig = {
|
|||||||
getAbsolutePath("@storybook/addon-designs"),
|
getAbsolutePath("@storybook/addon-designs"),
|
||||||
getAbsolutePath("@storybook/addon-interactions"),
|
getAbsolutePath("@storybook/addon-interactions"),
|
||||||
{
|
{
|
||||||
|
// @storybook/addon-docs is part of @storybook/addon-essentials
|
||||||
|
// eslint-disable-next-line storybook/no-uninstalled-addons
|
||||||
name: "@storybook/addon-docs",
|
name: "@storybook/addon-docs",
|
||||||
options: {
|
options: {
|
||||||
mdxPluginOptions: {
|
mdxPluginOptions: {
|
||||||
|
@ -50,10 +50,14 @@ const darkTheme = create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getPreferredColorScheme = () => {
|
export const getPreferredColorScheme = () => {
|
||||||
if (!globalThis || !globalThis.matchMedia) return "light";
|
if (!globalThis || !globalThis.matchMedia) {
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
|
|
||||||
const isDarkThemePreferred = globalThis.matchMedia("(prefers-color-scheme: dark)").matches;
|
const isDarkThemePreferred = globalThis.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
if (isDarkThemePreferred) return "dark";
|
if (isDarkThemePreferred) {
|
||||||
|
return "dark";
|
||||||
|
}
|
||||||
|
|
||||||
return "light";
|
return "light";
|
||||||
};
|
};
|
||||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -7,5 +7,6 @@
|
|||||||
"**/_locales/*[^n]/messages.json": true
|
"**/_locales/*[^n]/messages.json": true
|
||||||
},
|
},
|
||||||
"rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"],
|
"rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"],
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"eslint.useFlatConfig": true
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"webextensions": true
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["src/**/*.ts"],
|
|
||||||
"excludedFiles": [
|
|
||||||
"src/**/{content,popup,spec}/**/*.ts",
|
|
||||||
"src/**/autofill/{notification,overlay}/**/*.ts",
|
|
||||||
"src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts",
|
|
||||||
"src/**/*.spec.ts"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-restricted-globals": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"name": "window",
|
|
||||||
"message": "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -13,9 +13,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -34,9 +32,7 @@ export class RegisterComponent extends BaseRegisterComponent {
|
|||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
keyService: KeyService,
|
keyService: KeyService,
|
||||||
apiService: ApiService,
|
apiService: ApiService,
|
||||||
stateService: StateService,
|
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
auditService: AuditService,
|
auditService: AuditService,
|
||||||
@ -51,9 +47,7 @@ export class RegisterComponent extends BaseRegisterComponent {
|
|||||||
i18nService,
|
i18nService,
|
||||||
keyService,
|
keyService,
|
||||||
apiService,
|
apiService,
|
||||||
stateService,
|
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
passwordGenerationService,
|
|
||||||
environmentService,
|
environmentService,
|
||||||
logService,
|
logService,
|
||||||
auditService,
|
auditService,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { dirname, join } from "path";
|
import path, { dirname, join } from "path";
|
||||||
import path from "path";
|
|
||||||
import type { StorybookConfig } from "@storybook/web-components-webpack5";
|
import type { StorybookConfig } from "@storybook/web-components-webpack5";
|
||||||
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
||||||
|
|
||||||
const getAbsolutePath = (value: string): string =>
|
const getAbsolutePath = (value: string): string =>
|
||||||
dirname(require.resolve(join(value, "package.json")));
|
dirname(require.resolve(join(value, "package.json")));
|
||||||
|
@ -47,6 +47,6 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
*ngIf="showOverlay"
|
*ngIf="showOverlay"
|
||||||
class="tw-absolute tw-w-full tw-h-full tw-bg-background-alt tw-inset-0 tw-bg-opacity-80 tw-z-50"
|
class="tw-absolute tw-size-full tw-bg-background-alt tw-inset-0 tw-bg-opacity-80 tw-z-50"
|
||||||
></div>
|
></div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
<ng-content select="[slot=above-scroll-area]"></ng-content>
|
<ng-content select="[slot=above-scroll-area]"></ng-content>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="tw-max-w-screen-sm tw-mx-auto tw-overflow-y-auto tw-flex tw-flex-col tw-w-full tw-h-full tw-styled-scrollbar"
|
class="tw-max-w-screen-sm tw-mx-auto tw-overflow-y-auto tw-flex tw-flex-col tw-size-full tw-styled-scrollbar"
|
||||||
data-testid="popup-layout-scroll-region"
|
data-testid="popup-layout-scroll-region"
|
||||||
(scroll)="handleScroll($event)"
|
(scroll)="handleScroll($event)"
|
||||||
[ngClass]="{ 'tw-invisible': loading }"
|
[ngClass]="{ 'tw-invisible': loading }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="tw-max-w-screen-sm tw-mx-auto tw-flex-1 tw-flex tw-flex-col tw-h-full tw-w-full"
|
class="tw-max-w-screen-sm tw-mx-auto tw-flex-1 tw-flex tw-flex-col tw-size-full"
|
||||||
[ngClass]="{ 'tw-p-3 bit-compact:tw-p-2': !disablePadding }"
|
[ngClass]="{ 'tw-p-3 bit-compact:tw-p-2': !disablePadding }"
|
||||||
>
|
>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
|
@ -11,11 +11,11 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs
|
|||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { DialogService, ItemModule } from "@bitwarden/components";
|
import { DialogService, ItemModule } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { FamiliesPolicyService } from "../../../../billing/services/families-policy.service";
|
||||||
import { BrowserApi } from "../../../../platform/browser/browser-api";
|
import { BrowserApi } from "../../../../platform/browser/browser-api";
|
||||||
import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component";
|
import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component";
|
||||||
import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component";
|
import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component";
|
||||||
import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component";
|
import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component";
|
||||||
import { FamiliesPolicyService } from "../../../../services/families-policy.service";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "more-from-bitwarden-page-v2.component.html",
|
templateUrl: "more-from-bitwarden-page-v2.component.html",
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
*ngIf="showBadge$ | async"
|
*ngIf="showBadge$ | async"
|
||||||
class="tw-flex tw-items-center tw-justify-center tw-z-10 tw-absolute tw-rounded-full tw-h-[15px] tw-w-[15px] tw-top-[1px] tw-right-[1px] tw-text-notification-600 tw-text-[8px] tw-border-notification-600 tw-border-[0.5px] tw-border-solid tw-bg-notification-100 tw-leading-normal"
|
class="tw-flex tw-items-center tw-justify-center tw-z-10 tw-absolute tw-rounded-full tw-size-[15px] tw-top-[1px] tw-right-[1px] tw-text-notification-600 tw-text-[8px] tw-border-notification-600 tw-border-[0.5px] tw-border-solid tw-bg-notification-100 tw-leading-normal"
|
||||||
data-testid="filter-badge"
|
data-testid="filter-badge"
|
||||||
>
|
>
|
||||||
{{ numberOfAppliedFilters$ | async }}
|
{{ numberOfAppliedFilters$ | async }}
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
<form
|
<form
|
||||||
[formGroup]="filterForm"
|
[formGroup]="filterForm"
|
||||||
class="tw-gap-2 tw-mt-2 tw-grid tw-grid-cols-2 sm:tw-grid-cols-3 lg:tw-grid-cols-4"
|
class="tw-gap-2 tw-mt-2 tw-grid tw-grid-cols-2 sm:tw-grid-cols-3 lg:tw-grid-cols-4"
|
||||||
|
*ngIf="allFilters$ | async as allFilters"
|
||||||
>
|
>
|
||||||
<ng-container *ngIf="organizations$ | async as organizations">
|
<ng-container *ngIf="allFilters.organizations as organizations">
|
||||||
<bit-chip-select
|
<bit-chip-select
|
||||||
*ngIf="organizations.length"
|
*ngIf="organizations.length"
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -14,7 +15,7 @@
|
|||||||
>
|
>
|
||||||
</bit-chip-select>
|
</bit-chip-select>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="collections$ | async as collections">
|
<ng-container *ngIf="allFilters.collections as collections">
|
||||||
<bit-chip-select
|
<bit-chip-select
|
||||||
*ngIf="collections.length"
|
*ngIf="collections.length"
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -25,7 +26,7 @@
|
|||||||
>
|
>
|
||||||
</bit-chip-select>
|
</bit-chip-select>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="folders$ | async as folders">
|
<ng-container *ngIf="allFilters.folders as folders">
|
||||||
<bit-chip-select
|
<bit-chip-select
|
||||||
*ngIf="folders.length"
|
*ngIf="folders.length"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ReactiveFormsModule } from "@angular/forms";
|
import { ReactiveFormsModule } from "@angular/forms";
|
||||||
|
import { combineLatest, map } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { ChipSelectComponent } from "@bitwarden/components";
|
import { ChipSelectComponent } from "@bitwarden/components";
|
||||||
@ -20,5 +21,20 @@ export class VaultListFiltersComponent {
|
|||||||
protected folders$ = this.vaultPopupListFiltersService.folders$;
|
protected folders$ = this.vaultPopupListFiltersService.folders$;
|
||||||
protected cipherTypes = this.vaultPopupListFiltersService.cipherTypes;
|
protected cipherTypes = this.vaultPopupListFiltersService.cipherTypes;
|
||||||
|
|
||||||
|
// Combine all filters into a single observable to eliminate the filters from loading separately in the UI.
|
||||||
|
protected allFilters$ = combineLatest([
|
||||||
|
this.organizations$,
|
||||||
|
this.collections$,
|
||||||
|
this.folders$,
|
||||||
|
]).pipe(
|
||||||
|
map(([organizations, collections, folders]) => {
|
||||||
|
return {
|
||||||
|
organizations,
|
||||||
|
collections,
|
||||||
|
folders,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
constructor(private vaultPopupListFiltersService: VaultPopupListFiltersService) {}
|
constructor(private vaultPopupListFiltersService: VaultPopupListFiltersService) {}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { ScrollingModule } from "@angular/cdk/scrolling";
|
import { CdkVirtualScrollableElement, ScrollingModule } from "@angular/cdk/scrolling";
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, DestroyRef, OnDestroy, OnInit } from "@angular/core";
|
import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { RouterLink } from "@angular/router";
|
import { RouterLink } from "@angular/router";
|
||||||
import { combineLatest, Observable, shareReplay, switchMap } from "rxjs";
|
import { combineLatest, filter, map, Observable, shareReplay, switchMap, take } from "rxjs";
|
||||||
import { filter, map, take } from "rxjs/operators";
|
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
@ -19,6 +18,7 @@ import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-he
|
|||||||
import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component";
|
import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component";
|
||||||
import { VaultPopupItemsService } from "../../services/vault-popup-items.service";
|
import { VaultPopupItemsService } from "../../services/vault-popup-items.service";
|
||||||
import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service";
|
import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service";
|
||||||
|
import { VaultPopupScrollPositionService } from "../../services/vault-popup-scroll-position.service";
|
||||||
|
|
||||||
import { BlockedInjectionBanner } from "./blocked-injection-banner/blocked-injection-banner.component";
|
import { BlockedInjectionBanner } from "./blocked-injection-banner/blocked-injection-banner.component";
|
||||||
import {
|
import {
|
||||||
@ -58,7 +58,9 @@ enum VaultState {
|
|||||||
DecryptionFailureDialogComponent,
|
DecryptionFailureDialogComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class VaultV2Component implements OnInit, OnDestroy {
|
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
|
||||||
|
|
||||||
cipherType = CipherType;
|
cipherType = CipherType;
|
||||||
|
|
||||||
protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$;
|
protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$;
|
||||||
@ -88,9 +90,12 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
protected VaultStateEnum = VaultState;
|
protected VaultStateEnum = VaultState;
|
||||||
|
|
||||||
|
private allFilters$ = this.vaultPopupListFiltersService.allFilters$;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private vaultPopupItemsService: VaultPopupItemsService,
|
private vaultPopupItemsService: VaultPopupItemsService,
|
||||||
private vaultPopupListFiltersService: VaultPopupListFiltersService,
|
private vaultPopupListFiltersService: VaultPopupListFiltersService,
|
||||||
|
private vaultScrollPositionService: VaultPopupScrollPositionService,
|
||||||
private destroyRef: DestroyRef,
|
private destroyRef: DestroyRef,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
@ -119,6 +124,17 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
if (this.virtualScrollElement) {
|
||||||
|
// The filters component can cause the size of the virtual scroll element to change,
|
||||||
|
// which can cause the scroll position to be land in the wrong spot. To fix this,
|
||||||
|
// wait until all filters are populated before restoring the scroll position.
|
||||||
|
this.allFilters$.pipe(take(1), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||||
|
this.vaultScrollPositionService.start(this.virtualScrollElement!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.cipherService.failedToDecryptCiphers$
|
this.cipherService.failedToDecryptCiphers$
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -134,5 +150,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {}
|
ngOnDestroy(): void {
|
||||||
|
this.vaultScrollPositionService.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,15 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp
|
|||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||||
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { CopyCipherFieldService } from "@bitwarden/vault";
|
import { CopyCipherFieldService } from "@bitwarden/vault";
|
||||||
|
|
||||||
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
||||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||||
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
|
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
|
||||||
|
import { VaultPopupScrollPositionService } from "../../../services/vault-popup-scroll-position.service";
|
||||||
|
|
||||||
import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service";
|
import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service";
|
||||||
import { ViewV2Component } from "./view-v2.component";
|
import { ViewV2Component } from "./view-v2.component";
|
||||||
@ -44,6 +47,10 @@ describe("ViewV2Component", () => {
|
|||||||
const collect = jest.fn().mockResolvedValue(null);
|
const collect = jest.fn().mockResolvedValue(null);
|
||||||
const doAutofill = jest.fn().mockResolvedValue(true);
|
const doAutofill = jest.fn().mockResolvedValue(true);
|
||||||
const copy = jest.fn().mockResolvedValue(true);
|
const copy = jest.fn().mockResolvedValue(true);
|
||||||
|
const back = jest.fn().mockResolvedValue(null);
|
||||||
|
const openSimpleDialog = jest.fn().mockResolvedValue(true);
|
||||||
|
const stop = jest.fn();
|
||||||
|
const showToast = jest.fn();
|
||||||
|
|
||||||
const mockCipher = {
|
const mockCipher = {
|
||||||
id: "122-333-444",
|
id: "122-333-444",
|
||||||
@ -54,7 +61,7 @@ describe("ViewV2Component", () => {
|
|||||||
password: "test-password",
|
password: "test-password",
|
||||||
totp: "123",
|
totp: "123",
|
||||||
},
|
},
|
||||||
};
|
} as unknown as CipherView;
|
||||||
|
|
||||||
const mockVaultPopupAutofillService = {
|
const mockVaultPopupAutofillService = {
|
||||||
doAutofill,
|
doAutofill,
|
||||||
@ -68,13 +75,21 @@ describe("ViewV2Component", () => {
|
|||||||
const mockCipherService = {
|
const mockCipherService = {
|
||||||
get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }),
|
get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }),
|
||||||
getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}),
|
getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}),
|
||||||
|
deleteWithServer: jest.fn().mockResolvedValue(undefined),
|
||||||
|
softDeleteWithServer: jest.fn().mockResolvedValue(undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
mockCipherService.deleteWithServer.mockClear();
|
||||||
|
mockCipherService.softDeleteWithServer.mockClear();
|
||||||
mockNavigate.mockClear();
|
mockNavigate.mockClear();
|
||||||
collect.mockClear();
|
collect.mockClear();
|
||||||
doAutofill.mockClear();
|
doAutofill.mockClear();
|
||||||
copy.mockClear();
|
copy.mockClear();
|
||||||
|
stop.mockClear();
|
||||||
|
openSimpleDialog.mockClear();
|
||||||
|
back.mockClear();
|
||||||
|
showToast.mockClear();
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [ViewV2Component],
|
imports: [ViewV2Component],
|
||||||
@ -84,9 +99,12 @@ describe("ViewV2Component", () => {
|
|||||||
{ provide: LogService, useValue: mock<LogService>() },
|
{ provide: LogService, useValue: mock<LogService>() },
|
||||||
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
||||||
{ provide: ConfigService, useValue: mock<ConfigService>() },
|
{ provide: ConfigService, useValue: mock<ConfigService>() },
|
||||||
{ provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() },
|
{ provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>({ back }) },
|
||||||
{ provide: ActivatedRoute, useValue: { queryParams: params$ } },
|
{ provide: ActivatedRoute, useValue: { queryParams: params$ } },
|
||||||
{ provide: EventCollectionService, useValue: { collect } },
|
{ provide: EventCollectionService, useValue: { collect } },
|
||||||
|
{ provide: VaultPopupScrollPositionService, useValue: { stop } },
|
||||||
|
{ provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService },
|
||||||
|
{ provide: ToastService, useValue: { showToast } },
|
||||||
{
|
{
|
||||||
provide: I18nService,
|
provide: I18nService,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -98,7 +116,6 @@ describe("ViewV2Component", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService },
|
|
||||||
{
|
{
|
||||||
provide: AccountService,
|
provide: AccountService,
|
||||||
useValue: accountService,
|
useValue: accountService,
|
||||||
@ -114,7 +131,13 @@ describe("ViewV2Component", () => {
|
|||||||
useValue: mockCopyCipherFieldService,
|
useValue: mockCopyCipherFieldService,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
})
|
||||||
|
.overrideProvider(DialogService, {
|
||||||
|
useValue: {
|
||||||
|
openSimpleDialog,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ViewV2Component);
|
fixture = TestBed.createComponent(ViewV2Component);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
@ -223,4 +246,130 @@ describe("ViewV2Component", () => {
|
|||||||
expect(closeSpy).toHaveBeenCalledTimes(1);
|
expect(closeSpy).toHaveBeenCalledTimes(1);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("delete", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.cipher = mockCipher;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens confirmation modal", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(openSimpleDialog).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("navigates back", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(back).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops scroll position service", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(stop).toHaveBeenCalledTimes(1);
|
||||||
|
expect(stop).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deny confirmation", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
openSimpleDialog.mockResolvedValue(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not delete the cipher", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(mockCipherService.deleteWithServer).not.toHaveBeenCalled();
|
||||||
|
expect(mockCipherService.softDeleteWithServer).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not interact with side effects", () => {
|
||||||
|
expect(back).not.toHaveBeenCalled();
|
||||||
|
expect(stop).not.toHaveBeenCalled();
|
||||||
|
expect(showToast).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("accept confirmation", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
openSimpleDialog.mockResolvedValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("soft delete", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(mockCipher as any).isDeleted = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens confirmation dialog", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(openSimpleDialog).toHaveBeenCalledTimes(1);
|
||||||
|
expect(openSimpleDialog).toHaveBeenCalledWith({
|
||||||
|
content: {
|
||||||
|
key: "deleteItemConfirmation",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
key: "deleteItem",
|
||||||
|
},
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls soft delete", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(mockCipherService.softDeleteWithServer).toHaveBeenCalled();
|
||||||
|
expect(mockCipherService.deleteWithServer).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows toast", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(showToast).toHaveBeenCalledWith({
|
||||||
|
variant: "success",
|
||||||
|
title: null,
|
||||||
|
message: "deletedItem",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hard delete", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(mockCipher as any).isDeleted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens confirmation dialog", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(openSimpleDialog).toHaveBeenCalledTimes(1);
|
||||||
|
expect(openSimpleDialog).toHaveBeenCalledWith({
|
||||||
|
content: {
|
||||||
|
key: "permanentlyDeleteItemConfirmation",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
key: "deleteItem",
|
||||||
|
},
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls soft delete", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(mockCipherService.deleteWithServer).toHaveBeenCalled();
|
||||||
|
expect(mockCipherService.softDeleteWithServer).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows toast", async () => {
|
||||||
|
await component.delete();
|
||||||
|
|
||||||
|
expect(showToast).toHaveBeenCalledWith({
|
||||||
|
variant: "success",
|
||||||
|
title: null,
|
||||||
|
message: "permanentlyDeletedItem",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -49,6 +49,7 @@ import { PopOutComponent } from "../../../../../platform/popup/components/pop-ou
|
|||||||
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
|
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
|
||||||
import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service";
|
import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service";
|
||||||
import { BrowserViewPasswordHistoryService } from "../../../services/browser-view-password-history.service";
|
import { BrowserViewPasswordHistoryService } from "../../../services/browser-view-password-history.service";
|
||||||
|
import { VaultPopupScrollPositionService } from "../../../services/vault-popup-scroll-position.service";
|
||||||
import { closeViewVaultItemPopout, VaultPopoutType } from "../../../utils/vault-popout-window";
|
import { closeViewVaultItemPopout, VaultPopoutType } from "../../../utils/vault-popout-window";
|
||||||
|
|
||||||
import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component";
|
import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component";
|
||||||
@ -113,6 +114,7 @@ export class ViewV2Component {
|
|||||||
private popupRouterCacheService: PopupRouterCacheService,
|
private popupRouterCacheService: PopupRouterCacheService,
|
||||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||||
private copyCipherFieldService: CopyCipherFieldService,
|
private copyCipherFieldService: CopyCipherFieldService,
|
||||||
|
private popupScrollPositionService: VaultPopupScrollPositionService,
|
||||||
) {
|
) {
|
||||||
this.subscribeToParams();
|
this.subscribeToParams();
|
||||||
}
|
}
|
||||||
@ -202,6 +204,7 @@ export class ViewV2Component {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.popupScrollPositionService.stop(true);
|
||||||
await this.popupRouterCacheService.back();
|
await this.popupRouterCacheService.back();
|
||||||
|
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
|
@ -370,6 +370,9 @@ export class VaultPopupListFiltersService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** Organizations, collection, folders filters. */
|
||||||
|
allFilters$ = combineLatest([this.organizations$, this.collections$, this.folders$]);
|
||||||
|
|
||||||
/** Updates the stored state for filter visibility. */
|
/** Updates the stored state for filter visibility. */
|
||||||
async updateFilterVisibility(isVisible: boolean): Promise<void> {
|
async updateFilterVisibility(isVisible: boolean): Promise<void> {
|
||||||
await this.filterVisibilityState.update(() => isVisible);
|
await this.filterVisibilityState.update(() => isVisible);
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
import { CdkVirtualScrollableElement } from "@angular/cdk/scrolling";
|
||||||
|
import { fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||||
|
import { NavigationEnd, Router } from "@angular/router";
|
||||||
|
import { Subject, Subscription } from "rxjs";
|
||||||
|
|
||||||
|
import { VaultPopupScrollPositionService } from "./vault-popup-scroll-position.service";
|
||||||
|
|
||||||
|
describe("VaultPopupScrollPositionService", () => {
|
||||||
|
let service: VaultPopupScrollPositionService;
|
||||||
|
const events$ = new Subject();
|
||||||
|
const unsubscribe = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
unsubscribe.mockClear();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
VaultPopupScrollPositionService,
|
||||||
|
{ provide: Router, useValue: { events: events$ } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
service = TestBed.inject(VaultPopupScrollPositionService);
|
||||||
|
|
||||||
|
// set up dummy values
|
||||||
|
service["scrollPosition"] = 234;
|
||||||
|
service["scrollSubscription"] = { unsubscribe } as unknown as Subscription;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("router events", () => {
|
||||||
|
it("does not reset service when navigating to `/tabs/vault`", fakeAsync(() => {
|
||||||
|
const event = new NavigationEnd(22, "/tabs/vault", "");
|
||||||
|
events$.next(event);
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(service["scrollPosition"]).toBe(234);
|
||||||
|
expect(service["scrollSubscription"]).not.toBeNull();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("resets values when navigating to other tab pages", fakeAsync(() => {
|
||||||
|
const event = new NavigationEnd(23, "/tabs/generator", "");
|
||||||
|
events$.next(event);
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(service["scrollPosition"]).toBeNull();
|
||||||
|
expect(unsubscribe).toHaveBeenCalled();
|
||||||
|
expect(service["scrollSubscription"]).toBeNull();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("stop", () => {
|
||||||
|
it("removes scroll listener", () => {
|
||||||
|
service.stop();
|
||||||
|
|
||||||
|
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
||||||
|
expect(service["scrollSubscription"]).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resets stored values", () => {
|
||||||
|
service.stop(true);
|
||||||
|
|
||||||
|
expect(service["scrollPosition"]).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("start", () => {
|
||||||
|
const elementScrolled$ = new Subject();
|
||||||
|
const focus = jest.fn();
|
||||||
|
const nativeElement = {
|
||||||
|
scrollTop: 0,
|
||||||
|
querySelector: jest.fn(() => ({ focus })),
|
||||||
|
addEventListener: jest.fn(),
|
||||||
|
style: {
|
||||||
|
visibility: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const virtualElement = {
|
||||||
|
elementScrolled: () => elementScrolled$,
|
||||||
|
getElementRef: () => ({ nativeElement }),
|
||||||
|
scrollTo: jest.fn(),
|
||||||
|
} as unknown as CdkVirtualScrollableElement;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// remove the actual subscription created by `.subscribe`
|
||||||
|
service["scrollSubscription"]?.unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initial scroll position", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(virtualElement.scrollTo as jest.Mock).mockClear();
|
||||||
|
nativeElement.querySelector.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not scroll when `scrollPosition` is null", () => {
|
||||||
|
service["scrollPosition"] = null;
|
||||||
|
|
||||||
|
service.start(virtualElement);
|
||||||
|
|
||||||
|
expect(virtualElement.scrollTo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("scrolls the virtual element to `scrollPosition`", fakeAsync(() => {
|
||||||
|
service["scrollPosition"] = 500;
|
||||||
|
nativeElement.scrollTop = 500;
|
||||||
|
|
||||||
|
service.start(virtualElement);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(virtualElement.scrollTo).toHaveBeenCalledWith({ behavior: "instant", top: 500 });
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("scroll listener", () => {
|
||||||
|
it("unsubscribes from any existing subscription", () => {
|
||||||
|
service.start(virtualElement);
|
||||||
|
|
||||||
|
expect(unsubscribe).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("subscribes to `elementScrolled`", fakeAsync(() => {
|
||||||
|
virtualElement.measureScrollOffset = jest.fn(() => 455);
|
||||||
|
|
||||||
|
service.start(virtualElement);
|
||||||
|
|
||||||
|
elementScrolled$.next(null); // first subscription is skipped by `skip(1)`
|
||||||
|
elementScrolled$.next(null);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(virtualElement.measureScrollOffset).toHaveBeenCalledTimes(1);
|
||||||
|
expect(virtualElement.measureScrollOffset).toHaveBeenCalledWith("top");
|
||||||
|
expect(service["scrollPosition"]).toBe(455);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,81 @@
|
|||||||
|
import { CdkVirtualScrollableElement } from "@angular/cdk/scrolling";
|
||||||
|
import { inject, Injectable } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
import { NavigationEnd, Router } from "@angular/router";
|
||||||
|
import { filter, skip, Subscription } from "rxjs";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: "root",
|
||||||
|
})
|
||||||
|
export class VaultPopupScrollPositionService {
|
||||||
|
private router = inject(Router);
|
||||||
|
|
||||||
|
/** Path of the vault screen */
|
||||||
|
private readonly vaultPath = "/tabs/vault";
|
||||||
|
|
||||||
|
/** Current scroll position relative to the top of the viewport. */
|
||||||
|
private scrollPosition: number | null = null;
|
||||||
|
|
||||||
|
/** Subscription associated with the virtual scroll element. */
|
||||||
|
private scrollSubscription: Subscription | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.router.events
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(),
|
||||||
|
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
|
||||||
|
)
|
||||||
|
.subscribe((event) => {
|
||||||
|
this.resetListenerForNavigation(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Scrolls the user to the stored scroll position and starts tracking scroll of the page. */
|
||||||
|
start(virtualScrollElement: CdkVirtualScrollableElement) {
|
||||||
|
if (this.hasScrollPosition()) {
|
||||||
|
// Use `setTimeout` to scroll after rendering is complete
|
||||||
|
setTimeout(() => {
|
||||||
|
virtualScrollElement.scrollTo({ top: this.scrollPosition!, behavior: "instant" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollSubscription?.unsubscribe();
|
||||||
|
|
||||||
|
// Skip the first scroll event to avoid settings the scroll from the above `scrollTo` call
|
||||||
|
this.scrollSubscription = virtualScrollElement
|
||||||
|
?.elementScrolled()
|
||||||
|
.pipe(skip(1))
|
||||||
|
.subscribe(() => {
|
||||||
|
const offset = virtualScrollElement.measureScrollOffset("top");
|
||||||
|
this.scrollPosition = offset;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stops the scroll listener from updating the stored location. */
|
||||||
|
stop(reset?: true) {
|
||||||
|
this.scrollSubscription?.unsubscribe();
|
||||||
|
this.scrollSubscription = null;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
this.scrollPosition = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true when a scroll position has been stored. */
|
||||||
|
hasScrollPosition() {
|
||||||
|
return this.scrollPosition !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Conditionally resets the scroll listeners based on the ending path of the navigation */
|
||||||
|
private resetListenerForNavigation(event: NavigationEnd): void {
|
||||||
|
// The vault page is the target of the scroll listener, return early
|
||||||
|
if (event.url === this.vaultPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all other tab pages reset the scroll position
|
||||||
|
if (event.url.startsWith("/tabs/")) {
|
||||||
|
this.stop(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable no-undef, @typescript-eslint/no-var-requires */
|
/* eslint-disable no-undef, @typescript-eslint/no-require-imports */
|
||||||
const config = require("../../libs/components/tailwind.config.base");
|
const config = require("../../libs/components/tailwind.config.base");
|
||||||
|
|
||||||
config.content = [
|
config.content = [
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../../../libs/admin-console/.eslintrc.json"
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"rules": {
|
|
||||||
"no-console": "off"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
import "module-alias/register";
|
import "module-alias/register";
|
||||||
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
@ -12,9 +12,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = "RegisterComponent";
|
const BroadcasterSubscriptionId = "RegisterComponent";
|
||||||
@ -32,9 +30,7 @@ export class RegisterComponent extends BaseRegisterComponent implements OnInit,
|
|||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
keyService: KeyService,
|
keyService: KeyService,
|
||||||
apiService: ApiService,
|
apiService: ApiService,
|
||||||
stateService: StateService,
|
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
private broadcasterService: BroadcasterService,
|
private broadcasterService: BroadcasterService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
@ -51,9 +47,7 @@ export class RegisterComponent extends BaseRegisterComponent implements OnInit,
|
|||||||
i18nService,
|
i18nService,
|
||||||
keyService,
|
keyService,
|
||||||
apiService,
|
apiService,
|
||||||
stateService,
|
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
passwordGenerationService,
|
|
||||||
environmentService,
|
environmentService,
|
||||||
logService,
|
logService,
|
||||||
auditService,
|
auditService,
|
||||||
|
@ -22,6 +22,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { SshKeyPasswordPromptComponent } from "@bitwarden/importer/ui";
|
import { SshKeyPasswordPromptComponent } from "@bitwarden/importer/ui";
|
||||||
@ -148,6 +149,16 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the cipher when an attachment is altered.
|
||||||
|
* Note: This only updates the `attachments` and `revisionDate`
|
||||||
|
* properties to ensure any in-progress edits are not lost.
|
||||||
|
*/
|
||||||
|
patchCipherAttachments(cipher: CipherView) {
|
||||||
|
this.cipher.attachments = cipher.attachments;
|
||||||
|
this.cipher.revisionDate = cipher.revisionDate;
|
||||||
|
}
|
||||||
|
|
||||||
async importSshKeyFromClipboard(password: string = "") {
|
async importSshKeyFromClipboard(password: string = "") {
|
||||||
const key = await this.platformUtilsService.readFromClipboard();
|
const key = await this.platformUtilsService.readFromClipboard();
|
||||||
const parsedKey = await ipc.platform.sshAgent.importKey(key, password);
|
const parsedKey = await ipc.platform.sshAgent.importKey(key, password);
|
||||||
|
@ -159,11 +159,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter);
|
||||||
await this.vaultFilterComponent.reloadOrganizations();
|
await this.vaultFilterComponent.reloadOrganizations();
|
||||||
break;
|
break;
|
||||||
case "refreshCiphers":
|
|
||||||
// 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.vaultItemsComponent.refresh();
|
|
||||||
break;
|
|
||||||
case "modalShown":
|
case "modalShown":
|
||||||
this.showingModal = true;
|
this.showingModal = true;
|
||||||
break;
|
break;
|
||||||
@ -535,9 +530,19 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
let madeAttachmentChanges = false;
|
let madeAttachmentChanges = false;
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||||
childComponent.onUploadedAttachment.subscribe(() => (madeAttachmentChanges = true));
|
childComponent.onUploadedAttachment.subscribe((cipher) => {
|
||||||
|
madeAttachmentChanges = true;
|
||||||
|
// Update the edit component cipher with the updated cipher,
|
||||||
|
// which is needed because the revision date is updated when an attachment is altered
|
||||||
|
this.addEditComponent.patchCipherAttachments(cipher);
|
||||||
|
});
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||||
childComponent.onDeletedAttachment.subscribe(() => (madeAttachmentChanges = true));
|
childComponent.onDeletedAttachment.subscribe((cipher) => {
|
||||||
|
madeAttachmentChanges = true;
|
||||||
|
// Update the edit component cipher with the updated cipher,
|
||||||
|
// which is needed because the revision date is updated when an attachment is altered
|
||||||
|
this.addEditComponent.patchCipherAttachments(cipher);
|
||||||
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||||
this.modal.onClosed.subscribe(async () => {
|
this.modal.onClosed.subscribe(async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable no-undef, @typescript-eslint/no-var-requires */
|
/* eslint-disable no-undef, @typescript-eslint/no-require-imports */
|
||||||
const config = require("../../libs/components/tailwind.config.base");
|
const config = require("../../libs/components/tailwind.config.base");
|
||||||
|
|
||||||
config.content = [
|
config.content = [
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"patterns": [
|
|
||||||
"**/app/core/*",
|
|
||||||
"**/reports/*",
|
|
||||||
"**/app/shared/*",
|
|
||||||
"**/organizations/settings/*",
|
|
||||||
"**/organizations/policies/*",
|
|
||||||
"@bitwarden/web-vault/*",
|
|
||||||
"src/**/*",
|
|
||||||
"bitwarden_license"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../../../../libs/admin-console/.eslintrc.json"
|
|
||||||
}
|
|
@ -7,7 +7,7 @@
|
|||||||
<div
|
<div
|
||||||
class="tw-flex tw-h-32 tw-bg-secondary-100 tw-items-center tw-justify-center tw-pb-2 tw-px-6 lg:tw-pb-4 lg:tw-px-12"
|
class="tw-flex tw-h-32 tw-bg-secondary-100 tw-items-center tw-justify-center tw-pb-2 tw-px-6 lg:tw-pb-4 lg:tw-px-12"
|
||||||
>
|
>
|
||||||
<div class="tw-flex tw-items-center tw-justify-center tw-h-28 tw-w-28 lg:tw-w-40">
|
<div class="tw-flex tw-items-center tw-justify-center tw-size-28 lg:tw-w-40">
|
||||||
<img
|
<img
|
||||||
#imageEle
|
#imageEle
|
||||||
[src]="image"
|
[src]="image"
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<div class="tw-p-5">
|
<div class="tw-p-5">
|
||||||
<h3 class="tw-text-main tw-text-lg tw-font-semibold">{{ name }}</h3>
|
<h3 class="tw-text-main tw-text-lg tw-font-semibold">{{ name }}</h3>
|
||||||
<a
|
<a
|
||||||
class="tw-block tw-mb-0 tw-font-bold hover:tw-no-underline focus:tw-outline-none after:tw-content-[''] after:tw-block after:tw-absolute after:tw-w-full after:tw-h-full after:tw-left-0 after:tw-top-0"
|
class="tw-block tw-mb-0 tw-font-bold hover:tw-no-underline focus:tw-outline-none after:tw-content-[''] after:tw-block after:tw-absolute after:tw-size-full after:tw-left-0 after:tw-top-0"
|
||||||
[href]="linkURL"
|
[href]="linkURL"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -3,7 +3,6 @@ import { LayoutModule } from "@angular/cdk/layout";
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { FormsModule } from "@angular/forms";
|
import { FormsModule } from "@angular/forms";
|
||||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
import { InfiniteScrollDirective } from "ngx-infinite-scroll";
|
|
||||||
|
|
||||||
import { AppComponent } from "./app.component";
|
import { AppComponent } from "./app.component";
|
||||||
import { CoreModule } from "./core";
|
import { CoreModule } from "./core";
|
||||||
@ -23,7 +22,6 @@ import { WildcardRoutingModule } from "./wildcard-routing.module";
|
|||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
InfiniteScrollDirective,
|
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
LayoutModule,
|
LayoutModule,
|
||||||
OssRoutingModule,
|
OssRoutingModule,
|
||||||
|
@ -17,9 +17,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service";
|
import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service";
|
||||||
@ -45,9 +43,7 @@ export class RegisterFormComponent extends BaseRegisterComponent implements OnIn
|
|||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
keyService: KeyService,
|
keyService: KeyService,
|
||||||
apiService: ApiService,
|
apiService: ApiService,
|
||||||
stateService: StateService,
|
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
@ -64,9 +60,7 @@ export class RegisterFormComponent extends BaseRegisterComponent implements OnIn
|
|||||||
i18nService,
|
i18nService,
|
||||||
keyService,
|
keyService,
|
||||||
apiService,
|
apiService,
|
||||||
stateService,
|
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
passwordGenerationService,
|
|
||||||
environmentService,
|
environmentService,
|
||||||
logService,
|
logService,
|
||||||
auditService,
|
auditService,
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
'!tw-outline-[3px] tw-outline-primary-600 hover:tw-outline-[3px] hover:tw-outline-primary-600':
|
'!tw-outline-[3px] tw-outline-primary-600 hover:tw-outline-[3px] hover:tw-outline-primary-600':
|
||||||
customColorSelected,
|
customColorSelected,
|
||||||
}"
|
}"
|
||||||
class="tw-relative tw-flex tw-h-24 tw-w-24 tw-cursor-pointer tw-place-content-center tw-content-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600"
|
class="tw-relative tw-flex tw-size-24 tw-cursor-pointer tw-place-content-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600"
|
||||||
[style.background-color]="customColor$ | async"
|
[style.background-color]="customColor$ | async"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
@ -37,7 +37,7 @@
|
|||||||
></i>
|
></i>
|
||||||
<input
|
<input
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class="tw-absolute tw-bottom-0 tw-right-0 tw-h-px tw-w-px tw-border-none tw-bg-transparent tw-opacity-0"
|
class="tw-absolute tw-bottom-0 tw-right-0 tw-size-px tw-border-none tw-bg-transparent tw-opacity-0"
|
||||||
#colorPicker
|
#colorPicker
|
||||||
type="color"
|
type="color"
|
||||||
[ngModel]="customColor$ | async"
|
[ngModel]="customColor$ | async"
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<button
|
<button
|
||||||
[bitPopoverTriggerFor]="infoPopover"
|
[bitPopoverTriggerFor]="infoPopover"
|
||||||
type="button"
|
type="button"
|
||||||
class="tw-border-none tw-bg-transparent tw-text-primary-600 tw-flex tw-items-center tw-h-4 tw-w-4"
|
class="tw-border-none tw-bg-transparent tw-text-primary-600 tw-flex tw-items-center tw-size-4"
|
||||||
[position]="'right-start'"
|
[position]="'right-start'"
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
@ -15,7 +15,7 @@
|
|||||||
</bit-popover>
|
</bit-popover>
|
||||||
<i
|
<i
|
||||||
*ngIf="asyncActionLoading"
|
*ngIf="asyncActionLoading"
|
||||||
class="bwi bwi-spinner bwi-spin tw-flex tw-items-center tw-h-4 tw-w-4"
|
class="bwi bwi-spinner bwi-spin tw-flex tw-items-center tw-size-4"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<div class="tw-mb-1 tw-items-center" *ngIf="annualPlan !== null">
|
<div class="tw-mb-1 tw-items-center" *ngIf="annualPlan !== null">
|
||||||
<label class="tw- tw-block tw-text-main" for="annual">
|
<label class="tw- tw-block tw-text-main" for="annual">
|
||||||
<input
|
<input
|
||||||
class="tw-h-4 tw-w-4 tw-align-middle"
|
class="tw-size-4 tw-align-middle"
|
||||||
id="annual"
|
id="annual"
|
||||||
name="cadence"
|
name="cadence"
|
||||||
type="radio"
|
type="radio"
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<div class="tw-mb-1 tw-items-center" *ngIf="monthlyPlan !== null">
|
<div class="tw-mb-1 tw-items-center" *ngIf="monthlyPlan !== null">
|
||||||
<label class="tw- tw-block tw-text-main" for="monthly">
|
<label class="tw- tw-block tw-text-main" for="monthly">
|
||||||
<input
|
<input
|
||||||
class="tw-h-4 tw-w-4 tw-align-middle"
|
class="tw-size-4 tw-align-middle"
|
||||||
id="monthly"
|
id="monthly"
|
||||||
name="cadence"
|
name="cadence"
|
||||||
type="radio"
|
type="radio"
|
||||||
|
@ -23,7 +23,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { FreeTrial } from "../../../core/types/free-trial";
|
|
||||||
import { TrialFlowService } from "../../services/trial-flow.service";
|
import { TrialFlowService } from "../../services/trial-flow.service";
|
||||||
import {
|
import {
|
||||||
AddCreditDialogResult,
|
AddCreditDialogResult,
|
||||||
@ -33,6 +32,7 @@ import {
|
|||||||
AdjustPaymentDialogComponent,
|
AdjustPaymentDialogComponent,
|
||||||
AdjustPaymentDialogResultType,
|
AdjustPaymentDialogResultType,
|
||||||
} from "../../shared/adjust-payment-dialog/adjust-payment-dialog.component";
|
} from "../../shared/adjust-payment-dialog/adjust-payment-dialog.component";
|
||||||
|
import { FreeTrial } from "../../types/free-trial";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./organization-payment-method.component.html",
|
templateUrl: "./organization-payment-method.component.html",
|
||||||
|
@ -16,11 +16,11 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { FreeTrial } from "../../core/types/free-trial";
|
|
||||||
import {
|
import {
|
||||||
ChangePlanDialogResultType,
|
ChangePlanDialogResultType,
|
||||||
openChangePlanDialog,
|
openChangePlanDialog,
|
||||||
} from "../organizations/change-plan-dialog.component";
|
} from "../organizations/change-plan-dialog.component";
|
||||||
|
import { FreeTrial } from "../types/free-trial";
|
||||||
|
|
||||||
@Injectable({ providedIn: "root" })
|
@Injectable({ providedIn: "root" })
|
||||||
export class TrialFlowService {
|
export class TrialFlowService {
|
||||||
|
@ -24,8 +24,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { FreeTrial } from "../../core/types/free-trial";
|
|
||||||
import { TrialFlowService } from "../services/trial-flow.service";
|
import { TrialFlowService } from "../services/trial-flow.service";
|
||||||
|
import { FreeTrial } from "../types/free-trial";
|
||||||
|
|
||||||
import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component";
|
import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component";
|
||||||
import {
|
import {
|
||||||
|
@ -25,13 +25,13 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { ToastService } from "@bitwarden/components";
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { AcceptOrganizationInviteService } from "../../../auth/organization-invite/accept-organization.service";
|
||||||
import {
|
import {
|
||||||
OrganizationCreatedEvent,
|
OrganizationCreatedEvent,
|
||||||
SubscriptionProduct,
|
SubscriptionProduct,
|
||||||
TrialOrganizationType,
|
TrialOrganizationType,
|
||||||
} from "../../../billing/accounts/trial-initiation/trial-billing-step.component";
|
} from "../../../billing/accounts/trial-initiation/trial-billing-step.component";
|
||||||
import { RouterService } from "../../../core/router.service";
|
import { RouterService } from "../../../core/router.service";
|
||||||
import { AcceptOrganizationInviteService } from "../../organization-invite/accept-organization.service";
|
|
||||||
import { VerticalStepperComponent } from "../vertical-stepper/vertical-stepper.component";
|
import { VerticalStepperComponent } from "../vertical-stepper/vertical-stepper.component";
|
||||||
|
|
||||||
export type InitiationPath =
|
export type InitiationPath =
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user