-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MOBILE-3371 search: Implement global search
- Loading branch information
1 parent
eaeb6db
commit 5e08921
Showing
13 changed files
with
860 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
145
src/core/features/search/classes/global-search-results-source.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// (C) Copyright 2015 Moodle Pty Ltd. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import { | ||
CoreSearchGlobalSearchResult, | ||
CoreSearchGlobalSearch, | ||
CORE_SEARCH_GLOBAL_SEARCH_PAGE_LENGTH, | ||
CoreSearchGlobalSearchFilters, | ||
} from '@features/search/services/global-search'; | ||
import { CorePaginatedItemsManagerSource } from '@classes/items-management/paginated-items-manager-source'; | ||
|
||
/** | ||
* Provides a collection of global search results. | ||
*/ | ||
export class CoreSearchGlobalSearchResultsSource extends CorePaginatedItemsManagerSource<CoreSearchGlobalSearchResult> { | ||
|
||
private query: string; | ||
private filters: CoreSearchGlobalSearchFilters; | ||
private pagesLoaded = 0; | ||
private topResultsIds?: number[]; | ||
|
||
constructor(query: string, filters: CoreSearchGlobalSearchFilters) { | ||
super(); | ||
|
||
this.query = query; | ||
this.filters = filters; | ||
} | ||
|
||
/** | ||
* Check whether the source has an empty query. | ||
* | ||
* @returns Whether the source has an empty query. | ||
*/ | ||
hasEmptyQuery(): boolean { | ||
return !this.query || this.query.trim().length === 0; | ||
} | ||
|
||
/** | ||
* Get search query. | ||
* | ||
* @returns Search query. | ||
*/ | ||
getQuery(): string { | ||
return this.query; | ||
} | ||
|
||
/** | ||
* Get search filters. | ||
* | ||
* @returns Search filters. | ||
*/ | ||
getFilters(): CoreSearchGlobalSearchFilters { | ||
return this.filters; | ||
} | ||
|
||
/** | ||
* Set search query. | ||
* | ||
* @param query Search query. | ||
*/ | ||
setQuery(query: string): void { | ||
this.query = query; | ||
|
||
this.setDirty(true); | ||
} | ||
|
||
/** | ||
* Set search filters. | ||
* | ||
* @param filters Search filters. | ||
*/ | ||
setFilters(filters: CoreSearchGlobalSearchFilters): void { | ||
this.filters = filters; | ||
|
||
this.setDirty(true); | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
getPagesLoaded(): number { | ||
return this.pagesLoaded; | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
async reload(): Promise<void> { | ||
this.pagesLoaded = 0; | ||
|
||
await super.reload(); | ||
} | ||
|
||
/** | ||
* Reset collection data. | ||
*/ | ||
reset(): void { | ||
this.pagesLoaded = 0; | ||
delete this.topResultsIds; | ||
|
||
super.reset(); | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
protected async loadPageItems(page: number): Promise<{ items: CoreSearchGlobalSearchResult[]; hasMoreItems: boolean }> { | ||
this.pagesLoaded++; | ||
|
||
const results: CoreSearchGlobalSearchResult[] = []; | ||
|
||
if (page === 0) { | ||
const topResults = await CoreSearchGlobalSearch.getTopResults(this.query, this.filters); | ||
|
||
results.push(...topResults); | ||
|
||
this.topResultsIds = topResults.map(result => result.id); | ||
} | ||
|
||
const pageResults = await CoreSearchGlobalSearch.getResults(this.query, this.filters, page); | ||
|
||
results.push(...pageResults.results.filter(result => !this.topResultsIds?.includes(result.id))); | ||
|
||
return { items: results, hasMoreItems: pageResults.canLoadMore }; | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
protected getPageLength(): number { | ||
return CORE_SEARCH_GLOBAL_SEARCH_PAGE_LENGTH; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
{ | ||
"empty": "What are you searching for?", | ||
"globalsearch": "Global search", | ||
"noresults": "No results for \"{{$a}}\"", | ||
"noresultshelp": "Check for typos or try using different keywords", | ||
"resultby": "By {{$a}}" | ||
} |
46 changes: 46 additions & 0 deletions
46
src/core/features/search/pages/global-search/global-search.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<ion-header> | ||
<ion-toolbar> | ||
<ion-buttons slot="start"> | ||
<ion-back-button [text]="'core.back' | translate"></ion-back-button> | ||
</ion-buttons> | ||
<ion-title> | ||
<h1>{{ 'core.search.globalsearch' | translate }}</h1> | ||
</ion-title> | ||
<ion-buttons slot="end"> | ||
<core-user-menu-button></core-user-menu-button> | ||
</ion-buttons> | ||
</ion-toolbar> | ||
</ion-header> | ||
<ion-content class="limited-width"> | ||
<div> | ||
<ion-card class="core-danger-card" *ngIf="searchBanner"> | ||
<ion-item> | ||
<ion-icon name="fas-triangle-exclamation" slot="start" aria-hidden="true"></ion-icon> | ||
<ion-label> | ||
<core-format-text [text]="searchBanner"></core-format-text> | ||
</ion-label> | ||
</ion-item> | ||
</ion-card> | ||
|
||
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch()" [placeholder]="'core.courses.search' | translate" | ||
[searchLabel]="'core.search' | translate" [autoFocus]="true" searchArea="CoreSearchGlobalSearch"></core-search-box> | ||
|
||
<ion-list *ngIf="this.resultsSource.isLoaded()"> | ||
<core-search-global-search-result *ngFor="let result of this.resultsSource.getItems()" [result]="result" | ||
(onClick)="visitResult(result)"> | ||
</core-search-global-search-result> | ||
</ion-list> | ||
|
||
<core-infinite-loading [enabled]="resultsSource.isLoaded() && !resultsSource.isCompleted()" (action)="loadMoreResults($event)" | ||
[error]="loadMoreError"> | ||
</core-infinite-loading> | ||
|
||
<core-empty-box *ngIf="this.resultsSource.isEmpty()" icon="fas-magnifying-glass" [dimmed]="!this.resultsSource.isLoaded()"> | ||
<p *ngIf="!this.resultsSource.isLoaded()">{{ 'core.search.empty' | translate }}</p> | ||
<ng-container *ngIf="this.resultsSource.isLoaded()"> | ||
<p><strong>{{ 'core.search.noresults' | translate: { $a: this.resultsSource.getQuery() } }}</strong></p> | ||
<p><small>{{ 'core.search.noresultshelp' | translate }}</small></p> | ||
</ng-container> | ||
</core-empty-box> | ||
</div> | ||
</ion-content> |
96 changes: 96 additions & 0 deletions
96
src/core/features/search/pages/global-search/global-search.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// (C) Copyright 2015 Moodle Pty Ltd. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import { Component, OnInit } from '@angular/core'; | ||
import { CoreDomUtils } from '@services/utils/dom'; | ||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; | ||
import { CoreSearchGlobalSearchResultsSource } from '@features/search/classes/global-search-results-source'; | ||
import { CoreSites } from '@services/sites'; | ||
import { CoreUtils } from '@services/utils/utils'; | ||
import { CoreSearchGlobalSearchResult, CoreSearchGlobalSearch } from '@features/search/services/global-search'; | ||
|
||
@Component({ | ||
selector: 'page-core-search-global-search', | ||
templateUrl: 'global-search.html', | ||
}) | ||
export class CoreSearchGlobalSearchPage implements OnInit { | ||
|
||
loadMoreError: string | null = null; | ||
searchBanner: string | null = null; | ||
resultsSource = new CoreSearchGlobalSearchResultsSource('', {}); | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
ngOnInit(): void { | ||
const site = CoreSites.getRequiredCurrentSite(); | ||
const searchBanner = site.config?.searchbanner?.trim() ?? ''; | ||
|
||
if (CoreUtils.isTrueOrOne(site.config?.searchbannerenable) && searchBanner.length > 0) { | ||
this.searchBanner = searchBanner; | ||
} | ||
} | ||
|
||
/** | ||
* Perform a new search. | ||
* | ||
* @param query Search query. | ||
*/ | ||
async search(query: string): Promise<void> { | ||
this.resultsSource.setQuery(query); | ||
|
||
if (this.resultsSource.hasEmptyQuery()) { | ||
return; | ||
} | ||
|
||
await CoreDomUtils.showOperationModals('core.searching', true, async () => { | ||
await this.resultsSource.reload(); | ||
await CoreUtils.ignoreErrors( | ||
CoreSearchGlobalSearch.logViewResults(this.resultsSource.getQuery(), this.resultsSource.getFilters()), | ||
); | ||
}); | ||
} | ||
|
||
/** | ||
* Clear search results. | ||
*/ | ||
clearSearch(): void { | ||
this.loadMoreError = null; | ||
} | ||
|
||
/** | ||
* Visit a result's origin. | ||
* | ||
* @param result Result to visit. | ||
*/ | ||
async visitResult(result: CoreSearchGlobalSearchResult): Promise<void> { | ||
await CoreContentLinksHelper.handleLink(result.url); | ||
} | ||
|
||
/** | ||
* Load more results. | ||
* | ||
* @param complete Notify completion. | ||
*/ | ||
async loadMoreResults(complete: () => void ): Promise<void> { | ||
try { | ||
await this.resultsSource?.load(); | ||
} catch (error) { | ||
this.loadMoreError = CoreDomUtils.getErrorMessage(error); | ||
} finally { | ||
complete(); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// (C) Copyright 2015 Moodle Pty Ltd. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import { CoreSharedModule } from '@/core/shared.module'; | ||
import { NgModule, Injector } from '@angular/core'; | ||
import { RouterModule, Routes, ROUTES } from '@angular/router'; | ||
import { CoreSearchGlobalSearchPage } from './pages/global-search/global-search'; | ||
import { CoreSearchComponentsModule } from '@features/search/components/components.module'; | ||
import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module'; | ||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module'; | ||
import { CoreSearchGlobalSearchResultComponent } from '@features/search/components/global-search-result/global-search-result'; | ||
|
||
/** | ||
* Build module routes. | ||
* | ||
* @param injector Injector. | ||
* @returns Routes. | ||
*/ | ||
function buildRoutes(injector: Injector): Routes { | ||
return buildTabMainRoutes(injector, { | ||
component: CoreSearchGlobalSearchPage, | ||
}); | ||
} | ||
|
||
@NgModule({ | ||
imports: [ | ||
CoreSharedModule, | ||
CoreSearchComponentsModule, | ||
CoreMainMenuComponentsModule, | ||
], | ||
exports: [RouterModule], | ||
declarations: [ | ||
CoreSearchGlobalSearchPage, | ||
CoreSearchGlobalSearchResultComponent, | ||
], | ||
providers: [ | ||
{ | ||
provide: ROUTES, | ||
multi: true, | ||
deps: [Injector], | ||
useFactory: buildRoutes, | ||
}, | ||
], | ||
}) | ||
export class CoreSearchLazyModule {} |
Oops, something went wrong.