diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 061929fc49c..3c0d212bfc1 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -531,6 +531,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -2267,6 +2270,23 @@ "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2409,6 +2429,10 @@ "message": "Optionally require a password for users to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendPasswordDescV2": { + "message": "Require this password to view the Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "sendNotesDesc": { "message": "Private notes about this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2514,6 +2538,9 @@ "hideEmail": { "message": "Hide my email address from recipients." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "sendOptionsPolicyInEffect": { "message": "One or more organization policies are affecting your Send options." }, diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f54f1de1dd5..62d83b19009 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -610,8 +610,6 @@ export default class MainBackground { migrationRunner, ); - this.themeStateService = new DefaultThemeStateService(this.globalStateProvider); - this.masterPasswordService = new MasterPasswordService( this.stateProvider, this.stateService, @@ -785,6 +783,11 @@ export default class MainBackground { this.authService, ); + this.themeStateService = new DefaultThemeStateService( + this.globalStateProvider, + this.configService, + ); + this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService); this.cipherService = new CipherService( diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts index 7ca073d51b0..12f5c540409 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -59,8 +59,6 @@ export class AppearanceV2Component implements OnInit { { name: i18nService.t("systemDefault"), value: ThemeType.System }, { name: i18nService.t("light"), value: ThemeType.Light }, { name: i18nService.t("dark"), value: ThemeType.Dark }, - { name: "Nord", value: ThemeType.Nord }, - { name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark }, ]; } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 4562653978b..40ce2fec5d9 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2024.9.0", + "version": "2024.9.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/app/services/desktop-theme.service.ts b/apps/desktop/src/app/services/desktop-theme.service.ts new file mode 100644 index 00000000000..321aff677df --- /dev/null +++ b/apps/desktop/src/app/services/desktop-theme.service.ts @@ -0,0 +1,25 @@ +import { map } from "rxjs"; + +import { ThemeType } from "@bitwarden/common/platform/enums"; +import { GlobalStateProvider } from "@bitwarden/common/platform/state"; +import { + THEME_SELECTION, + ThemeStateService, +} from "@bitwarden/common/platform/theming/theme-state.service"; + +export class DesktopThemeStateService implements ThemeStateService { + private readonly selectedThemeState = this.globalStateProvider.get(THEME_SELECTION); + + selectedTheme$ = this.selectedThemeState.state$.pipe(map((theme) => theme ?? this.defaultTheme)); + + constructor( + private globalStateProvider: GlobalStateProvider, + private defaultTheme: ThemeType = ThemeType.System, + ) {} + + async setSelectedTheme(theme: ThemeType): Promise { + await this.selectedThemeState.update(() => theme, { + shouldUpdate: (currentTheme) => currentTheme !== theme, + }); + } +} diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index d4b51ca1c7e..d5672f54c0f 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -66,6 +66,7 @@ import { SystemService } from "@bitwarden/common/platform/services/system.servic import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service"; +import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService } from "@bitwarden/components"; @@ -93,6 +94,7 @@ import { SearchBarService } from "../layout/search/search-bar.service"; import { DesktopFileDownloadService } from "./desktop-file-download.service"; import { DesktopSetPasswordJitService } from "./desktop-set-password-jit.service"; +import { DesktopThemeStateService } from "./desktop-theme.service"; import { InitService } from "./init.service"; import { NativeMessagingManifestService } from "./native-messaging-manifest.service"; import { RendererCryptoFunctionService } from "./renderer-crypto-function.service"; @@ -212,6 +214,11 @@ const safeProviders: SafeProvider[] = [ useFactory: () => fromIpcSystemTheme(), deps: [], }), + safeProvider({ + provide: ThemeStateService, + useClass: DesktopThemeStateService, + deps: [GlobalStateProvider], + }), safeProvider({ provide: EncryptedMessageHandlerService, deps: [ diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 6823bddceb8..ce08dfde2cd 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2024.9.0", + "version": "2024.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.9.0", + "version": "2024.9.1", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 18a046d5bce..e34641f1e09 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2024.9.0", + "version": "2024.9.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 887c8fb626a..5bf9373b032 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -33,6 +33,7 @@ import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -174,10 +175,10 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: ThemeStateService, - useFactory: (globalStateProvider: GlobalStateProvider) => + useFactory: (globalStateProvider: GlobalStateProvider, configService: ConfigService) => // Web chooses to have Light as the default theme - new DefaultThemeStateService(globalStateProvider, ThemeType.Light), - deps: [GlobalStateProvider], + new DefaultThemeStateService(globalStateProvider, configService, ThemeType.Light), + deps: [GlobalStateProvider, ConfigService], }), safeProvider({ provide: CLIENT_TYPE, diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index 6a04ff6071d..20942da3fe8 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -21,6 +21,7 @@ [routerLink]="[]" [queryParams]="{ itemId: cipher.id, action: extensionRefreshEnabled ? 'view' : null }" queryParamsHandling="merge" + [replaceUrl]="extensionRefreshEnabled" title="{{ 'editItemWithName' | i18n: cipher.name }}" type="button" appStopProp @@ -157,7 +158,7 @@ -
+
+ + {{ "sendPasswordDescV2" | i18n }} + + + + {{ "hideYourEmail" | i18n }} + + + {{ "privateNote" | i18n }} + + + + diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts new file mode 100644 index 00000000000..ab4ffaa9dd0 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -0,0 +1,111 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { map } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { + CardComponent, + CheckboxModule, + FormFieldModule, + IconButtonModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, +} from "@bitwarden/components"; + +import { SendFormConfig } from "../../abstractions/send-form-config.service"; +import { SendFormContainer } from "../../send-form-container"; + +@Component({ + selector: "tools-send-options", + templateUrl: "./send-options.component.html", + standalone: true, + imports: [ + SectionComponent, + SectionHeaderComponent, + TypographyModule, + JslibModule, + CardComponent, + FormFieldModule, + ReactiveFormsModule, + IconButtonModule, + CheckboxModule, + CommonModule, + ], +}) +export class SendOptionsComponent implements OnInit { + @Input({ required: true }) + config: SendFormConfig; + @Input() + originalSendView: SendView; + disableHideEmail = false; + sendOptionsForm = this.formBuilder.group({ + maxAccessCount: [null as number], + accessCount: [null as number], + notes: [null as string], + password: [null as string], + hideEmail: [false as boolean], + }); + + get hasPassword(): boolean { + return ( + this.sendOptionsForm.value.password !== null && this.sendOptionsForm.value.password !== "" + ); + } + + get shouldShowCount(): boolean { + return this.config.mode === "edit" && this.sendOptionsForm.value.maxAccessCount !== null; + } + + get viewsLeft(): number { + return this.sendOptionsForm.value.maxAccessCount + ? this.sendOptionsForm.value.maxAccessCount - this.sendOptionsForm.value.accessCount + : 0; + } + + constructor( + private sendFormContainer: SendFormContainer, + private formBuilder: FormBuilder, + private policyService: PolicyService, + ) { + this.sendFormContainer.registerChildForm("sendOptionsForm", this.sendOptionsForm); + this.policyService + .getAll$(PolicyType.SendOptions) + .pipe( + map((policies) => policies?.some((p) => p.data.disableHideEmail)), + takeUntilDestroyed(), + ) + .subscribe((disableHideEmail) => { + this.disableHideEmail = disableHideEmail; + }); + + this.sendOptionsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { + this.sendFormContainer.patchSend((send) => { + Object.assign(send, { + maxAccessCount: value.maxAccessCount, + accessCount: value.accessCount, + password: value.password, + hideEmail: value.hideEmail, + notes: value.notes, + }); + return send; + }); + }); + } + ngOnInit() { + if (this.sendFormContainer.originalSendView) { + this.sendOptionsForm.patchValue({ + maxAccessCount: this.sendFormContainer.originalSendView.maxAccessCount, + accessCount: this.sendFormContainer.originalSendView.accessCount, + password: this.sendFormContainer.originalSendView.password, + hideEmail: this.sendFormContainer.originalSendView.hideEmail, + notes: this.sendFormContainer.originalSendView.notes, + }); + } + } +} diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/base-send-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/base-send-details.component.ts index f3e2229dd2b..b5cf8ee0c76 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/base-send-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/base-send-details.component.ts @@ -1,7 +1,7 @@ import { DatePipe } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormBuilder, FormGroup, FormControl, Validators } from "@angular/forms"; +import { FormBuilder, FormControl, Validators } from "@angular/forms"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; @@ -9,11 +9,6 @@ import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendFormConfig } from "../../abstractions/send-form-config.service"; import { SendFormContainer } from "../../send-form-container"; -export type BaseSendDetailsForm = FormGroup<{ - name: FormControl; - selectedDeletionDatePreset: FormControl; -}>; - // Value = hours export enum DatePreset { OneHour = 1, @@ -38,21 +33,20 @@ export class BaseSendDetailsComponent implements OnInit { @Input() config: SendFormConfig; @Input() originalSendView?: SendView; - sendDetailsForm: BaseSendDetailsForm; customDeletionDateOption: DatePresetSelectOption | null = null; datePresetOptions: DatePresetSelectOption[] = []; + sendDetailsForm = this.formBuilder.group({ + name: new FormControl("", Validators.required), + selectedDeletionDatePreset: new FormControl(DatePreset.SevenDays || "", Validators.required), + }); + constructor( protected sendFormContainer: SendFormContainer, protected formBuilder: FormBuilder, protected i18nService: I18nService, protected datePipe: DatePipe, ) { - this.sendDetailsForm = this.formBuilder.group({ - name: new FormControl("", Validators.required), - selectedDeletionDatePreset: new FormControl(DatePreset.SevenDays || "", Validators.required), - }); - this.sendDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { this.sendFormContainer.patchSend((send) => { return Object.assign(send, { diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html index 47e1fc6059a..e5b99828fe4 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html @@ -1,5 +1,5 @@ - +

{{ "sendDetails" | i18n }}

@@ -13,16 +13,27 @@ *ngIf="config.sendType === TextSendType" [config]="config" [originalSendView]="originalSendView" - [sendDetailsForm]="sendDetailsForm" > + + {{ "sendLink" | i18n }} + + + + {{ "deletionDate" | i18n }} {{ "deletionDateDescV2" | i18n }} +
diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts index aadfc590c28..0b287205be4 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts @@ -1,8 +1,10 @@ import { CommonModule, DatePipe } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { @@ -17,6 +19,7 @@ import { } from "@bitwarden/components"; import { SendFormContainer } from "../../send-form-container"; +import { SendOptionsComponent } from "../options/send-options.component"; import { BaseSendDetailsComponent } from "./base-send-details.component"; import { SendFileDetailsComponent } from "./send-file-details.component"; @@ -36,6 +39,7 @@ import { SendTextDetailsComponent } from "./send-text-details.component"; ReactiveFormsModule, SendTextDetailsComponent, SendFileDetailsComponent, + SendOptionsComponent, IconButtonModule, CheckboxModule, CommonModule, @@ -45,17 +49,27 @@ import { SendTextDetailsComponent } from "./send-text-details.component"; export class SendDetailsComponent extends BaseSendDetailsComponent implements OnInit { FileSendType = SendType.File; TextSendType = SendType.Text; + sendLink: string | null = null; constructor( protected sendFormContainer: SendFormContainer, protected formBuilder: FormBuilder, protected i18nService: I18nService, protected datePipe: DatePipe, + protected environmentService: EnvironmentService, ) { super(sendFormContainer, formBuilder, i18nService, datePipe); } + async getSendLink() {} + async ngOnInit() { await super.ngOnInit(); + if (!this.originalSendView) { + return; + } + const env = await firstValueFrom(this.environmentService.environment$); + this.sendLink = + env.getSendUrl() + this.originalSendView.accessId + "/" + this.originalSendView.urlB64Key; } } diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts index 86c9fa96f1a..61391e20725 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts @@ -1,14 +1,7 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { - FormBuilder, - FormControl, - FormGroup, - Validators, - ReactiveFormsModule, - FormsModule, -} from "@angular/forms"; +import { FormBuilder, Validators, ReactiveFormsModule, FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; @@ -19,14 +12,6 @@ import { ButtonModule, FormFieldModule, SectionComponent } from "@bitwarden/comp import { SendFormConfig } from "../../abstractions/send-form-config.service"; import { SendFormContainer } from "../../send-form-container"; -import { BaseSendDetailsForm } from "./base-send-details.component"; - -type BaseSendFileDetailsForm = FormGroup<{ - file: FormControl; -}>; - -export type SendFileDetailsForm = BaseSendFileDetailsForm & BaseSendDetailsForm; - @Component({ selector: "tools-send-file-details", templateUrl: "./send-file-details.component.html", @@ -44,10 +29,10 @@ export type SendFileDetailsForm = BaseSendFileDetailsForm & BaseSendDetailsForm; export class SendFileDetailsComponent implements OnInit { @Input() config: SendFormConfig; @Input() originalSendView?: SendView; - @Input() sendDetailsForm: BaseSendDetailsForm; - baseSendFileDetailsForm: BaseSendFileDetailsForm; - sendFileDetailsForm: SendFileDetailsForm; + sendFileDetailsForm = this.formBuilder.group({ + file: this.formBuilder.control(null, Validators.required), + }); FileSendType = SendType.File; fileName = ""; @@ -56,12 +41,6 @@ export class SendFileDetailsComponent implements OnInit { private formBuilder: FormBuilder, protected sendFormContainer: SendFormContainer, ) { - this.baseSendFileDetailsForm = this.formBuilder.group({ - file: this.formBuilder.control(null, Validators.required), - }); - - this.sendFileDetailsForm = Object.assign(this.baseSendFileDetailsForm, this.sendDetailsForm); - this.sendFormContainer.registerChildForm("sendFileDetailsForm", this.sendFileDetailsForm); this.sendFileDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.html b/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.html index bddd8f226d1..b7875d1d45f 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.html +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.html @@ -1,4 +1,4 @@ - + {{ "sendTypeTextToShare" | i18n }} diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts index 970c74644d8..85fc324f2f7 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts @@ -1,13 +1,7 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { - FormBuilder, - FormControl, - FormGroup, - Validators, - ReactiveFormsModule, -} from "@angular/forms"; +import { FormBuilder, FormControl, Validators, ReactiveFormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; @@ -16,15 +10,6 @@ import { CheckboxModule, FormFieldModule, SectionComponent } from "@bitwarden/co import { SendFormConfig } from "../../abstractions/send-form-config.service"; import { SendFormContainer } from "../../send-form-container"; -import { BaseSendDetailsForm } from "./base-send-details.component"; - -type BaseSendTextDetailsForm = FormGroup<{ - text: FormControl; - hidden: FormControl; -}>; - -export type SendTextDetailsForm = BaseSendTextDetailsForm & BaseSendDetailsForm; - @Component({ selector: "tools-send-text-details", templateUrl: "./send-text-details.component.html", @@ -41,22 +26,16 @@ export type SendTextDetailsForm = BaseSendTextDetailsForm & BaseSendDetailsForm; export class SendTextDetailsComponent implements OnInit { @Input() config: SendFormConfig; @Input() originalSendView?: SendView; - @Input() sendDetailsForm: BaseSendDetailsForm; - baseSendTextDetailsForm: BaseSendTextDetailsForm; - sendTextDetailsForm: SendTextDetailsForm; + sendTextDetailsForm = this.formBuilder.group({ + text: new FormControl("", Validators.required), + hidden: new FormControl(false), + }); constructor( private formBuilder: FormBuilder, protected sendFormContainer: SendFormContainer, ) { - this.baseSendTextDetailsForm = this.formBuilder.group({ - text: new FormControl("", Validators.required), - hidden: new FormControl(false), - }); - - this.sendTextDetailsForm = Object.assign(this.baseSendTextDetailsForm, this.sendDetailsForm); - this.sendFormContainer.registerChildForm("sendTextDetailsForm", this.sendTextDetailsForm); this.sendTextDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { @@ -73,7 +52,7 @@ export class SendTextDetailsComponent implements OnInit { ngOnInit() { if (this.originalSendView) { - this.baseSendTextDetailsForm.patchValue({ + this.sendTextDetailsForm.patchValue({ text: this.originalSendView.text?.text || "", hidden: this.originalSendView.text?.hidden || false, }); diff --git a/libs/tools/send/send-ui/src/send-form/send-form-container.ts b/libs/tools/send/send-ui/src/send-form/send-form-container.ts index 21508d96727..84e0913194b 100644 --- a/libs/tools/send/send-ui/src/send-form/send-form-container.ts +++ b/libs/tools/send/send-ui/src/send-form/send-form-container.ts @@ -1,17 +1,19 @@ import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendFormConfig } from "./abstractions/send-form-config.service"; +import { SendOptionsComponent } from "./components/options/send-options.component"; import { SendDetailsComponent } from "./components/send-details/send-details.component"; -import { SendFileDetailsForm } from "./components/send-details/send-file-details.component"; -import { SendTextDetailsForm } from "./components/send-details/send-text-details.component"; +import { SendFileDetailsComponent } from "./components/send-details/send-file-details.component"; +import { SendTextDetailsComponent } from "./components/send-details/send-text-details.component"; /** * The complete form for a send. Includes all the sub-forms from their respective section components. * TODO: Add additional form sections as they are implemented. */ export type SendForm = { sendDetailsForm?: SendDetailsComponent["sendDetailsForm"]; - sendTextDetailsForm?: SendTextDetailsForm; - sendFileDetailsForm?: SendFileDetailsForm; + sendTextDetailsForm?: SendTextDetailsComponent["sendTextDetailsForm"]; + sendFileDetailsForm?: SendFileDetailsComponent["sendFileDetailsForm"]; + sendOptionsForm?: SendOptionsComponent["sendOptionsForm"]; }; /** diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html index ff94548b342..1ad42f95ec2 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html @@ -38,7 +38,7 @@ *ngIf="variant === 'edit'" type="button" buttonType="danger" - class="tw-border-0 tw-ml-auto" + class="tw-ml-auto" bitIconButton="bwi-trash" [appA11yTitle]="'deleteCustomField' | i18n: customFieldForm.value.label" (click)="removeField()" diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html index c34fc05b1d3..b693c448158 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.html +++ b/libs/vault/src/cipher-view/cipher-view.component.html @@ -16,7 +16,7 @@ diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 10701083b79..cadf388e76d 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnDestroy, OnInit } from "@angular/core"; -import { Observable, Subject, takeUntil } from "rxjs"; +import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -12,7 +12,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { isCardExpired } from "@bitwarden/common/vault/utils"; -import { SearchModule, CalloutModule } from "@bitwarden/components"; +import { CalloutModule, SearchModule } from "@bitwarden/components"; import { AdditionalOptionsComponent } from "./additional-options/additional-options.component"; import { AttachmentsV2ViewComponent } from "./attachments/attachments-v2-view.component"; @@ -45,10 +45,15 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide ], }) export class CipherViewComponent implements OnInit, OnDestroy { - @Input() cipher: CipherView; + @Input({ required: true }) cipher: CipherView; + + /** + * Optional list of collections the cipher is assigned to. If none are provided, they will be fetched using the + * `CipherService` and the `collectionIds` property of the cipher. + */ + @Input() collections: CollectionView[]; organization$: Observable; folder$: Observable; - collections$: Observable; private destroyed$: Subject = new Subject(); cardIsExpired: boolean = false; @@ -84,10 +89,16 @@ export class CipherViewComponent implements OnInit, OnDestroy { } async loadCipherData() { - if (this.cipher.collectionIds.length > 0) { - this.collections$ = this.collectionService - .decryptedCollectionViews$(this.cipher.collectionIds as CollectionId[]) - .pipe(takeUntil(this.destroyed$)); + // Load collections if not provided and the cipher has collectionIds + if ( + this.cipher.collectionIds.length > 0 && + (!this.collections || this.collections.length === 0) + ) { + this.collections = await firstValueFrom( + this.collectionService.decryptedCollectionViews$( + this.cipher.collectionIds as CollectionId[], + ), + ); } if (this.cipher.organizationId) { diff --git a/package-lock.json b/package-lock.json index 95e8fcb50b4..bfc943b55b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -232,7 +232,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.9.0", + "version": "2024.9.1", "hasInstallScript": true, "license": "GPL-3.0" },