mirror of
https://github.com/bitwarden/browser
synced 2026-01-29 15:53:45 +00:00
reverted search component, added search directly to vault-list
This commit is contained in:
@@ -26,6 +26,7 @@ import { UserVerificationComponent } from "./components/user-verification.compon
|
||||
import { AccountSwitcherComponent } from "./layout/account-switcher.component";
|
||||
import { HeaderComponent } from "./layout/header.component";
|
||||
import { NavComponent } from "./layout/nav.component";
|
||||
import { SearchComponent } from "./layout/search/search.component";
|
||||
import { SharedModule } from "./shared/shared.module";
|
||||
|
||||
@NgModule({
|
||||
@@ -50,6 +51,7 @@ import { SharedModule } from "./shared/shared.module";
|
||||
ColorPasswordCountPipe,
|
||||
HeaderComponent,
|
||||
PremiumComponent,
|
||||
SearchComponent,
|
||||
],
|
||||
providers: [
|
||||
SshAgentService,
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
@if (state.enabled) {
|
||||
<bit-search [placeholder]="state.placeholderText" [formControl]="searchText" appAutofocus>
|
||||
</bit-search>
|
||||
}
|
||||
<div class="search" *ngIf="state.enabled">
|
||||
<input
|
||||
type="search"
|
||||
[placeholder]="state.placeholderText"
|
||||
id="search"
|
||||
autocomplete="off"
|
||||
[formControl]="searchText"
|
||||
appAutofocus
|
||||
/>
|
||||
<i class="bwi bwi-search" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ReactiveFormsModule, UntypedFormControl } from "@angular/forms";
|
||||
import { UntypedFormControl } from "@angular/forms";
|
||||
import { Subscription } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AutofocusDirective, SearchModule } from "@bitwarden/components";
|
||||
|
||||
import { SearchBarService, SearchBarState } from "./search-bar.service";
|
||||
|
||||
@@ -15,7 +13,7 @@ import { SearchBarService, SearchBarState } from "./search-bar.service";
|
||||
@Component({
|
||||
selector: "app-search",
|
||||
templateUrl: "search.component.html",
|
||||
imports: [CommonModule, ReactiveFormsModule, AutofocusDirective, SearchModule],
|
||||
standalone: false,
|
||||
})
|
||||
export class SearchComponent implements OnInit, OnDestroy {
|
||||
state: SearchBarState;
|
||||
|
||||
@@ -12,7 +12,13 @@
|
||||
</app-header>
|
||||
</div>
|
||||
<div class="tw-mb-[16px]">
|
||||
<app-search />
|
||||
<bit-search
|
||||
[ngModel]="searchText"
|
||||
(ngModelChange)="onSearchTextChanged($event)"
|
||||
[placeholder]="placeholderText()"
|
||||
appAutofocus
|
||||
>
|
||||
</bit-search>
|
||||
</div>
|
||||
<bit-table [dataSource]="dataSource" layout="fixed">
|
||||
<ng-container header>
|
||||
|
||||
@@ -5,12 +5,14 @@ import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||
import { AsyncPipe } from "@angular/common";
|
||||
import { Component, input, output, effect, inject, computed } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { Observable, of, switchMap } from "rxjs";
|
||||
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import {
|
||||
@@ -28,14 +30,13 @@ import {
|
||||
MenuModule,
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
SearchModule,
|
||||
} from "@bitwarden/components";
|
||||
import { OrganizationId } from "@bitwarden/sdk-internal";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
import { NewCipherMenuComponent, VaultItem, VaultItemEvent } from "@bitwarden/vault";
|
||||
|
||||
import { DesktopHeaderComponent } from "../../../app/layout/header/desktop-header.component";
|
||||
import { SearchBarService } from "../../../app/layout/search/search-bar.service";
|
||||
import { SearchComponent } from "../../../app/layout/search/search.component";
|
||||
|
||||
import { VaultItemsModule } from "./vault-items/vault-items.module";
|
||||
|
||||
@@ -57,7 +58,8 @@ export const RowHeightClass = `tw-h-[75px]`;
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
VaultItemsModule,
|
||||
SearchComponent,
|
||||
SearchModule,
|
||||
FormsModule,
|
||||
DesktopHeaderComponent,
|
||||
NewCipherMenuComponent,
|
||||
],
|
||||
@@ -80,6 +82,7 @@ export class VaultListComponent<C extends CipherViewLike> {
|
||||
protected readonly activeCollection = input<CollectionView | undefined>();
|
||||
protected readonly userCanArchive = input<boolean>();
|
||||
protected readonly enforceOrgDataOwnershipPolicy = input<boolean>();
|
||||
protected readonly placeholderText = input<string>("");
|
||||
|
||||
protected readonly ciphers = input<C[]>([]);
|
||||
|
||||
@@ -92,20 +95,17 @@ export class VaultListComponent<C extends CipherViewLike> {
|
||||
protected cipherAuthorizationService = inject(CipherAuthorizationService);
|
||||
protected restrictedItemTypesService = inject(RestrictedItemTypesService);
|
||||
protected cipherArchiveService = inject(CipherArchiveService);
|
||||
private searchBarService = inject(SearchBarService);
|
||||
private i18nService = inject(I18nService);
|
||||
private searchService = inject(SearchService);
|
||||
private searchPipe = inject(SearchPipe);
|
||||
|
||||
protected dataSource = new TableDataSource<VaultItem<C>>();
|
||||
protected selection = new SelectionModel<VaultItem<C>>(true, [], true);
|
||||
private restrictedTypes: RestrictedCipherType[] = [];
|
||||
protected searchText = "";
|
||||
|
||||
protected archiveFeatureEnabled$ = this.cipherArchiveService.hasArchiveFlagEnabled$;
|
||||
|
||||
constructor() {
|
||||
// Enable the search bar
|
||||
this.searchBarService.setEnabled(true);
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault"));
|
||||
|
||||
this.restrictedItemTypesService.restricted$.pipe(takeUntilDestroyed()).subscribe((types) => {
|
||||
this.restrictedTypes = types;
|
||||
this.refreshItems();
|
||||
@@ -133,6 +133,11 @@ export class VaultListComponent<C extends CipherViewLike> {
|
||||
this.onAddFolder.emit();
|
||||
}
|
||||
|
||||
protected onSearchTextChanged(searchText: string) {
|
||||
this.searchText = searchText;
|
||||
this.refreshItems();
|
||||
}
|
||||
|
||||
protected canClone$(vaultItem: VaultItem<C>): Observable<boolean> {
|
||||
return this.restrictedItemTypesService.restricted$.pipe(
|
||||
switchMap((restrictedTypes) => {
|
||||
@@ -202,14 +207,25 @@ export class VaultListComponent<C extends CipherViewLike> {
|
||||
}
|
||||
|
||||
private refreshItems() {
|
||||
const collections: VaultItem<C>[] =
|
||||
this.collections()?.map((collection) => ({ collection })) || [];
|
||||
const ciphers: VaultItem<C>[] = this.ciphers()
|
||||
.filter(
|
||||
(cipher) =>
|
||||
!this.restrictedItemTypesService.isCipherRestricted(cipher, this.restrictedTypes),
|
||||
)
|
||||
.map((cipher) => ({ cipher }));
|
||||
const filteredCollections: CollectionView[] = this.searchText
|
||||
? this.searchPipe.transform(
|
||||
this.collections() || [],
|
||||
this.searchText,
|
||||
(collection) => collection.name,
|
||||
(collection) => collection.id,
|
||||
)
|
||||
: this.collections() || [];
|
||||
|
||||
const allowedCiphers = this.ciphers().filter(
|
||||
(cipher) => !this.restrictedItemTypesService.isCipherRestricted(cipher, this.restrictedTypes),
|
||||
);
|
||||
|
||||
const filteredCiphers: C[] = this.searchText
|
||||
? this.searchService.searchCiphersBasic(allowedCiphers, this.searchText)
|
||||
: allowedCiphers;
|
||||
|
||||
const collections: VaultItem<C>[] = filteredCollections.map((collection) => ({ collection }));
|
||||
const ciphers: VaultItem<C>[] = filteredCiphers.map((cipher) => ({ cipher }));
|
||||
const items: VaultItem<C>[] = [].concat(collections).concat(ciphers);
|
||||
|
||||
this.dataSource.data = items;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[showAdminActions]="false"
|
||||
[userCanArchive]="userCanArchive$ | async"
|
||||
[enforceOrgDataOwnershipPolicy]="enforceOrgDataOwnershipPolicy$ | async"
|
||||
[placeholderText]="searchPlaceholderText"
|
||||
(onEvent)="onVaultItemsEvent($event)"
|
||||
(onAddCipher)="addCipher($event)"
|
||||
(onAddFolder)="addFolder()"
|
||||
|
||||
@@ -18,8 +18,6 @@ import {
|
||||
switchMap,
|
||||
lastValueFrom,
|
||||
Observable,
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
} from "rxjs";
|
||||
@@ -27,7 +25,6 @@ import { filter, map, take, first, shareReplay, concatMap, tap } from "rxjs/oper
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
@@ -35,10 +32,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { CollectionView, Unassigned } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import {
|
||||
getNestedCollectionTree,
|
||||
getFlatCollectionTree,
|
||||
} from "@bitwarden/common/admin-console/utils";
|
||||
import { getNestedCollectionTree } from "@bitwarden/common/admin-console/utils";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
@@ -50,14 +44,12 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { uuidAsString } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { getByIds } from "@bitwarden/common/platform/misc";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { CipherId, OrganizationId, UserId, CollectionId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -66,7 +58,6 @@ import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { SearchTextDebounceInterval } from "@bitwarden/common/vault/services/search.service";
|
||||
import {
|
||||
CipherViewLike,
|
||||
CipherViewLikeUtils,
|
||||
@@ -114,7 +105,6 @@ import {
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { DesktopHeaderComponent } from "../../../app/layout/header/desktop-header.component";
|
||||
import { SearchBarService } from "../../../app/layout/search/search-bar.service";
|
||||
import { DesktopCredentialGenerationService } from "../../../services/desktop-cipher-form-generator.service";
|
||||
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
|
||||
import { AssignCollectionsDesktopComponent } from "../vault/assign-collections";
|
||||
@@ -183,7 +173,6 @@ export class VaultComponent<C extends CipherViewLike>
|
||||
private eventCollectionService = inject(EventCollectionService);
|
||||
private totpService = inject(TotpService);
|
||||
private passwordRepromptService = inject(PasswordRepromptService);
|
||||
private searchBarService = inject(SearchBarService);
|
||||
private dialogService = inject(DialogService);
|
||||
private billingAccountProfileStateService = inject(BillingAccountProfileStateService);
|
||||
private toastService = inject(ToastService);
|
||||
@@ -202,8 +191,6 @@ export class VaultComponent<C extends CipherViewLike>
|
||||
private routedVaultFilterBridgeService = inject(RoutedVaultFilterBridgeService);
|
||||
private vaultFilterService = inject(VaultFilterService);
|
||||
private routedVaultFilterService = inject(RoutedVaultFilterService);
|
||||
private searchService = inject(SearchService);
|
||||
private searchPipe = inject(SearchPipe);
|
||||
private vaultItemTransferService: VaultItemsTransferService = inject(VaultItemsTransferService);
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
@@ -318,9 +305,7 @@ export class VaultComponent<C extends CipherViewLike>
|
||||
protected filteredCollections: CollectionView[] = [];
|
||||
protected collectionsToDisplay: CollectionView[] = [];
|
||||
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
||||
protected currentSearchText$: Observable<string> = this.route.queryParams.pipe(
|
||||
map((queryParams) => queryParams.search),
|
||||
);
|
||||
protected searchPlaceholderText: string;
|
||||
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
protected ciphers: C[] = [];
|
||||
protected filteredCiphers: C[] = [];
|
||||
@@ -390,8 +375,8 @@ export class VaultComponent<C extends CipherViewLike>
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((activeFilter) => {
|
||||
this.activeFilter = activeFilter;
|
||||
this.searchBarService.setPlaceholderText(
|
||||
this.i18nService.t(this.calculateSearchBarLocalizationString(activeFilter)),
|
||||
this.searchPlaceholderText = this.i18nService.t(
|
||||
this.calculateSearchBarLocalizationString(activeFilter),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -402,24 +387,6 @@ export class VaultComponent<C extends CipherViewLike>
|
||||
map((collections) => getNestedCollectionTree(collections)),
|
||||
);
|
||||
|
||||
// Connect search bar to route query params
|
||||
this.searchBarService.searchText$
|
||||
.pipe(
|
||||
debounceTime(SearchTextDebounceInterval),
|
||||
distinctUntilChanged(),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe((searchText: string) => {
|
||||
void this.router.navigate([], {
|
||||
queryParams: { search: Utils.isNullOrEmpty(searchText) ? null : searchText },
|
||||
queryParamsHandling: "merge",
|
||||
replaceUrl: true,
|
||||
state: {
|
||||
focusMainAfterNav: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const _ciphers = this.cipherService
|
||||
.cipherListViews$(activeUserId)
|
||||
.pipe(filter((c) => c !== null));
|
||||
@@ -441,34 +408,24 @@ export class VaultComponent<C extends CipherViewLike>
|
||||
const ciphers$ = combineLatest([
|
||||
allowedCiphers$,
|
||||
filter$,
|
||||
this.currentSearchText$,
|
||||
this.cipherArchiveService.hasArchiveFlagEnabled$,
|
||||
]).pipe(
|
||||
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
||||
concatMap(async ([ciphers, filter, searchText, showArchiveVault]) => {
|
||||
concatMap(async ([ciphers, filter, showArchiveVault]) => {
|
||||
const failedCiphers =
|
||||
(await firstValueFrom(this.cipherService.failedToDecryptCiphers$(activeUserId))) ?? [];
|
||||
const filterFunction = createFilterFunction(filter, showArchiveVault);
|
||||
// Append any failed to decrypt ciphers to the top of the cipher list
|
||||
const allCiphers = [...failedCiphers, ...ciphers];
|
||||
|
||||
if (await this.searchService.isSearchable(activeUserId, searchText)) {
|
||||
return await this.searchService.searchCiphers<C>(
|
||||
activeUserId,
|
||||
searchText,
|
||||
[filterFunction],
|
||||
allCiphers as C[],
|
||||
);
|
||||
}
|
||||
|
||||
return ciphers.filter(filterFunction) as C[];
|
||||
return allCiphers.filter(filterFunction) as C[];
|
||||
}),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
const collections$ = combineLatest([nestedCollections$, filter$, this.currentSearchText$]).pipe(
|
||||
const collections$ = combineLatest([nestedCollections$, filter$]).pipe(
|
||||
filter(([collections, filter]) => collections != undefined && filter != undefined),
|
||||
concatMap(async ([collections, filter, searchText]) => {
|
||||
map(([collections, filter]) => {
|
||||
if (filter.collectionId === undefined || filter.collectionId === Unassigned) {
|
||||
return [];
|
||||
}
|
||||
@@ -487,19 +444,6 @@ export class VaultComponent<C extends CipherViewLike>
|
||||
searchableCollectionNodes = selectedCollection?.children ?? [];
|
||||
}
|
||||
|
||||
if (await this.searchService.isSearchable(activeUserId, searchText)) {
|
||||
// Flatten the tree for searching through all levels
|
||||
const flatCollectionTree: CollectionView[] =
|
||||
getFlatCollectionTree(searchableCollectionNodes);
|
||||
|
||||
return this.searchPipe.transform(
|
||||
flatCollectionTree,
|
||||
searchText,
|
||||
(collection) => collection.name,
|
||||
(collection) => collection.id,
|
||||
);
|
||||
}
|
||||
|
||||
return searchableCollectionNodes.map((treeNode: TreeNode<CollectionView>) => treeNode.node);
|
||||
}),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
|
||||
Reference in New Issue
Block a user