Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/browser/src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,12 @@
"myFolders": {
"message": "My folders"
},
"addSharedFolder": {
"message": "Add shared folder"
},
"newSharedFolder": {
"message": "New shared folder"
},
"bitwardenForBusinessPageDesc": {
"message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website."
},
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/app/services/services.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ import {
RoutedVaultFilterService,
RoutedVaultFilterBridgeService,
VAULT_FILTER_BASE_ROUTE,
Vfo1TerminologyService,
} from "@bitwarden/vault";

import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service";
Expand Down Expand Up @@ -615,6 +616,7 @@ const safeProviders: SafeProvider[] = [
CollectionService,
AccountServiceAbstraction,
ConfigService,
Vfo1TerminologyService,
],
}),
safeProvider({
Expand Down
6 changes: 6 additions & 0 deletions apps/desktop/src/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@
"myFolders": {
"message": "My folders"
},
"addSharedFolder": {
"message": "Add shared folder"
},
"newSharedFolder": {
"message": "New shared folder"
},
"share": {
"message": "Share"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { StateProvider } from "@bitwarden/common/platform/state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { VaultFilterService as BaseVaultFilterService, CollectionFilter } from "@bitwarden/vault";
import {
VaultFilterService as BaseVaultFilterService,
CollectionFilter,
Vfo1TerminologyService,
} from "@bitwarden/vault";

@Injectable()
export class VaultFilterService extends BaseVaultFilterService implements OnDestroy {
Expand All @@ -35,6 +39,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest
collectionService: CollectionService,
accountService: AccountService,
configService: ConfigService,
vfo1Terminology: Vfo1TerminologyService,
) {
super(
organizationService,
Expand All @@ -46,6 +51,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest
collectionService,
accountService,
configService,
vfo1Terminology,
);
}

Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,12 @@
"myFolders": {
"message": "My folders"
},
"addSharedFolder": {
"message": "Add shared folder"
},
"newSharedFolder": {
"message": "New shared folder"
},
"attachments": {
"message": "Attachments"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ <h2 bitTypography="h6">{{ "itemDetails" | i18n }}</h2>
@if (showCollectionsControl) {
<ng-container>
<bit-form-field class="tw-w-full" [disableMargin]="config.hideIndividualVaultFields">
<bit-label>{{ "collections" | i18n }}</bit-label>
<bit-label>{{ "collections" | vfo1I18n: "sharedFolders" }}</bit-label>
<bit-multi-select
class="tw-w-full"
formControlName="collectionIds"
Expand All @@ -55,7 +55,7 @@ <h2 bitTypography="h6">{{ "itemDetails" | i18n }}</h2>
}
@if (!config.hideIndividualVaultFields) {
<bit-form-field disableMargin>
<bit-label>{{ "folder" | i18n }}</bit-label>
<bit-label>{{ "folder" | vfo1I18n: "myFolder" }}</bit-label>
<bit-select formControlName="folderId">
@for (folder of config.folders; track folder.id) {
<bit-option [value]="folder.id" [label]="folder.name"></bit-option>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
TypographyModule,
} from "@bitwarden/components";

import { Vfo1I18nPipe } from "../../../pipes/vfo1-i18n.pipe";
import {
CipherFormConfig,
OptionalInitialValues,
Expand All @@ -55,6 +56,7 @@ import { CipherFormContainer } from "../../cipher-form-container";
IconButtonModule,
JslibModule,
CommonModule,
Vfo1I18nPipe,
],
})
export class ItemDetailsSectionComponent implements OnInit {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DomainSettingsService } from "@bitwarden/common/autofill/services/domai
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";

Expand Down Expand Up @@ -88,6 +89,27 @@ describe("ItemDetailsV2Component", () => {
expect(itemDetailsList[3].nativeElement.textContent.trim()).toContain(folder.name);
});

describe("getAriaLabel", () => {
it("separates the label and name with a space for organizations", () => {
const org = Object.assign(new Organization(), { name: "Organization 1" });
expect(component.getAriaLabel(org)).toBe("owner Organization 1");
});

it("separates the label and name with a space for collections", () => {
const col = new CollectionView({
id: "col1" as CollectionId,
organizationId: "org1" as OrganizationId,
name: "Collection 1",
});
expect(component.getAriaLabel(col)).toBe("collection Collection 1");
});

it("separates the label and name with a space for folders", () => {
const folderView = Object.assign(new FolderView(), { name: "Folder 1" });
expect(component.getAriaLabel(folderView)).toBe("folder Folder 1");
});
});

it("does not render owner when `hideOwner` is true", () => {
componentRef.setInput("hideOwner", true);
fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, computed, input, signal } from "@angular/core";
import { Component, computed, inject, input, signal } from "@angular/core";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
import { toSignal } from "@angular/core/rxjs-interop";
import { fromEvent, map, startWith } from "rxjs";
Expand All @@ -23,6 +23,7 @@ import {
} from "@bitwarden/components";

import { OrgIconDirective } from "../../components/org-icon.directive";
import { Vfo1TerminologyService } from "../../services/vfo1-terminology.service";

// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
Expand All @@ -47,6 +48,8 @@ export class ItemDetailsV2Component {
readonly collections = input<CollectionView[] | undefined>();
readonly showAllDetails = signal(false);

private readonly terminology = inject(Vfo1TerminologyService);

readonly showOwnership = computed(() => {
return this.cipher().organizationId && this.organization() && !this.hideOwner();
});
Expand Down Expand Up @@ -95,11 +98,13 @@ export class ItemDetailsV2Component {

getAriaLabel(item: Organization | CollectionView | FolderView): string {
if (item instanceof Organization) {
return this.i18nService.t("owner") + item.name;
return `${this.i18nService.t("owner")} ${item.name}`;
} else if (item instanceof CollectionView) {
return this.i18nService.t("collection") + item.name;
const label = this.i18nService.t(this.terminology.enabled() ? "sharedFolder" : "collection");
return `${label} ${item.name}`;
} else if (item instanceof FolderView) {
return this.i18nService.t("folder") + item.name;
const label = this.i18nService.t(this.terminology.enabled() ? "myFolder" : "folder");
return `${label} ${item.name}`;
}
return "";
}
Expand All @@ -117,9 +122,9 @@ export class ItemDetailsV2Component {

getItemTitle(item: Organization | CollectionView | FolderView): string {
if (item instanceof CollectionView) {
return this.i18nService.t("collection");
return this.i18nService.t(this.terminology.enabled() ? "sharedFolder" : "collection");
} else if (item instanceof FolderView) {
return this.i18nService.t("folder");
return this.i18nService.t(this.terminology.enabled() ? "myFolder" : "folder");
}
return "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
@if (canCreateCollection()) {
<button type="button" bitMenuItem (click)="collectionAdded.emit()">
<i class="bwi bwi-fw bwi-collection-shared" aria-hidden="true"></i>
{{ "collection" | i18n }}
{{ "collection" | vfo1I18n: "sharedFolder" }}
</button>
}
</bit-menu>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { Component, input, output } from "@angular/core";
import { Component, inject, input, output } from "@angular/core";
import { toObservable, toSignal } from "@angular/core/rxjs-interop";
import { combineLatest, map, shareReplay } from "rxjs";

Expand All @@ -21,6 +21,9 @@ import {
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";

import { Vfo1I18nPipe } from "../../pipes/vfo1-i18n.pipe";
import { Vfo1TerminologyService } from "../../services/vfo1-terminology.service";

// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
Expand All @@ -33,6 +36,7 @@ import { I18nPipe } from "@bitwarden/ui-common";
PopoverModule,
I18nPipe,
JslibModule,
Vfo1I18nPipe,
TooltipDirective,
],
})
Expand Down Expand Up @@ -62,6 +66,8 @@ export class NewCipherMenuComponent {
cipherAdded = output<CipherType>();
onAddItemDialog = output();

private readonly terminology = inject(Vfo1TerminologyService);

private readonly btnTextAddCreateFeatureFlag = toSignal(
this.configService.getFeatureFlag$(FeatureFlag.PM32380_BtnTextAddCreate),
{ initialValue: false },
Expand Down Expand Up @@ -113,10 +119,11 @@ export class NewCipherMenuComponent {

// If only collections can be created, be specific
if (!canCreateCipher && !canCreateFolder && canCreateCollection) {
const sharedFolderTerminology = this.terminology.enabled();
if (btnTextAddCreateFeatureFlag) {
return "addCollection";
return sharedFolderTerminology ? "addSharedFolder" : "addCollection";
} else {
return "newCollection";
return sharedFolderTerminology ? "newSharedFolder" : "newCollection";
}
}

Expand Down
22 changes: 22 additions & 0 deletions libs/vault/src/services/vault-filter.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { COLLAPSED_GROUPINGS } from "@bitwarden/common/vault/services/key-state/collapsed-groupings.state";

import { VaultFilterService } from "./vault-filter.service";
import { Vfo1TerminologyService } from "./vfo1-terminology.service";

jest.mock("@bitwarden/angular/vault/vault-filter/services/vault-filter.service", () => ({
sortDefaultCollections: jest.fn((): CollectionView[] => []),
Expand All @@ -54,6 +55,7 @@ describe("vault filter service", () => {
let autoConfirmPolicy: ReplaySubject<boolean>;
let stateProvider: FakeStateProvider;
let configService: MockProxy<ConfigService>;
let vfo1Terminology: MockProxy<Vfo1TerminologyService>;

const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
Expand All @@ -70,6 +72,8 @@ describe("vault filter service", () => {
i18nService.collator = new Intl.Collator("en-US");
collectionService = mock<CollectionService>();
configService = mock<ConfigService>();
vfo1Terminology = mock<Vfo1TerminologyService>();
vfo1Terminology.enabled.mockReturnValue(false);

organizations = new ReplaySubject<Organization[]>(1);
folderViews = new ReplaySubject<FolderView[]>(1);
Expand Down Expand Up @@ -104,11 +108,28 @@ describe("vault filter service", () => {
collectionService,
accountService,
configService,
vfo1Terminology,
);
collapsedGroupingsState = stateProvider.singleUser.getFake(mockUserId, COLLAPSED_GROUPINGS);
organizations.next([]);
});

describe("shared-folder terminology filter heads", () => {
it("uses collection/folder head names when the flag is off", () => {
vfo1Terminology.enabled.mockReturnValue(false);

expect(vaultFilterService["getCollectionFilterHead"]().node.name).toBe("collections");
expect(vaultFilterService["getFolderFilterHead"]().node.name).toBe("folders");
});

it("uses shared-folder/my-folder head names when the flag is on", () => {
vfo1Terminology.enabled.mockReturnValue(true);

expect(vaultFilterService["getCollectionFilterHead"]().node.name).toBe("sharedFolders");
expect(vaultFilterService["getFolderFilterHead"]().node.name).toBe("myFolders");
});
});

describe("collapsed filter nodes", () => {
const nodes = new Set(["1", "2"]);

Expand Down Expand Up @@ -382,6 +403,7 @@ describe("vault filter service", () => {
collectionService,
accountService,
configService,
vfo1Terminology,
);
});

Expand Down
9 changes: 7 additions & 2 deletions libs/vault/src/services/vault-filter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import {
OrganizationFilter,
} from "..";

import { Vfo1TerminologyService } from "./vfo1-terminology.service";

const NestingDelimiter = "/";

@Injectable()
Expand Down Expand Up @@ -211,6 +213,7 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
protected collectionService: CollectionService,
protected accountService: AccountService,
protected configService: ConfigService,
protected vfo1Terminology: Vfo1TerminologyService,
) {}

async getCollectionNodeFromTree(id: string) {
Expand Down Expand Up @@ -358,7 +361,8 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {

protected getCollectionFilterHead(): TreeNode<CollectionFilter> {
const head = CollectionView.vaultFilterHead() as CollectionFilter;
return new TreeNode<CollectionFilter>(head, null, "collections", "AllCollections");
const name = this.vfo1Terminology.enabled() ? "sharedFolders" : "collections";
return new TreeNode<CollectionFilter>(head, null, name, "AllCollections");
}

protected async filterFolders(
Expand Down Expand Up @@ -401,7 +405,8 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {

protected getFolderFilterHead(): TreeNode<FolderFilter> {
const head = new FolderView() as FolderFilter;
return new TreeNode<FolderFilter>(head, null, "folders", "AllFolders");
const name = this.vfo1Terminology.enabled() ? "myFolders" : "folders";
return new TreeNode<FolderFilter>(head, null, name, "AllFolders");
}

protected buildCipherTypeTree(): Observable<TreeNode<CipherTypeFilter>> {
Expand Down
Loading