diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 655d9e3d58e..796db71c2b0 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -1933,7 +1933,7 @@ "message": "E-mail pro doménový koš" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "Použijte nakonfigurovanou univerzální schránku své domény." }, "random": { "message": "Náhodný" diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index e48ff9db786..642fcec5c7e 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2030,18 +2030,18 @@ } }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "Log ind med hovedadgangskode" }, "loggingInAs": { - "message": "Logging in as" + "message": "Logger ind som" }, "notYou": { - "message": "Not you?" + "message": "Ikke dig?" }, "newAroundHere": { - "message": "New around here?" + "message": "Ny her?" }, "rememberEmail": { - "message": "Remember email" + "message": "Husk e-mail" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 8b263c097c5..370caaa68b1 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -98,7 +98,7 @@ "message": "Kopiuj nazwę pola niestandardowego" }, "noMatchingLogins": { - "message": "Brak pasujących danych logowania." + "message": "Brak pasujących danych logowania" }, "unlockVaultMenu": { "message": "Odblokuj sejf" @@ -936,7 +936,7 @@ "message": "Adres URL serwera" }, "apiUrl": { - "message": "Adres URL serwera interfejsu API" + "message": "Adres URL serwera API" }, "webVaultUrl": { "message": "Adres URL serwera sejfu internetowego" @@ -1040,7 +1040,7 @@ "message": "Pokaż ikony witryn" }, "faviconDesc": { - "message": "Pokaż rozpoznawalny obraz obok każdych danych logowania." + "message": "Pokaż rozpoznawalny obraz obok danych logowania." }, "enableBadgeCounter": { "message": "Pokaż licznik na ikonie" @@ -1316,7 +1316,7 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Aktualizacja hasła", + "message": "Hasło zostało zaktualizowane", "description": "ex. Date this password was updated" }, "neverLockWarning": { @@ -1488,7 +1488,7 @@ "message": "Zaznaczając tę opcję, akceptujesz:" }, "acceptPoliciesRequired": { - "message": "Warunki użytkowania i polityka prywatności nie zostały zaakceptowane." + "message": "Regulamin i polityka prywatności nie zostały zaakceptowane." }, "termsOfService": { "message": "Regulamin" @@ -1820,7 +1820,7 @@ "message": "Zaktualizuj hasło główne" }, "updateMasterPasswordWarning": { - "message": "Twoje hasło główne zostało ostatnio zmienione przez administratora Twojej organizacji. Musisz je teraz zaktualizować, aby uzyskać dostęp do sejfu. W przypadku kontynuacji nastąpi wylogowanie z bieżącej sesji, przez co konieczne będzie ponowne zalogowanie się. Aktywne sesje na innych urządzeniach mogą pozostać aktywne przez maksymalnie jedną godzinę." + "message": "Hasło główne zostało zmienione przez administratora Twojej organizacji. Musisz je zaktualizować, aby uzyskać dostęp do sejfu. Ta czynność spowoduje wylogowanie z bieżącej sesji, przez co konieczne będzie ponowne zalogowanie się. Aktywne sesje na innych urządzeniach mogą pozostać aktywne przez maksymalnie godzinę." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatyczne rejestrowanie użytkowników" @@ -1857,10 +1857,10 @@ "message": "Czas blokowania sejfu przekracza limit określony przez organizację." }, "vaultExportDisabled": { - "message": "Eksport sejfu wyłączony" + "message": "Eksportowanie sejfu jest niedostępne" }, "personalVaultExportPolicyInEffect": { - "message": "Co najmniej jedna zasada organizacji uniemożliwia wyeksportowanie Twojego sejfu." + "message": "Co najmniej jedna zasada organizacji uniemożliwia wyeksportowanie osobistego sejfu." }, "copyCustomFieldNameInvalidElement": { "message": "Nie można zidentyfikować poprawnego elementu formularza. Spróbuj sprawdzić kod HTML." @@ -1942,7 +1942,7 @@ "message": "Losowe słowo" }, "websiteName": { - "message": "Nazwa witryny" + "message": "Nazwa strony" }, "whatWouldYouLikeToGenerate": { "message": "Co chcesz wygenerować?" @@ -1954,7 +1954,7 @@ "message": "Usługa" }, "forwardedEmail": { - "message": "Alias przekazywanego e-maila" + "message": "Alias przekazywanego adresu" }, "forwardedEmailDesc": { "message": "Wygeneruj alias adresu e-mail z zewnętrznej usługi przekazywania." @@ -1967,7 +1967,7 @@ "message": "Token dostępu API" }, "apiKey": { - "message": "Klucz interfejsu API" + "message": "Klucz API" }, "ssoKeyConnectorError": { "message": "Błąd serwera Key Connector: upewnij się, że serwer Key Connector jest dostępny i działa poprawnie." @@ -1976,10 +1976,10 @@ "message": "Wymagana jest subskrypcja Premium" }, "organizationIsDisabled": { - "message": "Organizacja jest wyłączona." + "message": "Organizacja została zawieszona." }, "disabledOrganizationFilterError": { - "message": "Nie można uzyskać dostępu do elementów w wyłączonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." + "message": "Nie można uzyskać dostępu do elementów w zawieszonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." }, "cardBrandMir": { "message": "Mir" @@ -1994,7 +1994,7 @@ } }, "settingsEdited": { - "message": "Ustawienia zostały edytowane" + "message": "Ustawienia zostały zmienione" }, "environmentEditedClick": { "message": "Kliknij tutaj" @@ -2009,7 +2009,7 @@ "message": "Samodzielnie hostowany" }, "thirdParty": { - "message": "Innego dostawcy" + "message": "Inny dostawca" }, "thirdPartyServerMessage": { "message": "Połączono z implementacją serwera innego dostawcy, $SERVERNAME$. Zweryfikuj błędy za pomocą oficjalnego serwera lub zgłoś je serwerowi innego dostawcy.", @@ -2021,7 +2021,7 @@ } }, "lastSeenOn": { - "message": "ostatnio widziano $DATE$", + "message": "ostatnio widziany $DATE$", "placeholders": { "date": { "content": "$1", @@ -2039,7 +2039,7 @@ "message": "To nie Ty?" }, "newAroundHere": { - "message": "Jesteś tu nowy(a)?" + "message": "Nowy użytkownik?" }, "rememberEmail": { "message": "Zapamiętaj adres e-mail" diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index a4c107152af..af7f3cc090c 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -131,7 +131,7 @@ "message": "Skicka en verifieringskod till din e-postadress" }, "sendCode": { - "message": "Send-kod" + "message": "Skicka kod" }, "codeSent": { "message": "Kod har skickats" @@ -318,7 +318,7 @@ "message": "Öppna" }, "website": { - "message": "Webbsida" + "message": "Webbplats" }, "toggleVisibility": { "message": "Växla synlighet" @@ -436,7 +436,7 @@ "message": "Bekräftelsen för huvudlösenordet stämde ej." }, "newAccountCreated": { - "message": "Ditt nya konto har blivit skapat! Du kan nu logga in." + "message": "Ditt nya konto har skapats! Du kan logga in nu." }, "masterPassSent": { "message": "Vi har skickat ett e-postmeddelande till dig med din huvudlösenordsledtråd." @@ -448,7 +448,7 @@ "message": "Ogiltig verifieringskod" }, "valueCopied": { - "message": "$VALUE$ kopierat", + "message": "$VALUE$ har kopierats", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -461,10 +461,10 @@ "message": "Kunde inte automatiskt fylla i det valda objektet på den här webbsidan. Klipp/klistra informationen istället." }, "loggedOut": { - "message": "Loggade ut" + "message": "Utloggad" }, "loginExpired": { - "message": "Din inloggningssession har utgått." + "message": "Din inloggningssession har upphört." }, "logOutConfirmation": { "message": "Är du säker på att du vill logga ut?" @@ -497,7 +497,7 @@ "message": "Mapp sparad" }, "deleteFolderConfirmation": { - "message": "Är du säker på att du vill ta bort den här mappen?" + "message": "Är du säker på att du vill radera denna mapp?" }, "deletedFolder": { "message": "Mapp raderad" @@ -717,10 +717,10 @@ "message": "Bifogade filer" }, "deleteAttachment": { - "message": "Ta bort bilaga" + "message": "Radera bilaga" }, "deleteAttachmentConfirmation": { - "message": "Är du säker på att du vill ta bort bilagan?" + "message": "Är du säker på att du vill radera denna bilaga?" }, "deletedAttachment": { "message": "Raderade bilaga" @@ -1400,7 +1400,7 @@ "description": "Verb form: to make secure or inaccesible by" }, "trash": { - "message": "Papperskorgen", + "message": "Papperskorg", "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { @@ -1731,7 +1731,7 @@ "message": "Nuvarande antal åtkomster" }, "createSend": { - "message": "Skapa ny Send", + "message": "Ny Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { diff --git a/apps/browser/src/content/autofill.js b/apps/browser/src/content/autofill.js index ee4dfab1b0d..f56a1f843e2 100644 --- a/apps/browser/src/content/autofill.js +++ b/apps/browser/src/content/autofill.js @@ -42,6 +42,7 @@ 9. Add new handler, for new command that responds with page details in response callback 10. Handle sandbox iframe and sandbox rule in CSP 11. Work on array of saved urls instead of just one to determine if we should autofill non-https sites + 12. Remove setting of attribute com.browser.browser.userEdited on user-inputs */ function collect(document, undefined) { @@ -50,11 +51,6 @@ // END MODIFICATION document.elementsByOPID = {}; - document.addEventListener('input', function (inputevent) { - inputevent.a !== false && - inputevent.target.tagName.toLowerCase() === 'input' && - (inputevent.target.dataset['com.bitwarden.browser.userEdited'] = 'yes'); - }, true); function getPageDetails(theDoc, oneShotId) { // start helpers @@ -279,8 +275,6 @@ addProp(field, 'title', getElementAttrValue(el, 'title')); // START MODIFICATION - addProp(field, 'userEdited', !!el.dataset['com.browser.browser.userEdited']); - var elTagName = el.tagName.toLowerCase(); addProp(field, 'tagName', elTagName); diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts index c37b640f3f9..2630d9f7bb3 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts @@ -32,6 +32,7 @@ describe("session syncer", () => { stateService = mock(); stateService.hasInSessionMemory.mockResolvedValue(false); sut = new SessionSyncer(behaviorSubject, stateService, metaData); + jest.spyOn(sut as any, "debounceMs", "get").mockReturnValue(0); }); afterEach(() => { @@ -88,7 +89,7 @@ describe("session syncer", () => { sut.init(); - expect(sut["ignoreNUpdates"]).toBe(3); + expect(sut["ignoreNUpdates"]).toBe(1); }); it("should ignore BehaviorSubject's initial value", () => { @@ -128,28 +129,41 @@ describe("session syncer", () => { describe("a value is emitted on the observable", () => { let sendMessageSpy: jest.SpyInstance; - beforeEach(() => { + beforeEach(async () => { sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage"); sut.init(); + // allow initial value to be set + await awaitAsync(); behaviorSubject.next("test"); }); it("should update the session memory", async () => { // await finishing of fire-and-forget operation - await new Promise((resolve) => setTimeout(resolve, 100)); + await awaitAsync(); expect(stateService.setInSessionMemory).toHaveBeenCalledTimes(1); expect(stateService.setInSessionMemory).toHaveBeenCalledWith(sessionKey, "test"); }); it("should update sessionSyncers in other contexts", async () => { // await finishing of fire-and-forget operation - await new Promise((resolve) => setTimeout(resolve, 100)); + await awaitAsync(); expect(sendMessageSpy).toHaveBeenCalledTimes(1); expect(sendMessageSpy).toHaveBeenCalledWith(`${sessionKey}_update`, { id: sut.id }); }); + + it("should debounce subject updates", async () => { + behaviorSubject.next("test2"); + behaviorSubject.next("test3"); + + // await finishing of fire-and-forget operation + await awaitAsync(); + + expect(stateService.setInSessionMemory).toHaveBeenCalledTimes(1); + expect(stateService.setInSessionMemory).toHaveBeenCalledWith(sessionKey, "test3"); + }); }); describe("A message is received", () => { diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts index 91b371ef817..74f18577065 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts @@ -1,4 +1,11 @@ -import { BehaviorSubject, concatMap, ReplaySubject, Subject, Subscription } from "rxjs"; +import { + BehaviorSubject, + concatMap, + ReplaySubject, + Subject, + Subscription, + debounceTime, +} from "rxjs"; import { Utils } from "@bitwarden/common/misc/utils"; @@ -13,6 +20,9 @@ export class SessionSyncer { // ignore initial values private ignoreNUpdates = 0; + private get debounceMs() { + return 500; + } constructor( private subject: Subject, @@ -30,10 +40,8 @@ export class SessionSyncer { init() { switch (this.subject.constructor) { - case ReplaySubject: - // ignore all updates currently in the buffer - this.ignoreNUpdates = (this.subject as any)._buffer.length; - break; + // ignore all updates currently in the buffer + case ReplaySubject: // N = 1 due to debounce case BehaviorSubject: this.ignoreNUpdates = 1; break; @@ -58,6 +66,7 @@ export class SessionSyncer { // contexts. If so, this is handled by destruction of the context. this.subscription = this.subject .pipe( + debounceTime(this.debounceMs), concatMap(async (next) => { if (this.ignoreNUpdates > 0) { this.ignoreNUpdates -= 1; @@ -92,8 +101,15 @@ export class SessionSyncer { } private async updateSession(value: any) { - await this.stateService.setInSessionMemory(this.metaData.sessionKey, value); - await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id }); + try { + await this.stateService.setInSessionMemory(this.metaData.sessionKey, value); + await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id }); + } catch (e) { + if (e.message === "Could not establish connection. Receiving end does not exist.") { + return; + } + throw e; + } } private get updateMessageCommand() { diff --git a/apps/browser/src/popup/accounts/home.component.html b/apps/browser/src/popup/accounts/home.component.html index 7fc8163a7f0..24feb9c83e5 100644 --- a/apps/browser/src/popup/accounts/home.component.html +++ b/apps/browser/src/popup/accounts/home.component.html @@ -9,9 +9,18 @@ - -
@@ -22,10 +31,10 @@
- diff --git a/apps/browser/src/popup/accounts/home.component.ts b/apps/browser/src/popup/accounts/home.component.ts index 28d42dc482a..e6a31bea9a6 100644 --- a/apps/browser/src/popup/accounts/home.component.ts +++ b/apps/browser/src/popup/accounts/home.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { LoginService } from "@bitwarden/common/abstractions/login.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -16,6 +17,7 @@ export class HomeComponent implements OnInit { formGroup = this.formBuilder.group({ email: ["", [Validators.required, Validators.email]], + rememberEmail: [false], }); constructor( @@ -25,12 +27,26 @@ export class HomeComponent implements OnInit { private router: Router, private i18nService: I18nService, private environmentService: EnvironmentService, - private route: ActivatedRoute + private route: ActivatedRoute, + private loginService: LoginService ) {} async ngOnInit(): Promise { - const rememberedEmail = await this.stateService.getRememberedEmail(); - if (rememberedEmail != null) { - this.formGroup.patchValue({ email: await this.stateService.getRememberedEmail() }); + let savedEmail = this.loginService.getEmail(); + const rememberEmail = this.loginService.getRememberEmail(); + + if (savedEmail != null) { + this.formGroup.patchValue({ + email: savedEmail, + rememberEmail: rememberEmail, + }); + } else { + savedEmail = await this.stateService.getRememberedEmail(); + if (savedEmail != null) { + this.formGroup.patchValue({ + email: savedEmail, + rememberEmail: true, + }); + } } } @@ -45,12 +61,17 @@ export class HomeComponent implements OnInit { return; } - this.stateService.setRememberedEmail(this.formGroup.value.email); - + this.loginService.setEmail(this.formGroup.value.email); + this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } }); } get selfHostedDomain() { return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null; } + + setFormValues() { + this.loginService.setEmail(this.formGroup.value.email); + this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); + } } diff --git a/apps/browser/src/popup/accounts/login.component.ts b/apps/browser/src/popup/accounts/login.component.ts index d44847b35e0..7630f31ac3d 100644 --- a/apps/browser/src/popup/accounts/login.component.ts +++ b/apps/browser/src/popup/accounts/login.component.ts @@ -23,8 +23,6 @@ import { Utils } from "@bitwarden/common/misc/utils"; templateUrl: "login.component.html", }) export class LoginComponent extends BaseLoginComponent { - protected skipRememberEmail = true; - constructor( apiService: ApiService, appIdService: AppIdService, @@ -73,6 +71,7 @@ export class LoginComponent extends BaseLoginComponent { } async launchSsoBrowser() { + await this.loginService.saveEmailSettings(); // Generate necessary sso params const passwordOptions: any = { type: "password", diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 8803c73aa66..92c38773dad 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -536,10 +536,23 @@ main { } border-radius: $border-radius; padding: 6px 10px; - width: 160px; + width: auto; + max-width: 100%; display: flex; align-items: center; justify-content: space-between; + .org-filter-text-container { + // src: https://css-tricks.com/snippets/css/truncate-string-with-ellipsis/ + display: flex; + flex: 1; + min-width: 0; + + .org-filter-text-name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } } } .vault-select { @@ -550,9 +563,8 @@ main { @include themify($themes) { background-color: themed("boxBackgroundColor"); } - margin-right: 5px; + margin-right: 18px; margin-top: 1px; - width: 160px; @include themify($themes) { border: 1px solid themed("borderColor"); } @@ -560,7 +572,9 @@ main { button { border: none; background: transparent; - width: 100%; + width: auto; + max-width: 100%; + padding: 5px 10px; text-align: start; @include themify($themes) { @@ -578,6 +592,25 @@ main { background-color: themed("boxBackgroundHoverColor"); } } + + i.vault-select-prefix-icon { + margin-right: 5px; + } + i.vault-select-suffix-icon { + margin-left: 5px; + } + + .vault-select-org-text-container { + display: flex; + flex: 1; + min-width: 0; + align-items: center; + .vault-select-org-name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } } .border { @include themify($themes) { diff --git a/apps/browser/src/popup/scss/box.scss b/apps/browser/src/popup/scss/box.scss index 29e54ed9f30..ffb15ba0261 100644 --- a/apps/browser/src/popup/scss/box.scss +++ b/apps/browser/src/popup/scss/box.scss @@ -43,7 +43,7 @@ .icon { display: flex; align-items: center; - margin-left: 5px; + margin-right: 5px; @include themify($themes) { color: themed("headingColor"); diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss index 9ba21f63ca7..66bd190a5fc 100644 --- a/apps/browser/src/popup/scss/pages.scss +++ b/apps/browser/src/popup/scss/pages.scss @@ -143,6 +143,12 @@ body.body-full { padding: 30px 10px 0 10px; } +.remember-email-check { + padding-top: 8px; + padding-left: 10px; + padding-bottom: 18px; +} + .login-buttons > button { margin: 15px 0 15px 0; } diff --git a/apps/browser/src/popup/send/send-add-edit.component.html b/apps/browser/src/popup/send/send-add-edit.component.html index 19e5c535127..44002dddaa6 100644 --- a/apps/browser/src/popup/send/send-add-edit.component.html +++ b/apps/browser/src/popup/send/send-add-edit.component.html @@ -154,9 +154,9 @@ (click)="showOptions = !showOptions" [attr.aria-expanded]="showOptions" > + + {{ "options" | i18n }} - - diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index b2039ba5996..05cd4ede8f5 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -367,6 +367,7 @@ function getBgService(service: keyof MainBackground) { { provide: LoginServiceAbstraction, useClass: LoginService, + deps: [StateServiceAbstraction], }, { provide: AbstractThemingService, diff --git a/apps/browser/src/popup/settings/options.component.html b/apps/browser/src/popup/settings/options.component.html index 8f9874faa71..afa77407f5e 100644 --- a/apps/browser/src/popup/settings/options.component.html +++ b/apps/browser/src/popup/settings/options.component.html @@ -19,9 +19,9 @@ (click)="showGeneral = !showGeneral" [attr.aria-expanded]="showGeneral" > + + General - - @@ -127,9 +127,9 @@ (click)="showDisplay = !showDisplay" [attr.aria-expanded]="showDisplay" > + + Display - - @@ -210,9 +210,9 @@ (click)="showAutofill = !showAutofill" [attr.aria-expanded]="showAutofill" > + + Autofill - - diff --git a/apps/browser/src/popup/vault/vault-select.component.html b/apps/browser/src/popup/vault/vault-select.component.html index 8ef8c2dc04c..a25f49ebf2d 100644 --- a/apps/browser/src/popup/vault/vault-select.component.html +++ b/apps/browser/src/popup/vault/vault-select.component.html @@ -10,12 +10,16 @@ [attr.aria-expanded]="isOpen" [attr.aria-label]="vaultFilterDisplay" > - {{ vaultFilterDisplay | ellipsis: 45 }}  - + + {{ vaultFilterDisplay }}  + @@ -28,37 +32,42 @@ aria-modal="true" > diff --git a/apps/browser/store/locales/cs/copy.resx b/apps/browser/store/locales/cs/copy.resx index 76bd3a97397..5bf97484a52 100644 --- a/apps/browser/store/locales/cs/copy.resx +++ b/apps/browser/store/locales/cs/copy.resx @@ -124,31 +124,31 @@ Bezpečný a bezplatný správce hesel pro všechna vaše zařízení - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Bitwarden, Inc. je mateřskou společností 8bit Solutions LLC. -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +THE VERGE, U.S. NEWS & WORLD REPORT, CNET A DALŠÍ JI OZNAČILY ZA NEJLEPŠÍHO SPRÁVCE HESEL. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +Spravujte, ukládejte, zabezpečujte a sdílejte neomezený počet hesel na neomezeném počtu zařízení odkudkoliv. Bitwarden poskytuje open source řešení pro správu hesel všem, ať už doma, v práci nebo na cestách. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Generujte silná, jedinečná a náhodná hesla na základě bezpečnostních požadavků pro každou webovou stránku, kterou navštěvujete. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +Bitwarden Send rychle přenáší šifrované informace --- soubory a prostý text -- přímo komukoliv. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Bitwarden nabízí plány Teams a Enterprise pro firmy, takže můžete bezpečně sdílet hesla s kolegy. -Why Choose Bitwarden: +Proč si vybrat Bitwarden: -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Šifrování na světové úrovni +Hesla jsou chráněna pokročilým koncovým šifrováním (AES-256 bit, salted hashování a PBKDF2 SHA-256), takže vaše data zůstanou bezpečná a soukromá. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Vestavěný generátor hesel +Generujte silná, jedinečná a náhodná hesla na základě bezpečnostních požadavků pro každou webovou stránku, kterou navštěvujete. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Globální překlady +Překlady Bitwarden existují ve 40 jazycích a díky naší globální komunitě se stále rozšiřují. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Aplikace pro více platforem +Zabezpečte a sdílejte citlivá data v rámci svého trezoru Bitwarden z jakéhokoliv prohlížeče, mobilního zařízení nebo desktopového operačního systému a dalších. diff --git a/apps/desktop/src/app/accounts/environment.component.html b/apps/desktop/src/app/accounts/environment.component.html index 859554b92b1..1788e52e31a 100644 --- a/apps/desktop/src/app/accounts/environment.component.html +++ b/apps/desktop/src/app/accounts/environment.component.html @@ -26,8 +26,11 @@

diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 06cbb7fe8c6..2e409f6fcab 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -16,17 +16,17 @@ [attr.aria-expanded]="showSecurity" appAutofocus > - {{ "security" | i18n }} + {{ "security" | i18n }} @@ -120,17 +120,17 @@ (click)="showAccountPreferences = !showAccountPreferences" [attr.aria-expanded]="showAccountPreferences" > - {{ "accountPreferences" | i18n }} + {{ "accountPreferences" | i18n }} @@ -190,17 +190,17 @@ (click)="showAppPreferences = !showAppPreferences" [attr.aria-expanded]="showAppPreferences" > - {{ "appPreferences" | i18n }} + {{ "appPreferences" | i18n }} diff --git a/apps/desktop/src/app/send/add-edit.component.html b/apps/desktop/src/app/send/add-edit.component.html index 47c0d4ae00c..bae0e073dac 100644 --- a/apps/desktop/src/app/send/add-edit.component.html +++ b/apps/desktop/src/app/send/add-edit.component.html @@ -100,12 +100,12 @@ (click)="toggleOptions()" [attr.aria-expanded]="showOptions" > - {{ "options" | i18n }} + {{ "options" | i18n }}
diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 8696bd0b395..28d27a9ce22 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -180,6 +180,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK"); { provide: LoginServiceAbstraction, useClass: LoginService, + deps: [StateServiceAbstraction], }, ], }) diff --git a/apps/desktop/src/app/vault/generator.component.html b/apps/desktop/src/app/vault/generator.component.html index 6acf7a331bf..28d7b37afe7 100644 --- a/apps/desktop/src/app/vault/generator.component.html +++ b/apps/desktop/src/app/vault/generator.component.html @@ -99,14 +99,12 @@

-

@@ -299,14 +297,12 @@

-

diff --git a/apps/desktop/src/app/vault/vault-filter/filters/organization-filter.component.html b/apps/desktop/src/app/vault/vault-filter/filters/organization-filter.component.html index cfec1cf89d0..68aebbf41dd 100644 --- a/apps/desktop/src/app/vault/vault-filter/filters/organization-filter.component.html +++ b/apps/desktop/src/app/vault/vault-filter/filters/organization-filter.component.html @@ -46,6 +46,13 @@  {{ organization.name }} + + + @@ -113,7 +120,8 @@ diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index c57c32ec3ba..fefb00adf59 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -178,7 +178,7 @@ "message": "Premium required" }, "premiumRequiredDesc": { - "message": "A premium membership is required to use this feature." + "message": "A Premium membership is required to use this feature." }, "errorOccurred": { "message": "An error has occurred." @@ -269,7 +269,7 @@ "message": "Last name" }, "fullName": { - "message": "Full Name" + "message": "Full name" }, "address1": { "message": "Address 1" @@ -346,10 +346,10 @@ "message": "Name is required." }, "addedItem": { - "message": "Added item" + "message": "Item added" }, "editedItem": { - "message": "Edited item" + "message": "Item saved" }, "deleteItem": { "message": "Delete item" @@ -370,7 +370,7 @@ "message": "Are you sure you want to overwrite the current password?" }, "overwriteUsername": { - "message": "Overwrite Username" + "message": "Overwrite username" }, "overwriteUsernameConfirmation": { "message": "Are you sure you want to overwrite the current username?" @@ -395,7 +395,7 @@ "message": "Copy URI" }, "copyVerificationCodeTotp": { - "message": "Copy Verification Code (TOTP)" + "message": "Copy verification code (TOTP)" }, "length": { "message": "Length" @@ -410,7 +410,7 @@ "message": "Numbers (0-9)" }, "specialCharacters": { - "message": "Special Characters (!@#$%^&*)" + "message": "Special characters (!@#$%^&*)" }, "numWords": { "message": "Number of words" @@ -455,19 +455,19 @@ "message": "Add new attachment" }, "deletedAttachment": { - "message": "Deleted attachment" + "message": "Attachment deleted" }, "deleteAttachmentConfirmation": { "message": "Are you sure you want to delete this attachment?" }, "attachmentSaved": { - "message": "The attachment has been saved." + "message": "Attachment saved" }, "file": { "message": "File" }, "selectFile": { - "message": "Select a file." + "message": "Select a file" }, "maxFileSize": { "message": "Maximum file size is 500 MB." @@ -476,16 +476,16 @@ "message": "You cannot use this feature until you update your encryption key." }, "editedFolder": { - "message": "Edited folder" + "message": "Folder saved" }, "addedFolder": { - "message": "Added folder" + "message": "Folder added" }, "deleteFolderConfirmation": { "message": "Are you sure you want to delete this folder?" }, "deletedFolder": { - "message": "Deleted folder" + "message": "Folder deleted" }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." @@ -563,10 +563,10 @@ "message": "Send a verification code to your email" }, "sendCode": { - "message": "Send Code" + "message": "Send code" }, "codeSent": { - "message": "Code Sent" + "message": "Code sent" }, "verificationCode": { "message": "Verification code" @@ -620,7 +620,7 @@ "message": "Insert your security key into your computer's USB port. If it has a button, touch it." }, "recoveryCodeDesc": { - "message": "Lost access to all of your two-factor providers? Use your recovery code to disable all two-factor providers from your account." + "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers on your account." }, "recoveryCodeTitle": { "message": "Recovery code" @@ -650,7 +650,7 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Use any WebAuthn enabled security key to access your account." + "message": "Use any WebAuthn compatible security key to access your account." }, "emailTitle": { "message": "Email" @@ -662,7 +662,7 @@ "message": "Login unavailable" }, "noTwoStepProviders": { - "message": "This account has two-step login enabled. However, none of the configured two-step providers are supported by this device." + "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this device." }, "noTwoStepProviders2": { "message": "Please add additional providers that are better supported across devices (such as an authenticator app)." @@ -701,7 +701,7 @@ "message": "Icons server URL" }, "environmentSaved": { - "message": "The environment URLs have been saved." + "message": "Environment URLs saved" }, "ok": { "message": "OK" @@ -734,13 +734,13 @@ "message": "Log out" }, "addNewLogin": { - "message": "Add new login" + "message": "New login" }, "addNewItem": { - "message": "Add new item" + "message": "New item" }, "addNewFolder": { - "message": "Add new folder" + "message": "New folder" }, "view": { "message": "View" @@ -752,16 +752,16 @@ "message": "Loading..." }, "lockVault": { - "message": "Lock Vault" + "message": "Lock vault" }, "passwordGenerator": { "message": "Password generator" }, "contactUs": { - "message": "Contact Us" + "message": "Contact us" }, "getHelp": { - "message": "Get Help" + "message": "Get help" }, "fileBugReport": { "message": "File a bug report" @@ -790,7 +790,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "goToWebVault": { - "message": "Go to Web Vault" + "message": "Go to web vault" }, "getMobileApp": { "message": "Get mobile app" @@ -827,7 +827,7 @@ "message": "Invalid master password" }, "twoStepLoginConfirmation": { - "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, "twoStepLogin": { "message": "Two-step login" @@ -928,7 +928,7 @@ "message": "When closing the window, show an icon in the menu bar instead." }, "enableTray": { - "message": "Enable tray icon" + "message": "Show tray icon" }, "enableTrayDesc": { "message": "Always show an icon in the system tray." @@ -958,10 +958,10 @@ "message": "Show the Bitwarden icon in the Dock even when minimised to the menu bar." }, "confirmTrayTitle": { - "message": "Confirm disable tray" + "message": "Confirm hiding tray" }, "confirmTrayDesc": { - "message": "Disabling this setting will also disable all other tray related settings." + "message": "Turning off this setting will also turn off all other tray related settings." }, "language": { "message": "Language" @@ -988,7 +988,7 @@ "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "Check for Updates…" + "message": "Check for updates…" }, "version": { "message": "Version $VERSION_NUM$", @@ -1000,7 +1000,7 @@ } }, "restartToUpdate": { - "message": "Restart to Update" + "message": "Restart to update" }, "restartToUpdateDesc": { "message": "Version $VERSION_NUM$ is ready to install. You must restart the application to complete the installation. Do you want to restart and update now?", @@ -1056,10 +1056,10 @@ "message": "Refresh membership" }, "premiumNotCurrentMember": { - "message": "You are not currently a premium member." + "message": "You are not currently a Premium member." }, "premiumSignUpAndGet": { - "message": "Sign up for a premium membership and get:" + "message": "Sign up for a Premium membership and get:" }, "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." @@ -1213,7 +1213,7 @@ "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain Name", + "message": "Domain name", "description": "Domain name. Ex. website.com" }, "host": { @@ -1285,7 +1285,7 @@ "description": "hCaptcha is the name of a website, should not be translated" }, "loadAccessibilityCookie": { - "message": "Load Accessibility Cookie" + "message": "Load accessibility cookie" }, "registerAccessibilityUser": { "message": "Register as an accessibility user at", @@ -1319,7 +1319,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "Confirm Vault Export" + "message": "Confirm vault export" }, "exportWarningDesc": { "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over insecure channels (such as email). Delete it immediately after you are done using it." @@ -1428,7 +1428,7 @@ "message": "You must select at least one collection." }, "premiumUpdated": { - "message": "You've upgraded to premium." + "message": "You've upgraded to Premium." }, "restore": { "message": "Restore" @@ -1476,7 +1476,7 @@ "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "Search Bin" + "message": "Search bin" }, "permanentlyDeleteItem": { "message": "Permanently delete item" @@ -1485,7 +1485,7 @@ "message": "Are you sure you want to permanently delete this item?" }, "permanentlyDeletedItem": { - "message": "Permanently deleted item" + "message": "Item permanently deleted" }, "restoreItem": { "message": "Restore item" @@ -1494,7 +1494,7 @@ "message": "Are you sure you want to restore this item?" }, "restoredItem": { - "message": "Restored item" + "message": "Item restored" }, "permanentlyDelete": { "message": "Permanently delete" @@ -1626,10 +1626,10 @@ "message": "Biometrics not enabled" }, "biometricsNotEnabledDesc": { - "message": "Browser biometrics requires desktop biometrics to be enabled in the settings first." + "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." }, "personalOwnershipSubmitError": { - "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organisation and choose from available Collections." + "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organisation and choose from available collections." }, "hintEqualsPassword": { "message": "Your password hint cannot be the same as your password." @@ -1656,27 +1656,27 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { - "message": "My Vault" + "message": "My vault" }, "text": { "message": "Text" }, "deletionDate": { - "message": "Deletion Date" + "message": "Deletion date" }, "deletionDateDesc": { "message": "The Send will be permanently deleted on the specified date and time.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "Expiration Date" + "message": "Expiration date" }, "expirationDateDesc": { "message": "If set, access to this Send will expire on the specified date and time.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { - "message": "Maximum Access Count", + "message": "Maximum access count", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "maxAccessCountDesc": { @@ -1684,10 +1684,10 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { - "message": "Current Access Count" + "message": "Current access count" }, "disableSend": { - "message": "Disable this Send so that no one can access it.", + "message": "Deactivate this Send so that no one can access it.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { @@ -1703,7 +1703,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinkLabel": { - "message": "Send Link", + "message": "Send link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "textHiddenByDefault": { @@ -1711,26 +1711,26 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Created Send", + "message": "Send added", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Edited Send", + "message": "Send saved", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Deleted Send", + "message": "Send deleted", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { - "message": "New Password" + "message": "New password" }, "whatTypeOfSend": { "message": "What type of Send is this?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Create Send", + "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -1766,7 +1766,7 @@ "message": "Copy the link to share this Send to my clipboard upon save." }, "sendDisabled": { - "message": "Send disabled", + "message": "Send removed", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1807,7 +1807,7 @@ "message": "One or more organisation policies are affecting your Send options." }, "emailVerificationRequired": { - "message": "Email Verification Required" + "message": "Email verification required" }, "emailVerificationRequiredDesc": { "message": "You must verify your email to use this feature." @@ -1822,13 +1822,13 @@ "message": "This action is protected. To continue, please re-enter your master password to verify your identity." }, "updatedMasterPassword": { - "message": "Updated Master Password" + "message": "Updated master password" }, "updateMasterPassword": { - "message": "Update Master Password" + "message": "Update master password" }, "updateMasterPasswordWarning": { - "message": "Your Master Password was recently changed by an administrator in your organisation. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "Your master password was recently changed by an administrator in your organisation. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "hours": { "message": "Hours" @@ -1837,7 +1837,7 @@ "message": "Minutes" }, "vaultTimeoutPolicyInEffect": { - "message": "Your organisation policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", + "message": "Your organisation policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", "placeholders": { "hours": { "content": "$1", @@ -1853,25 +1853,25 @@ "message": "Your vault timeout exceeds the restrictions set by your organisation." }, "resetPasswordPolicyAutoEnroll": { - "message": "Automatic Enrollment" + "message": "Automatic enrollment" }, "resetPasswordAutoEnrollInviteWarning": { "message": "This organisation has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organisation administrators to change your master password." }, "vaultExportDisabled": { - "message": "Vault Export Disabled" + "message": "Vault export removed" }, "personalVaultExportPolicyInEffect": { "message": "One or more organisation policies prevents you from exporting your personal vault." }, "addAccount": { - "message": "Add Account" + "message": "Add account" }, "removeMasterPassword": { - "message": "Remove Master Password" + "message": "Remove master password" }, "removedMasterPassword": { - "message": "Master password removed." + "message": "Master password removed" }, "convertOrganizationEncryptionDesc": { "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", @@ -1883,7 +1883,7 @@ } }, "leaveOrganization": { - "message": "Leave Organisation" + "message": "Leave organisation" }, "leaveOrganizationConfirmation": { "message": "Are you sure you want to leave this organisation?" @@ -1892,10 +1892,10 @@ "message": "You have left the organisation." }, "ssoKeyConnectorError": { - "message": "Key Connector error: make sure Key Connector is available and working correctly." + "message": "Key connector error: make sure key connector is available and working correctly." }, "lockAllVaults": { - "message": "Lock All Vaults" + "message": "Lock all vaults" }, "accountLimitReached": { "message": "No more than 5 accounts may be logged in at the same time." @@ -1904,7 +1904,7 @@ "message": "Preferences" }, "appPreferences": { - "message": "App Settings (All Accounts)" + "message": "App settings (all accounts)" }, "accountSwitcherLimitReached": { "message": "Account limit reached. Log out of an account to add another." @@ -1919,7 +1919,7 @@ } }, "switchAccount": { - "message": "Switch Account" + "message": "Switch account" }, "options": { "message": "Options" @@ -1928,10 +1928,10 @@ "message": "Your session has timed out. Please go back and try logging in again." }, "exportingPersonalVaultTitle": { - "message": "Exporting Personal Vault" + "message": "Exporting individual vault" }, "exportingPersonalVaultDescription": { - "message": "Only the personal vault items associated with $EMAIL$ will be exported. Organisation vault items will not be included.", + "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organisation vault items will not be included.", "placeholders": { "email": { "content": "$1", @@ -1952,26 +1952,26 @@ "message": "What would you like to generate?" }, "passwordType": { - "message": "Password Type" + "message": "Password type" }, "regenerateUsername": { - "message": "Regenerate Username" + "message": "Regenerate username" }, "generateUsername": { - "message": "Generate Username" + "message": "Generate username" }, "usernameType": { - "message": "Username Type" + "message": "Username type" }, "plusAddressedEmail": { - "message": "Plus Addressed Email", + "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "Use your email provider's sub-addressing capabilities." }, "catchallEmail": { - "message": "Catch-all Email" + "message": "Catch-all email" }, "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." @@ -1980,25 +1980,25 @@ "message": "Random" }, "randomWord": { - "message": "Random Word" + "message": "Random word" }, "websiteName": { - "message": "Website Name" + "message": "Website name" }, "service": { "message": "Service" }, "allVaults": { - "message": "All Vaults" + "message": "All vaults" }, "searchOrganization": { - "message": "Search Organisation" + "message": "Search organisation" }, "searchMyVault": { - "message": "Search My Vault" + "message": "Search my vault" }, "forwardedEmail": { - "message": "Forwarded Email Alias" + "message": "Forwarded email alias" }, "forwardedEmailDesc": { "message": "Generate an email alias with an external forwarding service." @@ -2011,16 +2011,16 @@ "message": "API Access Token" }, "apiKey": { - "message": "API Key" + "message": "API key" }, "premiumSubcriptionRequired": { "message": "Premium subscription required" }, "organizationIsDisabled": { - "message": "Organisation is disabled." + "message": "Organisation suspended" }, "disabledOrganizationFilterError": { - "message": "Items in disabled Organisations cannot be accessed. Contact your Organisation owner for assistance." + "message": "Items in suspended organisations cannot be accessed. Contact your organisation owner for assistance." }, "neverLockWarning": { "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index b7906064d66..62ed5a4c647 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1780,13 +1780,13 @@ "message": "غیرفعال شد" }, "removePassword": { - "message": "Remove password" + "message": "حذف رمز عبور" }, "removedPassword": { - "message": "Password removed" + "message": "رمز عبور حذف شد" }, "removePasswordConfirmation": { - "message": "Are you sure you want to remove the password?" + "message": "مطمئنید که می‌خواهید رمز عبور حذف شود؟" }, "maxAccessCountReached": { "message": "به حداکثر تعداد دسترسی رسیده است" diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 86b357480e4..dc88f596c16 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1780,13 +1780,13 @@ "message": "Désactivé" }, "removePassword": { - "message": "Remove password" + "message": "Supprimer le mot de passe" }, "removedPassword": { - "message": "Password removed" + "message": "Mot de passe supprimé" }, "removePasswordConfirmation": { - "message": "Are you sure you want to remove the password?" + "message": "Êtes-vous sûr(e) de vouloir supprimer le mot de passe ?" }, "maxAccessCountReached": { "message": "Nombre maximum d'accès atteint" diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index bd37196ef7e..df3900cfad0 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -308,7 +308,7 @@ "message": "Edytuj" }, "authenticatorKeyTotp": { - "message": "Klucz Uwierzytelniający (TOTP)" + "message": "Klucz uwierzytelniający (TOTP)" }, "folder": { "message": "Folder" @@ -734,7 +734,7 @@ "message": "Wyloguj się" }, "addNewLogin": { - "message": "Dodaj dane logowania" + "message": "Nowe dane logowania" }, "addNewItem": { "message": "Nowy element" @@ -836,7 +836,7 @@ "message": "Blokowanie sejfu" }, "vaultTimeoutDesc": { - "message": "Wybierz kiedy sejf zostanie zablokowany i wykonaj następującą akcję." + "message": "Wybierz, kiedy sejf zostanie zablokowany i wykonaj następującą akcję." }, "immediately": { "message": "Natychmiast" @@ -928,7 +928,7 @@ "message": "Po zamknięciu okna, pokaż ikonę w pasku menu." }, "enableTray": { - "message": "Włącz ikonę w zasobniku systemowym" + "message": "Pokaż ikonę w zasobniku systemowym" }, "enableTrayDesc": { "message": "Zawsze pokazuj ikonę w zasobniku systemowym." @@ -958,7 +958,7 @@ "message": "Pokaż ikonę Bitwarden w Docku po zminimalizowaniu do paska menu." }, "confirmTrayTitle": { - "message": "Potwierdź wyłączenie zasobnika systemowego" + "message": "Potwierdź ukrycie zasobnika systemowego" }, "confirmTrayDesc": { "message": "Wyłączenie tej opcji spowoduje wyłącznie wszystkich innych powiązanych ustawień z zasobnikiem systemowym." @@ -1169,7 +1169,7 @@ "message": "Ukryj Bitwarden" }, "hideOthers": { - "message": "Ukryj pozostałe" + "message": "Ukryj inne" }, "showAll": { "message": "Pokaż wszystkie" @@ -1462,7 +1462,7 @@ "message": "Sposób blokowania sejfu" }, "vaultTimeoutActionLockDesc": { - "message": "Po zablokowaniu sejfu, musisz ponownie wpisać hasło główne, aby uzyskać do niego dostęp." + "message": "Aby uzyskać dostęp do sejfu, musisz wpisać hasło główne lub odblokować go inną metodą." }, "vaultTimeoutActionLogOutDesc": { "message": "Po wylogowaniu się z sejfu, musisz ponownie zalogować się, aby uzyskać do niego dostęp." @@ -1566,19 +1566,19 @@ "message": "Zaznaczając tę opcję, akceptujesz:" }, "acceptPoliciesRequired": { - "message": "Warunki użytkowania i polityka prywatności nie zostały zaakceptowane." + "message": "Regulamin i polityka prywatności nie zostały zaakceptowane." }, "enableBrowserIntegration": { "message": "Włącz połączenie z przeglądarką" }, "enableBrowserIntegrationDesc": { - "message": "Połączenie z przeglądarką jest używane do odblokowania danymi biometrycznymi." + "message": "Używane do odblokowania danymi biometrycznymi." }, "enableDuckDuckGoBrowserIntegration": { "message": "Włącz połączenie z przeglądarką DuckDuckGo" }, "enableDuckDuckGoBrowserIntegrationDesc": { - "message": "Korzystaj z sejfu Bitwarden podczas przeglądania za pomocą DuckDuckGo." + "message": "Korzystaj z sejfu Bitwarden podczas przeglądania za pomocą przeglądarki DuckDuckGo." }, "browserIntegrationUnsupportedTitle": { "message": "Połączenie z przeglądarką nie jest obsługiwane" @@ -1608,7 +1608,7 @@ "message": "Upewnij się, że wyświetlony identyfikator jest identyczny z pokazanym w rozszerzeniu przeglądarki." }, "verifyNativeMessagingConnectionTitle": { - "message": "$APPID$ chce połączyć się z Bitwarden", + "message": "Aplikacja $APPID$ chce połączyć się z Bitwarden", "placeholders": { "appid": { "content": "$1", @@ -1629,7 +1629,7 @@ "message": "Aby włączyć dane biometryczne w przeglądarce, musisz włączyć tę samą funkcję w ustawianiach aplikacji." }, "personalOwnershipSubmitError": { - "message": "Ze względu na zasadę przedsiębiorstwa, nie możesz zapisywać elementów w osobistym sejfie. Zmień właściciela elementu na organizację i wybierz jedną z dostępnych kolekcji,." + "message": "Ze względu na zasadę przedsiębiorstwa, nie możesz zapisywać elementów w osobistym sejfie. Zmień właściciela elementu na organizację i wybierz jedną z dostępnych kolekcji." }, "hintEqualsPassword": { "message": "Podpowiedź do hasła nie może być taka sama jak hasło." @@ -1807,7 +1807,7 @@ "message": "Co najmniej jedna zasada organizacji wpływa na ustawienia wysyłek." }, "emailVerificationRequired": { - "message": "Wymagana weryfikacja adresu e-mail" + "message": "Weryfikacja adresu e-mail jest wymagana" }, "emailVerificationRequiredDesc": { "message": "Musisz zweryfikować adres e-mail, aby używać tej funkcji." @@ -1983,7 +1983,7 @@ "message": "Losowe słowo" }, "websiteName": { - "message": "Nazwa witryny" + "message": "Nazwa strony" }, "service": { "message": "Usługa" @@ -1998,7 +1998,7 @@ "message": "Szukaj w sejfie" }, "forwardedEmail": { - "message": "Alias przekazywanego e-maila" + "message": "Alias przekazywanego adresu" }, "forwardedEmailDesc": { "message": "Wygeneruj alias adresu e-mail z zewnętrznej usługi przekazywania." @@ -2011,19 +2011,19 @@ "message": "Token dostępu API" }, "apiKey": { - "message": "Klucz interfejsu API" + "message": "Klucz API" }, "premiumSubcriptionRequired": { "message": "Wymagana jest subskrypcja Premium" }, "organizationIsDisabled": { - "message": "Organizacja jest wyłączona." + "message": "Organizacja została zawieszona" }, "disabledOrganizationFilterError": { - "message": "Nie można uzyskać dostępu do elementów w wyłączonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." + "message": "Nie można uzyskać dostępu do elementów w zawieszonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." }, "neverLockWarning": { - "message": "Czy na pewno chcesz użyć opcji „Nigdy”? Ustawienie opcji blokady na „Nigdy” powoduje zapisanie klucza szyfrowania sejfu na urządzeniu. Jeśli korzystasz z tej opcji, upewnij się, że urządzenie jest odpowiednio chronione." + "message": "Czy na pewno chcesz użyć opcji „Nigdy”? Ustawienie opcji blokady na „Nigdy” spowoduje zapisanie klucza szyfrowania sejfu na urządzeniu. Jeśli korzystasz z tej opcji, upewnij się, że urządzenie jest odpowiednio chronione." }, "cardBrandMir": { "message": "Mir" @@ -2044,7 +2044,7 @@ "message": "To nie Ty?" }, "newAroundHere": { - "message": "Jesteś tu nowy(a)?" + "message": "Nowy użytkownik?" }, "loggingInTo": { "message": "Logowanie do $DOMAIN$", diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 5b58172ac1b..b1d88a821f4 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -5,6 +5,7 @@ import { app, BrowserWindow, screen } from "electron"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; +import { WindowState } from "@bitwarden/common/models/domain/window-state"; import { cleanUserAgent, isDev, isMacAppStore, isSnapStore } from "../utils"; @@ -13,9 +14,10 @@ const WindowEventHandlingDelay = 100; export class WindowMain { win: BrowserWindow; isQuitting = false; + isClosing = false; private windowStateChangeTimer: NodeJS.Timer; - private windowStates: { [key: string]: any } = {}; + private windowStates: { [key: string]: WindowState } = {}; private enableAlwaysOnTop = false; constructor( @@ -128,6 +130,10 @@ export class WindowMain { }, }); + this.win.webContents.on("dom-ready", () => { + this.win.webContents.zoomFactor = this.windowStates[mainWindowSizeKey].zoomFactor ?? 1.0; + }); + if (this.windowStates[mainWindowSizeKey].isMaximized) { this.win.maximize(); } @@ -154,6 +160,7 @@ export class WindowMain { // Emitted when the window is closed. this.win.on("closed", async () => { + this.isClosing = false; await this.updateWindowState(mainWindowSizeKey, this.win); // Dereference the window object, usually you would store window @@ -163,6 +170,7 @@ export class WindowMain { }); this.win.on("close", async () => { + this.isClosing = true; await this.updateWindowState(mainWindowSizeKey, this.win); }); @@ -217,7 +225,7 @@ export class WindowMain { if (this.windowStates[configKey] == null) { this.windowStates[configKey] = await this.stateService.getWindow(); if (this.windowStates[configKey] == null) { - this.windowStates[configKey] = {}; + this.windowStates[configKey] = {}; } } @@ -231,6 +239,10 @@ export class WindowMain { this.windowStates[configKey].height = bounds.height; } + if (this.isClosing) { + this.windowStates[configKey].zoomFactor = win.webContents.zoomFactor; + } + await this.stateService.setWindow(this.windowStates[configKey]); } catch (e) { this.logService.error(e); diff --git a/apps/desktop/src/scss/box.scss b/apps/desktop/src/scss/box.scss index 64cbf370abf..54acf2e7d8d 100644 --- a/apps/desktop/src/scss/box.scss +++ b/apps/desktop/src/scss/box.scss @@ -58,7 +58,7 @@ .icon { display: flex; align-items: flex-end; - margin-left: 5px; + margin-right: 5px; @include themify($themes) { color: themed("headingColor"); diff --git a/apps/web/config/cloud.json b/apps/web/config/cloud.json index 7900740296d..7c561d5f998 100644 --- a/apps/web/config/cloud.json +++ b/apps/web/config/cloud.json @@ -16,6 +16,6 @@ "proxyEvents": "https://events.bitwarden.com" }, "flags": { - "showPasswordless": false + "showPasswordless": true } } diff --git a/apps/web/src/app/accounts/login/login-with-device.component.ts b/apps/web/src/app/accounts/login/login-with-device.component.ts index fabc2552208..82852000832 100644 --- a/apps/web/src/app/accounts/login/login-with-device.component.ts +++ b/apps/web/src/app/accounts/login/login-with-device.component.ts @@ -142,7 +142,7 @@ export class LoginWithDeviceComponent this.router.navigate([this.forcePasswordResetRoute]); } } else { - await this.setRememberEmailValues(); + await this.loginService.saveEmailSettings(); if (this.onSuccessfulLogin != null) { this.onSuccessfulLogin(); } @@ -202,12 +202,4 @@ export class LoginWithDeviceComponent localHashedPassword ); } - - private async setRememberEmailValues() { - const rememberEmail = this.loginService.getRememberEmail(); - const rememberedEmail = this.loginService.getEmail(); - await this.stateService.setRememberEmail(rememberEmail); - await this.stateService.setRememberedEmail(rememberEmail ? rememberedEmail : null); - this.loginService.clearValues(); - } } diff --git a/apps/web/src/app/accounts/login/login.component.html b/apps/web/src/app/accounts/login/login.component.html index 8202af0dfd8..b9529295671 100644 --- a/apps/web/src/app/accounts/login/login.component.html +++ b/apps/web/src/app/accounts/login/login.component.html @@ -120,7 +120,7 @@
{ component: BlankComponent, }, { - path: `organizations/${testOrgId}/manage/people`, + path: `organizations/${testOrgId}/manage/members`, component: BlankComponent, }, ]), @@ -301,7 +301,7 @@ describe("TrialInitiationComponent", () => { describe("navigateToOrgVault", () => { it("should call verticalStepper.previous()", fakeAsync(() => { component.navigateToOrgInvite(); - expect(routerSpy).toHaveBeenCalledWith(["organizations", testOrgId, "manage", "people"]); + expect(routerSpy).toHaveBeenCalledWith(["organizations", testOrgId, "manage", "members"]); })); }); }); diff --git a/apps/web/src/app/accounts/trial-initiation/trial-initiation.component.ts b/apps/web/src/app/accounts/trial-initiation/trial-initiation.component.ts index 2f3f75a4a1c..2ed89b8bb56 100644 --- a/apps/web/src/app/accounts/trial-initiation/trial-initiation.component.ts +++ b/apps/web/src/app/accounts/trial-initiation/trial-initiation.component.ts @@ -227,7 +227,7 @@ export class TrialInitiationComponent implements OnInit, OnDestroy { } navigateToOrgInvite() { - this.router.navigate(["organizations", this.orgId, "manage", "people"]); + this.router.navigate(["organizations", this.orgId, "manage", "members"]); } previousStep() { diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index ac349561c8f..8a37e5f44ac 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -103,6 +103,7 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service"; { provide: LoginServiceAbstraction, useClass: LoginService, + deps: [StateService], }, ], }) diff --git a/apps/web/src/app/core/state/state.service.ts b/apps/web/src/app/core/state/state.service.ts index 7c1eefe2701..577301cf867 100644 --- a/apps/web/src/app/core/state/state.service.ts +++ b/apps/web/src/app/core/state/state.service.ts @@ -48,23 +48,6 @@ export class StateService extends BaseStateService { await super.addAccount(account); } - async getRememberEmail(options?: StorageOptions) { - return ( - await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) - )?.rememberEmail; - } - - async setRememberEmail(value: boolean, options?: StorageOptions): Promise { - const globals = await this.getGlobals( - this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) - ); - globals.rememberEmail = value; - await this.saveGlobals( - globals, - this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) - ); - } - async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> { options = this.reconcileOptions(options, await this.defaultInMemoryOptions()); return await super.getEncryptedCiphers(options); diff --git a/apps/web/src/app/organizations/billing/organization-subscription.component.ts b/apps/web/src/app/organizations/billing/organization-subscription.component.ts index 803be05097c..1cfbd7ef868 100644 --- a/apps/web/src/app/organizations/billing/organization-subscription.component.ts +++ b/apps/web/src/app/organizations/billing/organization-subscription.component.ts @@ -75,6 +75,10 @@ export class OrganizationSubscriptionComponent implements OnInit, OnDestroy { } async ngOnInit() { + if (this.route.snapshot.queryParamMap.get("upgrade")) { + this.changePlan(); + } + this.route.params .pipe( concatMap(async (params) => { diff --git a/apps/web/src/app/organizations/manage/collections.component.ts b/apps/web/src/app/organizations/manage/collections.component.ts index f9e4675448a..a70ff2b12ed 100644 --- a/apps/web/src/app/organizations/manage/collections.component.ts +++ b/apps/web/src/app/organizations/manage/collections.component.ts @@ -11,6 +11,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { ProductType } from "@bitwarden/common/enums/productType"; import { CollectionData } from "@bitwarden/common/models/data/collection.data"; import { Collection } from "@bitwarden/common/models/domain/collection"; import { Organization } from "@bitwarden/common/models/domain/organization"; @@ -25,6 +26,7 @@ import { DialogService } from "@bitwarden/components"; import { CollectionDialogResult, openCollectionDialog } from "../shared"; import { EntityUsersComponent } from "./entity-users.component"; +import { OrgUpgradeDialogComponent } from "./org-upgrade-dialog/org-upgrade-dialog.component"; @Component({ selector: "app-org-manage-collections", @@ -130,6 +132,32 @@ export class CollectionsComponent implements OnInit { return; } + if ( + !collection && + this.organization.planProductType === ProductType.Free && + this.collections.length === this.organization.maxCollections + ) { + // Show org upgrade modal + const dialogBodyText = this.organization.canManageBilling + ? this.i18nService.t( + "freeOrgMaxCollectionReachedManageBilling", + this.organization.maxCollections.toString() + ) + : this.i18nService.t( + "freeOrgMaxCollectionReachedNoManageBilling", + this.organization.maxCollections.toString() + ); + + this.dialogService.open(OrgUpgradeDialogComponent, { + data: { + orgId: this.organization.id, + dialogBodyText: dialogBodyText, + orgCanManageBilling: this.organization.canManageBilling, + }, + }); + return; + } + const dialog = openCollectionDialog(this.dialogService, { data: { collectionId: collection?.id, organizationId: this.organizationId }, }); diff --git a/apps/web/src/app/organizations/manage/org-upgrade-dialog/org-upgrade-dialog.component.html b/apps/web/src/app/organizations/manage/org-upgrade-dialog/org-upgrade-dialog.component.html new file mode 100644 index 00000000000..16e99d76ff6 --- /dev/null +++ b/apps/web/src/app/organizations/manage/org-upgrade-dialog/org-upgrade-dialog.component.html @@ -0,0 +1,33 @@ + + + {{ "upgradeOrganization" | i18n }} + + {{ data.dialogBodyText }} + +
+ + + + + + + + +
+
diff --git a/apps/web/src/app/organizations/manage/org-upgrade-dialog/org-upgrade-dialog.component.ts b/apps/web/src/app/organizations/manage/org-upgrade-dialog/org-upgrade-dialog.component.ts new file mode 100644 index 00000000000..9ebdef9be1a --- /dev/null +++ b/apps/web/src/app/organizations/manage/org-upgrade-dialog/org-upgrade-dialog.component.ts @@ -0,0 +1,19 @@ +import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; + +export interface OrgUpgradeDialogData { + orgId: string; + orgCanManageBilling: boolean; + dialogBodyText: string; +} + +@Component({ + selector: "app-org-upgrade-dialog", + templateUrl: "org-upgrade-dialog.component.html", +}) +export class OrgUpgradeDialogComponent { + constructor( + public dialogRef: DialogRef, + @Inject(DIALOG_DATA) public data: OrgUpgradeDialogData + ) {} +} diff --git a/apps/web/src/app/organizations/members/people.component.html b/apps/web/src/app/organizations/members/people.component.html index 8518c61d254..2c847b062b1 100644 --- a/apps/web/src/app/organizations/members/people.component.html +++ b/apps/web/src/app/organizations/members/people.component.html @@ -84,7 +84,7 @@ }} {{ "name" | i18n }} - {{ (accessGroups ? "groups" : "collections") | i18n }} + {{ (organization.useGroups ? "groups" : "collections") | i18n }} {{ "role" | i18n }} {{ "policies" | i18n }} @@ -174,12 +174,12 @@ - {{ "all" | i18n }} + {{ "all" | i18n }} @@ -241,7 +241,12 @@ - diff --git a/apps/web/src/app/organizations/members/people.component.ts b/apps/web/src/app/organizations/members/people.component.ts index 73355fdc3dc..1cee76cd818 100644 --- a/apps/web/src/app/organizations/members/people.component.ts +++ b/apps/web/src/app/organizations/members/people.component.ts @@ -21,8 +21,10 @@ import { ValidationService } from "@bitwarden/common/abstractions/validation.ser import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; import { PolicyType } from "@bitwarden/common/enums/policyType"; +import { ProductType } from "@bitwarden/common/enums/productType"; import { CollectionData } from "@bitwarden/common/models/data/collection.data"; import { Collection } from "@bitwarden/common/models/domain/collection"; +import { Organization } from "@bitwarden/common/models/domain/organization"; import { OrganizationKeysRequest } from "@bitwarden/common/models/request/organization-keys.request"; import { OrganizationUserBulkRequest } from "@bitwarden/common/models/request/organization-user-bulk.request"; import { OrganizationUserConfirmRequest } from "@bitwarden/common/models/request/organization-user-confirm.request"; @@ -36,6 +38,7 @@ import { BasePeopleComponent } from "../../common/base.people.component"; import { GroupService } from "../core"; import { OrganizationUserView } from "../core/views/organization-user.view"; import { EntityEventsComponent } from "../manage/entity-events.component"; +import { OrgUpgradeDialogComponent } from "../manage/org-upgrade-dialog/org-upgrade-dialog.component"; import { BulkConfirmComponent } from "./components/bulk/bulk-confirm.component"; import { BulkRemoveComponent } from "./components/bulk/bulk-remove.component"; @@ -71,15 +74,9 @@ export class PeopleComponent userType = OrganizationUserType; userStatusType = OrganizationUserStatusType; - organizationId: string; + organization: Organization; status: OrganizationUserStatusType = null; - accessEvents = false; - accessGroups = false; - canResetPassword = false; // User permission (admin/custom) - orgUseResetPassword = false; // Org plan ability - orgHasKeys = false; // Org public/private keys orgResetPasswordPolicyEnabled = false; - callingUserType: OrganizationUserType = null; private destroy$ = new Subject(); @@ -123,26 +120,23 @@ export class PeopleComponent combineLatest([this.route.params, this.route.queryParams, this.policyService.policies$]) .pipe( concatMap(async ([params, qParams, policies]) => { - this.organizationId = params.organizationId; - const organization = await this.organizationService.get(this.organizationId); - this.accessEvents = organization.useEvents; - this.accessGroups = organization.useGroups; - this.canResetPassword = organization.canManageUsersPassword; - this.orgUseResetPassword = organization.useResetPassword; - this.callingUserType = organization.type; - this.orgHasKeys = organization.hasPublicAndPrivateKeys; + this.organization = await this.organizationService.get(params.organizationId); // Backfill pub/priv key if necessary - if (this.canResetPassword && !this.orgHasKeys) { - const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId); + if ( + this.organization.canManageUsersPassword && + !this.organization.hasPublicAndPrivateKeys + ) { + const orgShareKey = await this.cryptoService.getOrgKey(this.organization.id); const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey); const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); const response = await this.organizationApiService.updateKeys( - this.organizationId, + this.organization.id, request ); if (response != null) { - this.orgHasKeys = response.publicKey != null && response.privateKey != null; + this.organization.hasPublicAndPrivateKeys = + response.publicKey != null && response.privateKey != null; await this.syncService.fullSync(true); // Replace oganizations with new data } else { throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); @@ -151,7 +145,7 @@ export class PeopleComponent const resetPasswordPolicy = policies .filter((policy) => policy.type === PolicyType.ResetPassword) - .find((p) => p.organizationId === this.organizationId); + .find((p) => p.organizationId === this.organization.id); this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled; await this.load(); @@ -183,13 +177,13 @@ export class PeopleComponent let collectionsPromise: Promise>; // We don't need both groups and collections for the table, so only load one - const userPromise = this.apiService.getOrganizationUsers(this.organizationId, { - includeGroups: this.accessGroups, - includeCollections: !this.accessGroups, + const userPromise = this.apiService.getOrganizationUsers(this.organization.id, { + includeGroups: this.organization.useGroups, + includeCollections: !this.organization.useGroups, }); // Depending on which column is displayed, we need to load the group/collection names - if (this.accessGroups) { + if (this.organization.useGroups) { groupsPromise = this.getGroupNameMap(); } else { collectionsPromise = this.getCollectionNameMap(); @@ -216,7 +210,7 @@ export class PeopleComponent } async getGroupNameMap(): Promise> { - const groups = await this.groupService.getAll(this.organizationId); + const groups = await this.groupService.getAll(this.organization.id); const groupNameMap = new Map(); groups.forEach((g) => groupNameMap.set(g.id, g.name)); return groupNameMap; @@ -227,7 +221,7 @@ export class PeopleComponent */ async getCollectionNameMap() { const collectionMap = new Map(); - const response = await this.apiService.getCollections(this.organizationId); + const response = await this.apiService.getCollections(this.organization.id); const collections = response.data.map( (r) => new Collection(new CollectionData(r as CollectionDetailsResponse)) @@ -240,34 +234,34 @@ export class PeopleComponent } deleteUser(id: string): Promise { - return this.apiService.deleteOrganizationUser(this.organizationId, id); + return this.apiService.deleteOrganizationUser(this.organization.id, id); } revokeUser(id: string): Promise { - return this.apiService.revokeOrganizationUser(this.organizationId, id); + return this.apiService.revokeOrganizationUser(this.organization.id, id); } restoreUser(id: string): Promise { - return this.apiService.restoreOrganizationUser(this.organizationId, id); + return this.apiService.restoreOrganizationUser(this.organization.id, id); } reinviteUser(id: string): Promise { - return this.apiService.postOrganizationUserReinvite(this.organizationId, id); + return this.apiService.postOrganizationUserReinvite(this.organization.id, id); } async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise { - const orgKey = await this.cryptoService.getOrgKey(this.organizationId); + const orgKey = await this.cryptoService.getOrgKey(this.organization.id); const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer); const request = new OrganizationUserConfirmRequest(); request.key = key.encryptedString; - await this.apiService.postOrganizationUserConfirm(this.organizationId, user.id, request); + await this.apiService.postOrganizationUserConfirm(this.organization.id, user.id, request); } allowResetPassword(orgUser: OrganizationUserView): boolean { // Hierarchy check let callingUserHasPermission = false; - switch (this.callingUserType) { + switch (this.organization.type) { case OrganizationUserType.Owner: callingUserHasPermission = true; break; @@ -283,10 +277,10 @@ export class PeopleComponent // Final return ( - this.canResetPassword && + this.organization.canManageUsersPassword && callingUserHasPermission && - this.orgUseResetPassword && - this.orgHasKeys && + this.organization.useResetPassword && + this.organization.hasPublicAndPrivateKeys && orgUser.resetPasswordEnrolled && this.orgResetPasswordPolicyEnabled && orgUser.status === OrganizationUserStatusType.Confirmed @@ -295,17 +289,48 @@ export class PeopleComponent showEnrolledStatus(orgUser: OrganizationUserUserDetailsResponse): boolean { return ( - this.orgUseResetPassword && + this.organization.useResetPassword && orgUser.resetPasswordEnrolled && this.orgResetPasswordPolicyEnabled ); } async edit(user: OrganizationUserView) { + // Invite User: Add Flow + // Click on user email: Edit Flow + + // User attempting to invite new users in a free org with max users + if ( + !user && + this.organization.planProductType === ProductType.Free && + this.users.length === this.organization.seats + ) { + // Show org upgrade modal + + const dialogBodyText = this.organization.canManageBilling + ? this.i18nService.t( + "freeOrgInvLimitReachedManageBilling", + this.organization.seats.toString() + ) + : this.i18nService.t( + "freeOrgInvLimitReachedNoManageBilling", + this.organization.seats.toString() + ); + + this.dialogService.open(OrgUpgradeDialogComponent, { + data: { + orgId: this.organization.id, + orgCanManageBilling: this.organization.canManageBilling, + dialogBodyText: dialogBodyText, + }, + }); + return; + } + const dialog = openUserAddEditDialog(this.dialogService, { data: { name: this.userNamePipe.transform(user), - organizationId: this.organizationId, + organizationId: this.organization.id, organizationUserId: user != null ? user.id : null, usesKeyConnector: user?.usesKeyConnector, }, @@ -330,7 +355,7 @@ export class PeopleComponent this.groupsModalRef, (comp) => { comp.name = this.userNamePipe.transform(user); - comp.organizationId = this.organizationId; + comp.organizationId = this.organization.id; comp.organizationUserId = user != null ? user.id : null; // eslint-disable-next-line rxjs-angular/prefer-takeuntil comp.onSavedUser.subscribe(() => { @@ -350,7 +375,7 @@ export class PeopleComponent BulkRemoveComponent, this.bulkRemoveModalRef, (comp) => { - comp.organizationId = this.organizationId; + comp.organizationId = this.organization.id; comp.users = this.getCheckedUsers(); } ); @@ -375,7 +400,7 @@ export class PeopleComponent const ref = this.modalService.open(BulkRestoreRevokeComponent, { allowMultipleModals: true, data: { - organizationId: this.organizationId, + organizationId: this.organization.id, users: this.getCheckedUsers(), isRevoking: isRevoking, }, @@ -405,7 +430,7 @@ export class PeopleComponent try { const request = new OrganizationUserBulkRequest(filteredUsers.map((user) => user.id)); const response = this.apiService.postManyOrganizationUserReinvite( - this.organizationId, + this.organization.id, request ); this.showBulkStatus( @@ -429,7 +454,7 @@ export class PeopleComponent BulkConfirmComponent, this.bulkConfirmModalRef, (comp) => { - comp.organizationId = this.organizationId; + comp.organizationId = this.organization.id; comp.users = this.getCheckedUsers(); } ); @@ -441,7 +466,7 @@ export class PeopleComponent async events(user: OrganizationUserView) { await this.modalService.openViewRef(EntityEventsComponent, this.eventsModalRef, (comp) => { comp.name = this.userNamePipe.transform(user); - comp.organizationId = this.organizationId; + comp.organizationId = this.organization.id; comp.entityId = user.id; comp.showUser = false; comp.entity = "user"; @@ -455,7 +480,7 @@ export class PeopleComponent (comp) => { comp.name = this.userNamePipe.transform(user); comp.email = user != null ? user.email : null; - comp.organizationId = this.organizationId; + comp.organizationId = this.organization.id; comp.id = user != null ? user.id : null; // eslint-disable-next-line rxjs-angular/prefer-takeuntil diff --git a/apps/web/src/app/organizations/organization.module.ts b/apps/web/src/app/organizations/organization.module.ts index 3188e97d798..cdfffc240e8 100644 --- a/apps/web/src/app/organizations/organization.module.ts +++ b/apps/web/src/app/organizations/organization.module.ts @@ -3,11 +3,18 @@ import { NgModule } from "@angular/core"; import { CoreOrganizationModule } from "./core"; import { GroupAddEditComponent } from "./manage/group-add-edit.component"; import { GroupsComponent } from "./manage/groups.component"; +import { OrgUpgradeDialogComponent } from "./manage/org-upgrade-dialog/org-upgrade-dialog.component"; import { OrganizationsRoutingModule } from "./organization-routing.module"; import { SharedOrganizationModule } from "./shared"; +import { AccessSelectorModule } from "./shared/components/access-selector"; @NgModule({ - imports: [SharedOrganizationModule, CoreOrganizationModule, OrganizationsRoutingModule], - declarations: [GroupsComponent, GroupAddEditComponent], + imports: [ + SharedOrganizationModule, + AccessSelectorModule, + CoreOrganizationModule, + OrganizationsRoutingModule, + ], + declarations: [GroupsComponent, GroupAddEditComponent, OrgUpgradeDialogComponent], }) export class OrganizationModule {} diff --git a/apps/web/src/app/send/add-edit.component.html b/apps/web/src/app/send/add-edit.component.html index dd9f61c6c58..a112c7be156 100644 --- a/apps/web/src/app/send/add-edit.component.html +++ b/apps/web/src/app/send/add-edit.component.html @@ -141,14 +141,16 @@ class="section-header d-flex flex-row align-items-center mt-5" (click)="toggleOptions()" > -

{{ "options" | i18n }}

-
- - +

+ +

@@ -65,8 +65,8 @@ const AnchorTemplate: Story = (args: AnchorLinkDirective) =
diff --git a/libs/components/src/menu/menu-trigger-for.directive.ts b/libs/components/src/menu/menu-trigger-for.directive.ts index 059e6f812e6..37ea9de650b 100644 --- a/libs/components/src/menu/menu-trigger-for.directive.ts +++ b/libs/components/src/menu/menu-trigger-for.directive.ts @@ -49,7 +49,7 @@ export class MenuTriggerForDirective implements OnDestroy { ]) .withLockedPosition(true) .withFlexibleDimensions(false) - .withPush(false), + .withPush(true), }; private closedEventsSub: Subscription; private keyDownEventsSub: Subscription; diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 5829701c9fe..9cd83fa6ac3 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -42,6 +42,7 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI protected toggle(event?: MouseEvent) { event?.stopPropagation(); this.open = !this.open; + this.openChange.emit(this.open); } /** diff --git a/libs/components/src/stories/icons.stories.mdx b/libs/components/src/stories/icons.stories.mdx index 3b401695ee8..da8896a904c 100644 --- a/libs/components/src/stories/icons.stories.mdx +++ b/libs/components/src/stories/icons.stories.mdx @@ -61,7 +61,7 @@ Avoid using icons to convey information unless paired with meaningful, clear tex | | bwi-lock-f | - | | | bwi-minus-circle | remove action | | | bwi-minus-square | unselect all action | -| | bwi-paste | paste from clipbaord action | +| | bwi-paste | paste from clipboard action | | | bwi-pencil-square | edit action | | | bwi-play | start or play action | | | bwi-plus | new or add option in contained buttons/links | @@ -88,20 +88,20 @@ Avoid using icons to convey information unless paired with meaningful, clear tex | Icon | bwi-name | Usage | | ------------------------------------------ | ---------------------- | ------------------------------------------------------- | -| | bwi-angle-down | drop down or expandable options | +| | bwi-angle-down | closed dropdown or open expandable section | | | bwi-angle-left | - | -| | bwi-angle-right | collapsed section that can be expanded | +| | bwi-angle-right | closed expandable section | | | bwi-arrow-circle-down | table sort order | | | bwi-arrow-circle-left | - | | | bwi-arrow-circle-right | - | | | bwi-arrow-circle-up | table sort order | | | bwi-caret-down | - | | | bwi-caret-right | - | -| | bwi-chevron-up | - | +| | bwi-chevron-up | open dropdown | | | bwi-dbl-angle-left | - | | | bwi-dbl-angle-right | - | | | bwi-ellipsis-h | more options menu horizontal; used in mobile list items | -| | bwi-ellipsis-v | more optioins menu vertical; used primarily in tables | +| | bwi-ellipsis-v | more options menu vertical; used primarily in tables | | | bwi-filter | Product switcher | | | bwi-hamburger | navigation indicator | | | bwi-list | toggle list/grid view | @@ -159,11 +159,11 @@ Avoid using icons to convey information unless paired with meaningful, clear tex | | bwi-tag | labels | | | bwi-thumb-tack | - | | | bwi-thumbs-up | - | -| | bwi-universal-access | use for accessiblity related actions | +| | bwi-universal-access | use for accessibility related actions | | | bwi-user | relates to current user or organization member | | | bwi-user-circle | - | | | bwi-user-f | - | -| | bwi-wrench | tools or aditional configuration options | +| | bwi-wrench | tools or additional configuration options | ## Platforms and Logos diff --git a/libs/components/tailwind.config.base.js b/libs/components/tailwind.config.base.js index a700a3377dd..2420c3dc851 100644 --- a/libs/components/tailwind.config.base.js +++ b/libs/components/tailwind.config.base.js @@ -1,5 +1,6 @@ /* eslint-disable */ const colors = require("tailwindcss/colors"); +const plugin = require("tailwindcss/plugin"); function rgba(color) { return "rgb(var(" + color + ") / )"; @@ -94,5 +95,25 @@ module.exports = { }), }, }, - plugins: [], + plugins: [ + plugin(function ({ matchUtilities, theme, addUtilities, addComponents, e, config }) { + matchUtilities( + { + "mask-image": (value) => ({ + "-webkit-mask-image": value, + "mask-image": value, + }), + "mask-position": (value) => ({ + "-webkit-mask-position": value, + "mask-position": value, + }), + "mask-repeat": (value) => ({ + "-webkit-mask-repeat": value, + "mask-repeat": value, + }), + }, + {} + ); + }), + ], };