From 3f20122e5bc9364762c1b477f79f9b99e242d05b Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Fri, 11 Mar 2022 07:16:50 +1000 Subject: [PATCH 1/8] Avoid duplicate fullSync api calls (#716) --- common/src/services/sync.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/services/sync.service.ts b/common/src/services/sync.service.ts index cc512e50..93611238 100644 --- a/common/src/services/sync.service.ts +++ b/common/src/services/sync.service.ts @@ -13,6 +13,7 @@ import { SendService } from "../abstractions/send.service"; import { SettingsService } from "../abstractions/settings.service"; import { StateService } from "../abstractions/state.service"; import { SyncService as SyncServiceAbstraction } from "../abstractions/sync.service"; +import { sequentialize } from "../misc/sequentialize"; import { CipherData } from "../models/data/cipherData"; import { CollectionData } from "../models/data/collectionData"; import { FolderData } from "../models/data/folderData"; @@ -71,6 +72,7 @@ export class SyncService implements SyncServiceAbstraction { await this.stateService.setLastSync(date.toJSON(), { userId: userId }); } + @sequentialize(() => "fullSync") async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { this.syncStarted(); const isAuthenticated = await this.stateService.getIsAuthenticated(); From 41b199ab831eff8d197e006328274dbf8b1b11bd Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 11 Mar 2022 21:00:24 +0100 Subject: [PATCH 2/8] Tweak component library slightly (#715) --- components/src/badge/badge.component.ts | 67 +++++++++++++------ components/src/badge/badge.stories.ts | 6 +- components/src/button/button.component.ts | 20 +++--- .../{Button.stories.ts => button.stories.ts} | 5 +- components/src/callout/callout.component.html | 2 +- components/src/tw-theme.css | 19 ++++-- components/tailwind.config.base.js | 10 ++- 7 files changed, 90 insertions(+), 39 deletions(-) rename components/src/button/{Button.stories.ts => button.stories.ts} (87%) diff --git a/components/src/badge/badge.component.ts b/components/src/badge/badge.component.ts index 4f62affb..e66a5748 100644 --- a/components/src/badge/badge.component.ts +++ b/components/src/badge/badge.component.ts @@ -1,39 +1,68 @@ -import { Component, Input } from "@angular/core"; +import { Directive, ElementRef, HostBinding, Input, OnChanges, OnInit } from "@angular/core"; type BadgeTypes = "primary" | "secondary" | "success" | "danger" | "warning" | "info"; const styles: Record = { - primary: ["tw-bg-primary-500", "hover:tw-bg-primary-700"], - secondary: ["tw-bg-secondary-500", "hover:tw-bg-secondary-700"], - success: ["tw-bg-success-500", "hover:tw-bg-success-700"], - danger: ["tw-bg-danger-500", "hover:tw-bg-danger-700"], - warning: ["tw-bg-warning-500", "hover:tw-bg-warning-700"], - info: ["tw-bg-info-500", "hover:tw-bg-info-700"], + primary: ["tw-bg-primary-500"], + secondary: ["tw-bg-text-muted"], + success: ["tw-bg-success-500"], + danger: ["tw-bg-danger-500"], + warning: ["tw-bg-warning-500"], + info: ["tw-bg-info-500"], }; -@Component({ - selector: "bit-badge", - template: ``, +const hoverStyles: Record = { + primary: ["hover:tw-bg-primary-700"], + secondary: ["hover:tw-bg-secondary-700"], + success: ["hover:tw-bg-success-700"], + danger: ["hover:tw-bg-danger-700"], + warning: ["hover:tw-bg-warning-700"], + info: ["hover:tw-bg-info-700"], +}; + +@Directive({ + selector: "span[bit-badge], a[bit-badge], button[bit-badge]", }) -export class BadgeComponent { - @Input() - type: BadgeTypes = "primary"; +export class BadgeComponent implements OnInit, OnChanges { + @HostBinding("class") @Input("class") classList = ""; + + @Input() badgeType: BadgeTypes = "primary"; + + private isSpan = false; + + constructor(private el: ElementRef) { + this.isSpan = el?.nativeElement?.nodeName == "SPAN"; + } + + ngOnInit(): void { + this.classList = this.classes.join(" "); + } + + ngOnChanges() { + this.ngOnInit(); + } get classes() { return [ "tw-inline-block", - "tw-py-0.5", - "tw-px-1", + "tw-py-1", + "tw-px-1.5", "tw-font-bold", "tw-leading-none", "tw-text-center", - "tw-text-contrast", - "tw-align-baseline", + "!tw-text-contrast", "tw-rounded", - "tw-border-collapse", + "tw-border-none", "tw-box-border", "tw-whitespace-no-wrap", "tw-text-xs", - ].concat(styles[this.type]); + "hover:tw-no-underline", + "focus:tw-outline-none", + "focus:tw-ring", + "focus:tw-ring-offset-2", + "focus:tw-ring-primary-700", + ] + .concat(styles[this.badgeType]) + .concat(this.isSpan ? [] : hoverStyles[this.badgeType]); } } diff --git a/components/src/badge/badge.stories.ts b/components/src/badge/badge.stories.ts index f464e230..6af7c3b8 100644 --- a/components/src/badge/badge.stories.ts +++ b/components/src/badge/badge.stories.ts @@ -13,7 +13,11 @@ export default { const Template: Story = (args: BadgeComponent) => ({ props: args, template: ` - Test Content + Span Badge +

+ Link Badge +

+ Button `, }); diff --git a/components/src/button/button.component.ts b/components/src/button/button.component.ts index a56ae245..5fcbff6a 100644 --- a/components/src/button/button.component.ts +++ b/components/src/button/button.component.ts @@ -1,4 +1,4 @@ -import { Input, HostBinding, OnChanges, Directive } from "@angular/core"; +import { Input, HostBinding, OnChanges, Directive, OnInit } from "@angular/core"; export type ButtonTypes = "primary" | "secondary" | "danger"; @@ -18,10 +18,10 @@ const buttonStyles: Record = { "!tw-text-muted", "hover:tw-bg-secondary-500", "hover:tw-border-secondary-500", - "hover:tw-text-contrast", + "hover:!tw-text-contrast", "focus:tw-bg-secondary-500", "focus:tw-border-secondary-500", - "focus:tw-text-contrast", + "focus:!tw-text-contrast", ].join(" "), danger: [ "tw-bg-transparent", @@ -29,18 +29,18 @@ const buttonStyles: Record = { "!tw-text-danger", "hover:tw-bg-danger-500", "hover:tw-border-danger-500", - "hover:tw-text-contrast", + "hover:!tw-text-contrast", "focus:tw-bg-danger-500", "focus:tw-border-danger-500", - "focus:tw-text-contrast", + "focus:!tw-text-contrast", ].join(" "), }; @Directive({ selector: "button[bit-button], a[bit-button]", }) -export class ButtonComponent implements OnChanges { - @HostBinding("class") @Input("class") classList = ""; +export class ButtonComponent implements OnInit, OnChanges { + @HostBinding("class") @Input() classList = ""; @Input() buttonType: ButtonTypes = "secondary"; @@ -48,10 +48,14 @@ export class ButtonComponent implements OnChanges { @Input() block = false; - ngOnChanges() { + ngOnInit(): void { this.classList = this.classes.join(" "); } + ngOnChanges() { + this.ngOnInit(); + } + get classes(): string[] { return [ "tw-font-semibold", diff --git a/components/src/button/Button.stories.ts b/components/src/button/button.stories.ts similarity index 87% rename from components/src/button/Button.stories.ts rename to components/src/button/button.stories.ts index c5a2461a..03816326 100644 --- a/components/src/button/Button.stories.ts +++ b/components/src/button/button.stories.ts @@ -15,7 +15,10 @@ export default { // More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args const Template: Story = (args: ButtonComponent) => ({ props: args, - template: ``, + template: ` + + Link + `, }); export const Primary = Template.bind({}); diff --git a/components/src/callout/callout.component.html b/components/src/callout/callout.component.html index 006cedf6..8e846123 100644 --- a/components/src/callout/callout.component.html +++ b/components/src/callout/callout.component.html @@ -1,5 +1,5 @@

({ DEFAULT: theme("colors.background"), From 3ec0f6977acc9374b7b379cbd59a2d7d1dbe8beb Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 14 Mar 2022 20:12:31 -0500 Subject: [PATCH 3/8] Check runtime name vs mangled name (#724) --- angular/src/directives/api-action.directive.ts | 2 +- common/src/services/cipher.service.ts | 2 +- node/src/cli/commands/login.command.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/angular/src/directives/api-action.directive.ts b/angular/src/directives/api-action.directive.ts index 8fb2574b..ed119a36 100644 --- a/angular/src/directives/api-action.directive.ts +++ b/angular/src/directives/api-action.directive.ts @@ -32,7 +32,7 @@ export class ApiActionDirective implements OnChanges { this.el.nativeElement.loading = false; if ( - (e instanceof ErrorResponse || e.constructor.name === "ErrorResponse") && + (e instanceof ErrorResponse || e.constructor.name === ErrorResponse.name) && (e as ErrorResponse).captchaRequired ) { this.logService.error("Captcha required error response: " + e.getSingleMessage()); diff --git a/common/src/services/cipher.service.ts b/common/src/services/cipher.service.ts index ec98c347..0c5a9eaa 100644 --- a/common/src/services/cipher.service.ts +++ b/common/src/services/cipher.service.ts @@ -1029,7 +1029,7 @@ export class CipherService implements CipherServiceAbstraction { ciphers[c.id].revisionDate = c.revisionDate; }; - if (cipher.constructor.name === "Array") { + if (cipher.constructor.name === Array.name) { (cipher as { id: string; revisionDate: string }[]).forEach(clearDeletedDate); } else { clearDeletedDate(cipher as { id: string; revisionDate: string }); diff --git a/node/src/cli/commands/login.command.ts b/node/src/cli/commands/login.command.ts index db6d0f61..f8c9a6b9 100644 --- a/node/src/cli/commands/login.command.ts +++ b/node/src/cli/commands/login.command.ts @@ -467,7 +467,7 @@ export class LoginCommand { } catch (e) { if ( e instanceof ErrorResponse || - (e.constructor.name === "ErrorResponse" && + (e.constructor.name === ErrorResponse.name && (e as ErrorResponse).message.includes("Captcha is invalid")) ) { return badCaptcha; From 15ad2ca3ea92e6a6969f70acb2885dfb89ca7540 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 15 Mar 2022 13:50:42 +0100 Subject: [PATCH 4/8] Add Chromatic (#719) --- .github/workflows/chromatic.yml | 41 ++ components/.storybook/main.js | 2 +- components/package-lock.json | 147 ++++++++ components/package.json | 6 +- .../src/stories/Introduction.stories.mdx | 36 +- components/src/stories/colors.stories.mdx | 85 +++++ components/src/styles.scss | 2 +- components/src/variables.scss | 357 ++++++++++++++++++ components/tailwind.config.base.js | 6 +- components/tailwind.config.js | 7 +- 10 files changed, 650 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/chromatic.yml create mode 100644 components/src/stories/colors.stories.mdx create mode 100644 components/src/variables.scss diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 00000000..f149df6a --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,41 @@ +--- +name: Chromatic + +on: push + +jobs: + chromatic: + name: Chromatic + runs-on: ubuntu-20.04 + + steps: + - name: Set up Node + uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.1.5 + with: + node-version: "16" + + - name: Checkout repo + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 + with: + fetch-depth: 0 + + - name: Cache npm + id: npm-cache + uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353 # v2.1.6 + with: + path: "~/.npm" + key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }} + + - name: Install Node dependencies + run: npm ci + working-directory: ./components + + - name: Publish to Chromatic + uses: chromaui/action@c72f0b48c8887c0ef0abe18ad865a6c1e01e73c6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + workingDir: ./components + exitOnceUploaded: true + onlyChanged: true + externals: "[\"components/**/*.scss\", \"components/tailwind.config*.js\"]" diff --git a/components/.storybook/main.js b/components/.storybook/main.js index e7c9115a..ed4cd08e 100644 --- a/components/.storybook/main.js +++ b/components/.storybook/main.js @@ -1,6 +1,6 @@ module.exports = { stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], - addons: ["@storybook/addon-links", "@storybook/addon-essentials"], + addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-a11y"], framework: "@storybook/angular", core: { builder: "webpack5", diff --git a/components/package-lock.json b/components/package-lock.json index 86bdc41e..bf65bcd0 100644 --- a/components/package-lock.json +++ b/components/package-lock.json @@ -17,6 +17,7 @@ "@angular/platform-browser": "^12.2.13", "@angular/platform-browser-dynamic": "^12.2.13", "@bitwarden/jslib-angular": "file:../angular", + "bootstrap": "4.6.0", "tslib": "^2.3.0" }, "devDependencies": { @@ -26,6 +27,7 @@ "@angular/elements": "^12.2.13", "@babel/core": "^7.16.10", "@compodoc/compodoc": "^1.1.16", + "@storybook/addon-a11y": "^6.4.19", "@storybook/addon-actions": "^6.4.18", "@storybook/addon-essentials": "^6.4.18", "@storybook/addon-links": "^6.4.18", @@ -37,6 +39,7 @@ "@webcomponents/custom-elements": "^1.5.0", "autoprefixer": "^10.4.2", "babel-loader": "^8.2.3", + "chromatic": "^6.5.2", "jasmine-core": "~3.10.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -4757,6 +4760,46 @@ "node": ">= 0.6.0" } }, + "node_modules/@storybook/addon-a11y": { + "version": "6.4.19", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.4.19.tgz", + "integrity": "sha512-dG6easap6W4AqyggVZPq8lBrhza8StA8J4eYz/GVdoXINSGtq/casV0rkmY3+SUXhPYux5oGavHo86j5I4Q/0Q==", + "dev": true, + "dependencies": { + "@storybook/addons": "6.4.19", + "@storybook/api": "6.4.19", + "@storybook/channels": "6.4.19", + "@storybook/client-logger": "6.4.19", + "@storybook/components": "6.4.19", + "@storybook/core-events": "6.4.19", + "@storybook/csf": "0.0.2--canary.87bc651.0", + "@storybook/theming": "6.4.19", + "axe-core": "^4.2.0", + "core-js": "^3.8.2", + "global": "^4.4.0", + "lodash": "^4.17.21", + "react-sizeme": "^3.0.1", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/@storybook/addon-actions": { "version": "6.4.19", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.4.19.tgz", @@ -13487,6 +13530,15 @@ "postcss": "^8.1.0" } }, + "node_modules/axe-core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz", + "integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/babel-loader": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", @@ -14023,6 +14075,19 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "node_modules/bootstrap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", + "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + }, + "peerDependencies": { + "jquery": "1.9.1 - 3", + "popper.js": "^1.16.1" + } + }, "node_modules/boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -14694,6 +14759,17 @@ "node": ">=10" } }, + "node_modules/chromatic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-6.5.2.tgz", + "integrity": "sha512-TiAJAF2we4BUflKEfiXs2CiBFvW6yNWkiXKIuBtlSLl2fj1cuueXlV5dYVVoyMDfzTX5JdEn/Bd5CVlpKCeY6A==", + "dev": true, + "bin": { + "chroma": "bin/main.cjs", + "chromatic": "bin/main.cjs", + "chromatic-cli": "bin/main.cjs" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -21897,6 +21973,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", + "peer": true + }, "node_modules/js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -25131,6 +25213,17 @@ "node": ">=6.9.0" } }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -37713,6 +37806,30 @@ "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", "dev": true }, + "@storybook/addon-a11y": { + "version": "6.4.19", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.4.19.tgz", + "integrity": "sha512-dG6easap6W4AqyggVZPq8lBrhza8StA8J4eYz/GVdoXINSGtq/casV0rkmY3+SUXhPYux5oGavHo86j5I4Q/0Q==", + "dev": true, + "requires": { + "@storybook/addons": "6.4.19", + "@storybook/api": "6.4.19", + "@storybook/channels": "6.4.19", + "@storybook/client-logger": "6.4.19", + "@storybook/components": "6.4.19", + "@storybook/core-events": "6.4.19", + "@storybook/csf": "0.0.2--canary.87bc651.0", + "@storybook/theming": "6.4.19", + "axe-core": "^4.2.0", + "core-js": "^3.8.2", + "global": "^4.4.0", + "lodash": "^4.17.21", + "react-sizeme": "^3.0.1", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, "@storybook/addon-actions": { "version": "6.4.19", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.4.19.tgz", @@ -44584,6 +44701,12 @@ "postcss-value-parser": "^4.2.0" } }, + "axe-core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz", + "integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==", + "dev": true + }, "babel-loader": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", @@ -45025,6 +45148,12 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "bootstrap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", + "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", + "requires": {} + }, "boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -45548,6 +45677,12 @@ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, + "chromatic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-6.5.2.tgz", + "integrity": "sha512-TiAJAF2we4BUflKEfiXs2CiBFvW6yNWkiXKIuBtlSLl2fj1cuueXlV5dYVVoyMDfzTX5JdEn/Bd5CVlpKCeY6A==", + "dev": true + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -51165,6 +51300,12 @@ } } }, + "jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", + "peer": true + }, "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -53720,6 +53861,12 @@ } } }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "peer": true + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", diff --git a/components/package.json b/components/package.json index 99c7c49c..7b43f9d7 100644 --- a/components/package.json +++ b/components/package.json @@ -9,7 +9,8 @@ "test": "ng test", "docs:json": "compodoc -p ./tsconfig.json -e json -d .", "storybook": "npm run docs:json && start-storybook -p 6006", - "build-storybook": "npm run docs:json && build-storybook" + "build-storybook": "npm run docs:json && build-storybook", + "chromatic": "chromatic --exit-zero-on-changes" }, "private": true, "dependencies": { @@ -22,6 +23,7 @@ "@angular/platform-browser": "^12.2.13", "@angular/platform-browser-dynamic": "^12.2.13", "@bitwarden/jslib-angular": "file:../angular", + "bootstrap": "4.6.0", "tslib": "^2.3.0" }, "devDependencies": { @@ -31,6 +33,7 @@ "@angular/elements": "^12.2.13", "@babel/core": "^7.16.10", "@compodoc/compodoc": "^1.1.16", + "@storybook/addon-a11y": "^6.4.19", "@storybook/addon-actions": "^6.4.18", "@storybook/addon-essentials": "^6.4.18", "@storybook/addon-links": "^6.4.18", @@ -42,6 +45,7 @@ "@webcomponents/custom-elements": "^1.5.0", "autoprefixer": "^10.4.2", "babel-loader": "^8.2.3", + "chromatic": "^6.5.2", "jasmine-core": "~3.10.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", diff --git a/components/src/stories/Introduction.stories.mdx b/components/src/stories/Introduction.stories.mdx index a69b74b3..5b798704 100644 --- a/components/src/stories/Introduction.stories.mdx +++ b/components/src/stories/Introduction.stories.mdx @@ -1,6 +1,6 @@ import { Meta } from "@storybook/addon-docs"; - + -# Welcome to Storybook +# Bitwarden Component Library Storybook helps you build UI components in isolation from your app's business logic, data, and context. That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA. @@ -188,8 +161,3 @@ We recommend building UIs with a [**component-driven**](https://componentdriven.

- -
- TipEdit the Markdown in{" "} - src/stories/Introduction.stories.mdx -
diff --git a/components/src/stories/colors.stories.mdx b/components/src/stories/colors.stories.mdx new file mode 100644 index 00000000..f3110135 --- /dev/null +++ b/components/src/stories/colors.stories.mdx @@ -0,0 +1,85 @@ +import { Meta } from "@storybook/addon-docs"; + + + +export const Row = (name) => ( + + {name} + + +); + +export const Table = (args) => ( + + + + + + + + + {Row("background")} + {Row("background-alt")} + {Row("background-alt2")} + + + {Row("primary-300")} + {Row("primary-500")} + {Row("primary-700")} + + + {Row("secondary-100")} + {Row("secondary-300")} + {Row("secondary-500")} + {Row("secondary-700")} + + + {Row("success-500")} + {Row("success-700")} + + + {Row("danger-500")} + {Row("danger-700")} + + + {Row("warning-500")} + {Row("warning-700")} + + + {Row("info-500")} + {Row("info-700")} + + + {Row("text-main")} + {Row("text-muted")} + {Row("text-contrast")} + +
General usage
+); + + + +# Colors + +
+ +
+ diff --git a/components/src/styles.scss b/components/src/styles.scss index beb241da..8729b921 100644 --- a/components/src/styles.scss +++ b/components/src/styles.scss @@ -1,5 +1,5 @@ @import "../../angular/src/scss/webfonts.css"; -@import "./../../../src/scss/variables"; +@import "./variables"; @import "../../angular/src/scss/bwicons/styles/style.scss"; @import "../../angular/src/scss/icons.scss"; diff --git a/components/src/variables.scss b/components/src/variables.scss new file mode 100644 index 00000000..a0bc2d88 --- /dev/null +++ b/components/src/variables.scss @@ -0,0 +1,357 @@ +$dark-icon-themes: "theme_dark"; + +$primary: #175ddc; +$primary-accent: #1252a3; +$secondary: #ced4da; +$secondary-alt: #1a3b66; +$success: #00a65a; +$info: #555555; +$warning: #bf7e16; +$danger: #dd4b39; +$white: #ffffff; + +// Bootstrap Variable Overrides + +$theme-colors: ( + "primary-accent": $primary-accent, + "secondary-alt": $secondary-alt, +); + +$body-bg: $white; +$body-color: #333333; + +$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + +$h1-font-size: 1.7rem; +$h2-font-size: 1.3rem; +$h3-font-size: 1rem; +$h4-font-size: 1rem; +$h5-font-size: 1rem; +$h6-font-size: 1rem; + +$small-font-size: 90%; +$font-size-lg: 1.15rem; +$code-font-size: 100%; + +$navbar-padding-y: 0.75rem; +$grid-gutter-width: 20px; +$card-spacer-y: 0.6rem; + +$list-group-item-padding-y: 0.6rem; +$list-group-active-color: $body-color; +$list-group-active-bg: $white; +$list-group-active-border-color: rgba(#000000, 0.125); + +$dropdown-link-color: $body-color; +$dropdown-link-hover-bg: rgba(#000000, 0.06); +$dropdown-link-active-color: $dropdown-link-color; +$dropdown-link-active-bg: rgba(#000000, 0.1); +$dropdown-item-padding-x: 1rem; + +$navbar-brand-font-size: 35px; +$navbar-brand-height: 35px; +$navbar-brand-padding-y: 0; +$navbar-dark-color: rgba($white, 0.7); +$navbar-dark-hover-color: rgba($white, 0.9); +$navbar-nav-link-padding-x: 0.8rem; + +$input-bg: #fbfbfb; +$input-focus-bg: $white; +$input-disabled-bg: #e0e0e0; +$input-placeholder-color: #b4b4b4; + +$table-accent-bg: rgba(#000000, 0.02); +$table-hover-bg: rgba(#000000, 0.03); + +$modal-backdrop-opacity: 0.3; +$btn-font-weight: 600; +$lead-font-weight: normal; + +$grid-breakpoints: ( + xs: 0, + sm: 1px, + md: 2px, + lg: 3px, + xl: 4px, +); + +$border-color: $secondary; + +// MFA Types for logo styling with no dark theme alternative + +$mfaTypes: 0, 2, 3, 4, 6; + +// Theme Variables +// Light + +$lightDangerHover: #c43421; +$lightInputColor: #465057; +$lightInputPlaceholderColor: #b6b8b8; + +// Dark + +$darkPrimary: #6a99f0; +$darkPrimary-alt: #b4ccf9; +$darkDanger: #ff8d85; +$darkDangerHover: #ffbfbb; +$darkSuccess: #52e07c; +$darkWarning: #ffeb66; +$darkInfo: #a4b0c6; +$darkLinks: #6a99f0; +$darkGrey1: #bac0ce; +$darkGrey2: #8d94a5; +$darkBlue1: #4c525f; +$darkBlue2: #3c424e; +$darkDarkBlue1: #2f343d; +$darkDarkBlue2: #1f242e; +$darkInputColor: $white; +$darkInputPlaceholderColor: $darkGrey1; + +$themes: ( + light: ( + primary: $primary, + primaryAlt: $primary-accent, + danger: $danger, + info: #343a40, + success: $success, + warning: $warning, + backgroundColor: $white, + badgeDangerBackground: $danger, + badgeDangerText: $white, + badgeInfoBackground: #555555, + badgeInfoText: $white, + badgePrimaryBackground: $primary, + badgePrimaryBackgroundHover: #134eb9, + badgePrimaryText: $white, + badgeSecondaryBackground: #ced4da, + badgeSecondaryText: #212529, + bgLightColor: #f8f9fa, + bgPrimaryColor: $primary, + borderColor: $border-color, + borderPrimaryColor: $primary, + browserInputIconsFilter: invert(0), + btnDanger: $danger, + btnDangerHover: $lightDangerHover, + btnDangerText: $white, + btnLinkText: $primary, + btnLinkTextHover: #104097, + btnOutlineDangerBackground: $input-bg, + btnOutlineDangerBackgroundHover: $danger, + btnOutlineDangerBorder: #ced4da, + btnOutlineDangerBorderHover: $danger, + btnOutlineDangerText: $danger, + btnOutlineDangerTextHover: $white, + btnOutlinePrimaryBackground: $input-bg, + btnOutlinePrimaryBackgroundHover: $primary, + btnOutlinePrimaryBorder: #ced4da, + btnOutlinePrimaryBorderHover: $primary, + btnOutlinePrimaryText: $primary, + btnOutlinePrimaryTextHover: $white, + btnOutlineSecondaryBackground: $input-bg, + btnOutlineSecondaryBackgroundHover: #ced4da, + btnOutlineSecondaryBorder: #ced4da, + btnOutlineSecondaryBorderHover: #ced4da, + btnOutlineSecondaryText: #6c757d, + btnOutlineSecondaryTextHover: #333333, + btnPrimary: $primary, + btnPrimaryBorderHover: #1249ae, + btnPrimaryHover: #134eb9, + btnPrimaryText: $white, + btnSecondary: $secondary, + btnSecondaryBorder: $secondary, + btnSecondaryBorderHover: #b1bbc4, + btnSecondaryHover: #b8c1ca, + btnSecondaryText: #212529, + btnSecondaryTextHover: #212529, + calloutBackground: #fafafa, + calloutColor: #212529, + cdkDraggingBackground: $white, + codeColor: #e83e8c, + dropdownBackground: $white, + dropdownHover: rgba(0, 0, 0, 0.06), + dropdownTextColor: $body-color, + dropdownTextMuted: #6c757d, + focus: rgb(23 93 220 / 25%), + footerBackgroundColor: #fbfbfb, + foregroundColor: $white, + headerColor: rgba(0, 0, 0, 0.03), + iconColor: #777777, + iconHover: $body-color, + imgFilter: invert(0) grayscale(0), + inputBackgroundColor: $input-bg, + inputBorderColor: $border-color, + inputDisabledBackground: #e0e0e0, + inputDisabledColor: #6c757d, + inputPlaceholderColor: $lightInputPlaceholderColor, + inputTextColor: $lightInputColor, + layoutFrontendColor: #ecf0f5, + learnMoreHover: #104097, + linkColor: $primary, + linkColorHover: #104097, + linkWeight: 400, + listItemActive: $body-color, + listItemBorder: rgba(0, 0, 0, 0.125), + loadingSvg: url("../images/loading.svg"), + logoSuffix: "dark", + mfaLogoSuffix: ".png", + navActiveBackground: $white, + navActiveWeight: 600, + navBackground: $primary, + navBackgroundAlt: $secondary-alt, + navOrgBackgroundColor: #fbfbfb, + navWeight: 600, + pwLetter: $body-color, + pwNumber: #007fde, + pwSpecial: #c40800, + pwStrengthBackground: #e9ecef, + separator: $secondary, + separatorHr: rgb(0, 0, 0, 0.1), + tableColorHover: #333333, + tableLinkColor: $primary, + tableLinkColorHover: #104097, + tableRowHover: rgba(0, 0, 0, 0.03), + tableSeparator: #dee2e6, + textColor: $body-color, + textDangerColor: $white, + textInfoColor: $white, + textHeadingColor: #333333, + textMuted: #6c757d, + textSuccessColor: $white, + textWarningColor: $white, + ), + dark: ( + primary: $darkPrimary, + primaryAlt: $darkPrimary-alt, + danger: $darkDanger, + info: $darkInfo, + success: $darkSuccess, + warning: $darkWarning, + backgroundColor: $darkDarkBlue2, + badgeDangerBackground: $darkDanger, + badgeDangerText: $darkDarkBlue2, + badgeInfoBackground: $darkInfo, + badgeInfoText: $darkDarkBlue2, + badgePrimaryBackground: $darkLinks, + badgePrimaryBackgroundHover: $darkPrimary-alt, + badgePrimaryText: $darkDarkBlue2, + badgeSecondaryBackground: $darkGrey2, + badgeSecondaryText: $darkDarkBlue2, + bgLightColor: $darkDarkBlue2, + bgPrimaryColor: $darkPrimary, + borderColor: $darkBlue1, + borderPrimaryColor: $darkPrimary, + browserInputIconsFilter: invert(1), + btnDanger: $darkDanger, + btnDangerHover: $darkDangerHover, + btnDangerText: $darkDarkBlue2, + btnLinkText: $white, + btnLinkTextHover: $darkGrey1, + btnOutlineDangerBackground: $darkDanger, + btnOutlineDangerBackgroundHover: $darkDangerHover, + btnOutlineDangerBorder: $darkDanger, + btnOutlineDangerBorderHover: $darkDangerHover, + btnOutlineDangerText: $darkDarkBlue2, + btnOutlineDangerTextHover: $darkDarkBlue2, + btnOutlinePrimaryBackground: $darkPrimary, + btnOutlinePrimaryBackgroundHover: $darkPrimary-alt, + btnOutlinePrimaryBorder: $darkPrimary, + btnOutlinePrimaryBorderHover: $darkPrimary-alt, + btnOutlinePrimaryText: $darkDarkBlue2, + btnOutlinePrimaryTextHover: $darkDarkBlue2, + btnOutlineSecondaryBackground: transparent, + btnOutlineSecondaryBackgroundHover: transparent, + btnOutlineSecondaryBorder: $darkGrey1, + btnOutlineSecondaryBorderHover: $darkGrey2, + btnOutlineSecondaryText: $white, + btnOutlineSecondaryTextHover: $darkGrey2, + btnPrimary: $darkLinks, + btnPrimaryBorderHover: $darkPrimary-alt, + btnPrimaryHover: $darkPrimary-alt, + btnPrimaryText: $darkDarkBlue2, + btnSecondary: transparent, + btnSecondaryBorder: $darkGrey1, + btnSecondaryBorderHover: $darkGrey2, + btnSecondaryHover: transparent, + btnSecondaryText: $white, + btnSecondaryTextHover: $darkGrey2, + calloutBackground: $darkBlue2, + calloutColor: $white, + cdkDraggingBackground: $darkDarkBlue1, + codeColor: #e83e8c, + dropdownBackground: $darkDarkBlue1, + dropdownHover: rgba(255, 255, 255, 0.03), + dropdownTextColor: $white, + dropdownTextMuted: #bec6cf, + focus: rgb(106 153 240 / 25%), + footerBackgroundColor: $darkBlue1, + foregroundColor: $darkDarkBlue1, + headerColor: $darkBlue1, + iconColor: #777777, + iconHover: $darkGrey2, + imgFilter: invert(1) grayscale(1), + inputBackgroundColor: transparent, + inputBorderColor: $darkGrey1, + inputDisabledBackground: $darkBlue2, + inputDisabledColor: $darkGrey1, + inputPlaceholderColor: $darkInputPlaceholderColor, + inputTextColor: $darkInputColor, + layoutFrontendColor: $darkDarkBlue2, + learnMoreHover: $darkPrimary-alt, + linkColor: $darkLinks, + linkColorHover: $darkLinks, + linkWeight: 600, + listItemActive: $darkPrimary, + listItemBorder: $darkBlue1, + loadingSvg: url("../images/loading-white.svg"), + logoSuffix: "white", + mfaLogoSuffix: "-w.png", + navActiveBackground: $darkDarkBlue2, + navActiveWeight: 600, + navBackground: $darkDarkBlue1, + navBackgroundAlt: $darkDarkBlue1, + navOrgBackgroundColor: #161c26, + navWeight: 400, + pwLetter: $white, + pwNumber: #52bdfb, + pwSpecial: #ff7c70, + pwStrengthBackground: $darkBlue2, + separator: $darkBlue1, + separatorHr: $darkBlue1, + tableColorHover: $darkGrey1, + tableLinkColor: $white, + tableLinkColorHover: $white, + tableRowHover: rgba(255, 255, 255, 0.03), + tableSeparator: $darkBlue1, + textColor: $darkGrey1, + textDangerColor: $darkDarkBlue2, + textHeadingColor: $white, + textInfoColor: $darkDarkBlue2, + textMuted: $darkGrey1, + textSuccessColor: $darkDarkBlue2, + textWarningColor: $darkDarkBlue2, + ), +); + +@mixin themify($themes: $themes) { + @each $theme, $map in $themes { + html.theme_#{$theme} & { + $theme-map: () !global; + @each $key, $submap in $map { + $value: map-get(map-get($themes, $theme), "#{$key}"); + $theme-map: map-merge( + $theme-map, + ( + $key: $value, + ) + ) !global; + } + @content; + $theme-map: null !global; + } + } +} + +@function themed($key) { + @return map-get($theme-map, $key); +} ; diff --git a/components/tailwind.config.base.js b/components/tailwind.config.base.js index 8f515f3c..8b1afb16 100644 --- a/components/tailwind.config.base.js +++ b/components/tailwind.config.base.js @@ -37,7 +37,11 @@ module.exports = { 500: "var(--color-info-500)", 700: "var(--color-info-700)", }, - "text-muted": "var(--color-text-muted)", + text: { + main: "var(--color-text-main)", + muted: "var(--color-text-muted)", + contrast: "var(--color-text-contrast)", + }, background: { DEFAULT: "var(--color-background)", alt: "var(--color-background-alt)", diff --git a/components/tailwind.config.js b/components/tailwind.config.js index c458a0ab..5f7eb298 100644 --- a/components/tailwind.config.js +++ b/components/tailwind.config.js @@ -1,6 +1,11 @@ /* eslint-disable */ const config = require("./tailwind.config.base"); -config.content = ["./src/**/*.{html,ts}", "./.storybook/preview.js"]; +config.content = ["./src/**/*.{html,ts,mdx}", "./.storybook/preview.js"]; +config.safelist = [ + { + pattern: /tw-bg-(.*)/, + }, +]; module.exports = config; From e2d95741b00b24b96275bc11334aab7324a56d3a Mon Sep 17 00:00:00 2001 From: Chad Scharf <3904944+cscharf@users.noreply.github.com> Date: Tue, 15 Mar 2022 15:55:55 -0400 Subject: [PATCH 5/8] Update SECURITY.md (#725) * Update SECURITY.md Add link to our HackerOne program for submitting potential security issues. * Revise language on SECURITY.md --- SECURITY.md | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 7a055501..e6edb96d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,39 +1,11 @@ -Bitwarden believes that working with security researchers across the globe is crucial to keeping our -users safe. If you believe you've found a security issue in our product or service, we encourage you to -notify us. We welcome working with you to resolve the issue promptly. Thanks in advance! +Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance! # Disclosure Policy -- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every - effort to quickly resolve the issue. -- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a - third-party. We may publicly disclose the issue before resolving it, if appropriate. -- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or - degradation of our service. Only interact with accounts you own or with explicit permission of the - account holder. -- If you would like to encrypt your report, please use the PGP key with long ID - `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool). - -# In-scope - -- Security issues in any current release of Bitwarden. This includes the web vault, browser extension, - and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source - code is available at https://github.com/bitwarden. - -# Exclusions - -The following bug classes are out-of scope: - -- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden), - or that we already know of. Note that some of our issue tracking is private. -- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the - upstream maintainer. -- Attacks requiring physical access to a user's device. -- Self-XSS -- Issues related to software or protocols not under Bitwarden's control -- Vulnerabilities in outdated versions of Bitwarden -- Missing security best practices that do not directly lead to a vulnerability -- Issues that do not have any impact on the general public +- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue. +- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate. +- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder. +- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool). While researching, we'd like to ask you to refrain from: @@ -42,4 +14,8 @@ While researching, we'd like to ask you to refrain from: - Social engineering (including phishing) of Bitwarden staff or contractors - Any physical attempts against Bitwarden property or data centers +# We want to help you! + +If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help. + Thank you for helping keep Bitwarden and our users safe! From f88515745c1164576929f63f72b1f06bdf79464a Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Thu, 17 Mar 2022 22:24:22 -0400 Subject: [PATCH 6/8] Remove error Response type check (#731) * Remove error Response type check Minimization is impacting type checking in a non-consistent way. The previous type check works locally, but not from build artifacts :shrug:. We only set `captchaRequired` on our errors when we want a resubmit with captcha included, so we're safe keying off that * linter --- angular/src/directives/api-action.directive.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/angular/src/directives/api-action.directive.ts b/angular/src/directives/api-action.directive.ts index ed119a36..357cefb4 100644 --- a/angular/src/directives/api-action.directive.ts +++ b/angular/src/directives/api-action.directive.ts @@ -31,10 +31,7 @@ export class ApiActionDirective implements OnChanges { (e: any) => { this.el.nativeElement.loading = false; - if ( - (e instanceof ErrorResponse || e.constructor.name === ErrorResponse.name) && - (e as ErrorResponse).captchaRequired - ) { + if ((e as ErrorResponse).captchaRequired) { this.logService.error("Captcha required error response: " + e.getSingleMessage()); return; } From 9950fb42a15bad434a4b404419ff4a87af67a27b Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Mon, 21 Mar 2022 19:32:55 +1000 Subject: [PATCH 7/8] [JslibModule] Add JslibModule (#733) --- angular/src/jslib.module.ts | 94 +++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 angular/src/jslib.module.ts diff --git a/angular/src/jslib.module.ts b/angular/src/jslib.module.ts new file mode 100644 index 00000000..215e83e4 --- /dev/null +++ b/angular/src/jslib.module.ts @@ -0,0 +1,94 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; + +import { AvatarComponent } from "./components/avatar.component"; +import { CalloutComponent } from "./components/callout.component"; +import { ExportScopeCalloutComponent } from "./components/export-scope-callout.component"; +import { IconComponent } from "./components/icon.component"; +import { BitwardenToastModule } from "./components/toastr.component"; +import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component"; +import { A11yInvalidDirective } from "./directives/a11y-invalid.directive"; +import { A11yTitleDirective } from "./directives/a11y-title.directive"; +import { ApiActionDirective } from "./directives/api-action.directive"; +import { AutofocusDirective } from "./directives/autofocus.directive"; +import { BlurClickDirective } from "./directives/blur-click.directive"; +import { BoxRowDirective } from "./directives/box-row.directive"; +import { FallbackSrcDirective } from "./directives/fallback-src.directive"; +import { InputStripSpacesDirective } from "./directives/input-strip-spaces.directive"; +import { InputVerbatimDirective } from "./directives/input-verbatim.directive"; +import { NotPremiumDirective } from "./directives/not-premium.directive"; +import { SelectCopyDirective } from "./directives/select-copy.directive"; +import { StopClickDirective } from "./directives/stop-click.directive"; +import { StopPropDirective } from "./directives/stop-prop.directive"; +import { TrueFalseValueDirective } from "./directives/true-false-value.directive"; +import { ColorPasswordPipe } from "./pipes/color-password.pipe"; +import { I18nPipe } from "./pipes/i18n.pipe"; +import { SearchCiphersPipe } from "./pipes/search-ciphers.pipe"; +import { SearchPipe } from "./pipes/search.pipe"; +import { UserNamePipe } from "./pipes/user-name.pipe"; + +@NgModule({ + imports: [ + BitwardenToastModule.forRoot({ + maxOpened: 5, + autoDismiss: true, + closeButton: true, + }), + CommonModule, + ], + declarations: [ + A11yInvalidDirective, + A11yTitleDirective, + ApiActionDirective, + AvatarComponent, + AutofocusDirective, + BlurClickDirective, + BoxRowDirective, + ColorPasswordPipe, + FallbackSrcDirective, + I18nPipe, + InputStripSpacesDirective, + InputVerbatimDirective, + NotPremiumDirective, + SearchCiphersPipe, + SearchPipe, + SelectCopyDirective, + StopClickDirective, + StopPropDirective, + TrueFalseValueDirective, + UserNamePipe, + CalloutComponent, + IconComponent, + VerifyMasterPasswordComponent, + ExportScopeCalloutComponent, + ], + exports: [ + A11yInvalidDirective, + A11yTitleDirective, + ApiActionDirective, + AvatarComponent, + AutofocusDirective, + BitwardenToastModule, + BlurClickDirective, + BoxRowDirective, + ColorPasswordPipe, + FallbackSrcDirective, + I18nPipe, + InputStripSpacesDirective, + InputVerbatimDirective, + NotPremiumDirective, + SearchCiphersPipe, + SearchPipe, + SelectCopyDirective, + StopClickDirective, + StopPropDirective, + TrueFalseValueDirective, + UserNamePipe, + CalloutComponent, + IconComponent, + VerifyMasterPasswordComponent, + ExportScopeCalloutComponent, + ], + providers: [UserNamePipe, SearchPipe], +}) +export class JslibModule {} From 5409525ea2ed69c15998724015d5e1247d3a4a48 Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Mon, 21 Mar 2022 15:46:54 -0400 Subject: [PATCH 8/8] Add ellipsis pipe (#728) * add ellipsis pipe * run prettier * Account for ellipsis length in returned string * Fix complete words case * Fix another complete words issue * fix for if there are not spaces in long value * extract length check to beginning of method * condense if statements * remove log --- angular/src/pipes/ellipsis.pipe.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 angular/src/pipes/ellipsis.pipe.ts diff --git a/angular/src/pipes/ellipsis.pipe.ts b/angular/src/pipes/ellipsis.pipe.ts new file mode 100644 index 00000000..081dba11 --- /dev/null +++ b/angular/src/pipes/ellipsis.pipe.ts @@ -0,0 +1,17 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ + name: "ellipsis", +}) +export class EllipsisPipe implements PipeTransform { + transform(value: string, limit = 25, completeWords = false, ellipsis = "...") { + if (value.length <= limit) { + return value; + } + limit -= ellipsis.length; + if (completeWords && value.length > limit && value.indexOf(" ") > 0) { + limit = value.substring(0, limit).lastIndexOf(" "); + } + return value.substring(0, limit) + ellipsis; + } +}