From 17448fe01059d2724d933ba34e6b3acef8cbd3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 4 Feb 2020 11:28:47 +0100 Subject: [PATCH] MOBILE-3281 search: Add history to search boxes --- .../messages/components/components.module.ts | 4 +- .../messages/pages/search/search.module.ts | 4 +- .../glossary/components/components.module.ts | 4 +- src/app/app.module.ts | 2 + src/app/app.scss | 6 +- src/components/components.module.ts | 3 - src/components/search-box/search-box.ts | 88 -------- .../courses/pages/search/search.module.ts | 4 +- .../search/components/components.module.ts | 43 ++++ .../search-box/core-search-box.html | 12 +- .../components/search-box/search-box.scss | 21 ++ .../components/search-box/search-box.ts | 211 ++++++++++++++++++ src/core/search/providers/search-history.ts | 176 +++++++++++++++ src/core/search/search.module.ts | 30 +++ src/core/tag/pages/search/search.module.ts | 2 + src/core/user/components/components.module.ts | 4 +- 16 files changed, 514 insertions(+), 100 deletions(-) delete mode 100644 src/components/search-box/search-box.ts create mode 100644 src/core/search/components/components.module.ts rename src/{ => core/search}/components/search-box/core-search-box.html (54%) rename src/{ => core/search}/components/search-box/search-box.scss (56%) create mode 100644 src/core/search/components/search-box/search-box.ts create mode 100644 src/core/search/providers/search-history.ts create mode 100644 src/core/search/search.module.ts diff --git a/src/addon/messages/components/components.module.ts b/src/addon/messages/components/components.module.ts index 337c794e3..3e27e38bb 100644 --- a/src/addon/messages/components/components.module.ts +++ b/src/addon/messages/components/components.module.ts @@ -19,6 +19,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; import { AddonMessagesDiscussionsComponent } from '../components/discussions/discussions'; import { AddonMessagesConfirmedContactsComponent } from '../components/confirmed-contacts/confirmed-contacts'; import { AddonMessagesContactRequestsComponent } from '../components/contact-requests/contact-requests'; @@ -37,7 +38,8 @@ import { AddonMessagesContactsComponent } from '../components/contacts/contacts' TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, - CorePipesModule + CorePipesModule, + CoreSearchComponentsModule, ], providers: [ ], diff --git a/src/addon/messages/pages/search/search.module.ts b/src/addon/messages/pages/search/search.module.ts index 9dc8db426..ba6796a0e 100644 --- a/src/addon/messages/pages/search/search.module.ts +++ b/src/addon/messages/pages/search/search.module.ts @@ -19,6 +19,7 @@ import { AddonMessagesSearchPage } from './search'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; import { AddonMessagesComponentsModule } from '../../components/components.module'; @NgModule({ @@ -29,9 +30,10 @@ import { AddonMessagesComponentsModule } from '../../components/components.modul CoreComponentsModule, CoreDirectivesModule, CorePipesModule, + CoreSearchComponentsModule, AddonMessagesComponentsModule, IonicPageModule.forChild(AddonMessagesSearchPage), - TranslateModule.forChild() + TranslateModule.forChild(), ], }) export class AddonMessagesSearchPageModule {} diff --git a/src/addon/mod/glossary/components/components.module.ts b/src/addon/mod/glossary/components/components.module.ts index 6567b7ca8..ca3ffdcce 100644 --- a/src/addon/mod/glossary/components/components.module.ts +++ b/src/addon/mod/glossary/components/components.module.ts @@ -20,6 +20,7 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; import { CoreCourseComponentsModule } from '@core/course/components/components.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; import { AddonModGlossaryIndexComponent } from './index/index'; import { AddonModGlossaryModePickerPopoverComponent } from './mode-picker/mode-picker'; @@ -35,7 +36,8 @@ import { AddonModGlossaryModePickerPopoverComponent } from './mode-picker/mode-p CoreComponentsModule, CoreDirectivesModule, CorePipesModule, - CoreCourseComponentsModule + CoreCourseComponentsModule, + CoreSearchComponentsModule, ], providers: [ ], diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a3592092b..cd4d4e168 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -87,6 +87,7 @@ import { CoreRatingModule } from '@core/rating/rating.module'; import { CoreTagModule } from '@core/tag/tag.module'; import { CoreFilterModule } from '@core/filter/filter.module'; import { CoreH5PModule } from '@core/h5p/h5p.module'; +import { CoreSearchModule } from '@core/search/search.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; @@ -235,6 +236,7 @@ export const WP_PROVIDER: any = null; CoreTagModule, CoreFilterModule, CoreH5PModule, + CoreSearchModule, AddonBadgesModule, AddonBlogModule, AddonCalendarModule, diff --git a/src/app/app.scss b/src/app/app.scss index fbe62a90c..331f197c0 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -112,13 +112,15 @@ ion-app.app-root { @include core-selected-item($core-splitview-selected); } - // Recover borders on items inside cards. + // Recover borders on items inside cards and lists. .card.with-borders .core-as-item, + .list.with-borders .core-as-item, .core-as-item { @include core-as-items(); } - .card.with-borders .item { + .card.with-borders .item, + .list.with-borders .item { @include core-items(); } diff --git a/src/components/components.module.ts b/src/components/components.module.ts index fe663a255..bdd2f6d4c 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -25,7 +25,6 @@ import { CoreSplitViewComponent } from './split-view/split-view'; import { CoreIframeComponent } from './iframe/iframe'; import { CoreProgressBarComponent } from './progress-bar/progress-bar'; import { CoreEmptyBoxComponent } from './empty-box/empty-box'; -import { CoreSearchBoxComponent } from './search-box/search-box'; import { CoreFileComponent } from './file/file'; import { CoreFilesComponent } from './files/files'; import { CoreIconComponent } from './icon/icon'; @@ -66,7 +65,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, - CoreSearchBoxComponent, CoreFileComponent, CoreFilesComponent, CoreIconComponent, @@ -118,7 +116,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, - CoreSearchBoxComponent, CoreFileComponent, CoreFilesComponent, CoreIconComponent, diff --git a/src/components/search-box/search-box.ts b/src/components/search-box/search-box.ts deleted file mode 100644 index 3846e2f77..000000000 --- a/src/components/search-box/search-box.ts +++ /dev/null @@ -1,88 +0,0 @@ -// (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, Input, Output, EventEmitter, OnInit } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { CoreUtilsProvider } from '@providers/utils/utils'; - -/** - * Component to display a "search box". - * - * @description - * This component will display a standalone search box with its search button in order to have a better UX. - * - * Example usage: - * - */ -@Component({ - selector: 'core-search-box', - templateUrl: 'core-search-box.html' -}) -export class CoreSearchBoxComponent implements OnInit { - @Input() searchLabel?: string; // Label to be used on action button. - @Input() placeholder?: string; // Placeholder text for search text input. - @Input() autocorrect = 'on'; // Enables/disable Autocorrection on search text input. - @Input() spellcheck?: string | boolean = true; // Enables/disable Spellchecker on search text input. - @Input() autoFocus?: string | boolean; // Enables/disable Autofocus when entering view. - @Input() lengthCheck = 3; // Check value length before submit. If 0, any string will be submitted. - @Input() showClear = true; // Show/hide clear button. - @Input() disabled = false; // Disables the input text. - @Input() initialSearch: string; // Initial search text. - @Output() onSubmit: EventEmitter; // Send data when submitting the search form. - @Output() onClear: EventEmitter; // Send event when clearing the search form. - - searched = false; - searchText = ''; - - constructor(private translate: TranslateService, private utils: CoreUtilsProvider) { - this.onSubmit = new EventEmitter(); - this.onClear = new EventEmitter(); - } - - ngOnInit(): void { - this.searchLabel = this.searchLabel || this.translate.instant('core.search'); - this.placeholder = this.placeholder || this.translate.instant('core.search'); - this.spellcheck = this.utils.isTrueOrOne(this.spellcheck); - this.showClear = this.utils.isTrueOrOne(this.showClear); - this.searchText = this.initialSearch || ''; - } - - /** - * Form submitted. - * - * @param e Event. - */ - submitForm(e: Event): void { - e.preventDefault(); - e.stopPropagation(); - - if (this.searchText.length < this.lengthCheck) { - // The view should handle this case, but we check it here too just in case. - return; - } - - this.searched = true; - this.onSubmit.emit(this.searchText); - } - - /** - * Form submitted. - */ - clearForm(): void { - this.searched = false; - this.searchText = ''; - this.onClear.emit(); - } -} diff --git a/src/core/courses/pages/search/search.module.ts b/src/core/courses/pages/search/search.module.ts index 57a51cb46..56d61d396 100644 --- a/src/core/courses/pages/search/search.module.ts +++ b/src/core/courses/pages/search/search.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreCoursesSearchPage } from './search'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; import { CoreCoursesComponentsModule } from '../../components/components.module'; @NgModule({ @@ -28,8 +29,9 @@ import { CoreCoursesComponentsModule } from '../../components/components.module' CoreComponentsModule, CoreDirectivesModule, CoreCoursesComponentsModule, + CoreSearchComponentsModule, IonicPageModule.forChild(CoreCoursesSearchPage), - TranslateModule.forChild() + TranslateModule.forChild(), ], }) export class CoreCoursesSearchPageModule {} diff --git a/src/core/search/components/components.module.ts b/src/core/search/components/components.module.ts new file mode 100644 index 000000000..70776e0ff --- /dev/null +++ b/src/core/search/components/components.module.ts @@ -0,0 +1,43 @@ +// (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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreSearchBoxComponent } from './search-box/search-box'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreComponentsModule } from '@components/components.module'; + +@NgModule({ + declarations: [ + CoreSearchBoxComponent, + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreDirectivesModule, + CoreComponentsModule, + ], + providers: [ + ], + exports: [ + CoreSearchBoxComponent, + ], + entryComponents: [ + CoreSearchBoxComponent, + ] +}) +export class CoreSearchComponentsModule {} diff --git a/src/components/search-box/core-search-box.html b/src/core/search/components/search-box/core-search-box.html similarity index 54% rename from src/components/search-box/core-search-box.html rename to src/core/search/components/search-box/core-search-box.html index 84a8e4b74..adc00c50b 100644 --- a/src/components/search-box/core-search-box.html +++ b/src/core/search/components/search-box/core-search-box.html @@ -1,13 +1,21 @@
- + - +
+ + + + {{item.searchedtext}} + + +
diff --git a/src/components/search-box/search-box.scss b/src/core/search/components/search-box/search-box.scss similarity index 56% rename from src/components/search-box/search-box.scss rename to src/core/search/components/search-box/search-box.scss index 1e80c19f2..58eab7287 100644 --- a/src/components/search-box/search-box.scss +++ b/src/core/search/components/search-box/search-box.scss @@ -1,4 +1,12 @@ ion-app.app-root core-search-box { + height: 65px; + display: block; + + ion-card { + position: absolute; + z-index: 4; + } + .button.item-button[icon-only] { margin: 0; padding: ($content-padding / 2) $content-padding; @@ -18,4 +26,17 @@ ion-app.app-root core-search-box { border: 0; margin: 0; } + + .core-search-history { + max-height: calc(-120px + 80vh); + overflow-y: auto; + + .item:hover { + background-color: $gray-lighter; + } + + .list .item.item-block:last-child > .item-inner { + border-bottom: 0; + } + } } diff --git a/src/core/search/components/search-box/search-box.ts b/src/core/search/components/search-box/search-box.ts new file mode 100644 index 000000000..da5223e37 --- /dev/null +++ b/src/core/search/components/search-box/search-box.ts @@ -0,0 +1,211 @@ +// (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, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreSearchHistoryProvider, CoreSearchHistoryItem } from '../../providers/search-history'; + +/** + * Component to display a "search box". + * + * @description + * This component will display a standalone search box with its search button in order to have a better UX. + * + * Example usage: + * + */ +@Component({ + selector: 'core-search-box', + templateUrl: 'core-search-box.html' +}) +export class CoreSearchBoxComponent implements OnInit, OnDestroy { + @Input() searchLabel?: string; // Label to be used on action button. + @Input() placeholder?: string; // Placeholder text for search text input. + @Input() autocorrect = 'on'; // Enables/disable Autocorrection on search text input. + @Input() spellcheck?: string | boolean = true; // Enables/disable Spellchecker on search text input. + @Input() autoFocus?: string | boolean; // Enables/disable Autofocus when entering view. + @Input() lengthCheck = 3; // Check value length before submit. If 0, any string will be submitted. + @Input() showClear = true; // Show/hide clear button. + @Input() disabled = false; // Disables the input text. + @Input() protected initialSearch: string; // Initial search text. + @Input() protected searchArea?: string; // If provided. It will save and display a history of searches for this particular Id. + // To use different history lists, place different Id. + // I.e. AddonMessagesContacts or CoreUserParticipants-6 (using the course Id). + @Output() onSubmit: EventEmitter; // Send data when submitting the search form. + @Output() onClear: EventEmitter; // Send event when clearing the search form. + + searched = ''; // Last search emitted. + searchText = ''; + history: CoreSearchHistoryItem[]; + historyShown = false; + + protected elementClicked = ''; + protected backdropMouseUpFunc; + + constructor(protected translate: TranslateService, + protected utils: CoreUtilsProvider, + protected searchHistoryProvider: CoreSearchHistoryProvider, + ) { + this.onSubmit = new EventEmitter(); + this.onClear = new EventEmitter(); + } + + ngOnInit(): void { + this.searchLabel = this.searchLabel || this.translate.instant('core.search'); + this.placeholder = this.placeholder || this.translate.instant('core.search'); + this.spellcheck = this.utils.isTrueOrOne(this.spellcheck); + this.showClear = this.utils.isTrueOrOne(this.showClear); + this.searchText = this.initialSearch || ''; + + if (this.searchArea) { + this.loadHistory(); + + this.backdropMouseUpFunc = this.backdropMouseUp.bind(this); + } + } + + /** + * Form submitted. + * + * @param e Event. + */ + submitForm(e?: Event): void { + e && e.preventDefault(); + e && e.stopPropagation(); + + if (this.searchText.length < this.lengthCheck) { + // The view should handle this case, but we check it here too just in case. + return; + } + + if (this.searchArea) { + this.saveSearchToHistory(this.searchText); + } + + this.searched = this.searchText; + this.onSubmit.emit(this.searchText); + } + + /** + * Saves the search term onto the history. + * + * @param text Text to save. + * @return Promise resolved when done. + */ + protected async saveSearchToHistory(text: string): Promise { + try { + await this.searchHistoryProvider.insertOrUpdateSearchText(this.searchArea, text); + } finally { + this.loadHistory(); + } + } + + /** + * Loads search history. + * + * @return Promise resolved when done. + */ + protected async loadHistory(): Promise { + this.history = await this.searchHistoryProvider.getSearchHistory(this.searchArea); + } + + /** + * Show search history. + */ + showHistory(): void { + if (this.searchArea) { + this.historyShown = true; + this.elementClicked = ''; + document.body.addEventListener('mouseup', this.backdropMouseUpFunc); + } + } + + /** + * Hide search history. + * + * @param force: If force hidding the history without checking the clicked element. + */ + hideHistory(force: boolean = false): void { + if (this.searchArea && (force || this.elementClicked == '')) { + this.historyShown = false; + this.elementClicked = ''; + + document.body.removeEventListener('mouseup', this.backdropMouseUpFunc); + } + } + + /** + * Saves the item where you moused down. + * It uses mouseup/down because blur will block the click event. + * + * @param e Event. + * @param text Selected text. + */ + itemMouseDown(e: Event, text: string): void { + this.elementClicked = text; + } + + /** + * Select an item and use it for search text. + * It uses mouseup/down because blur will block the click event. + * + * @param e Event. + * @param text Selected text. + */ + itemMouseUp(e: Event, text: string): void { + if (this.elementClicked == text) { + this.hideHistory(true); + if (this.searched != text) { + this.searchText = text; + this.submitForm(e); + } + } + this.elementClicked = ''; + } + + /** + * Manages mouseup out of the history. + * + * @param e Event. + */ + backdropMouseUp(e: Event): void { + // Do not hide if the search box is focused. + if (document.activeElement['type'] && document.activeElement['type'] == 'search') { + return; + } + this.hideHistory(); + this.elementClicked = ''; + } + + /** + * Form submitted. + */ + clearForm(): void { + this.searched = ''; + this.searchText = ''; + this.hideHistory(true); + this.onClear.emit(); + } + + /** + * On destroy of the component, clear up any listeners. + */ + ngOnDestroy(): void { + if (this.backdropMouseUpFunc) { + document.body.removeEventListener('mouseup', this.backdropMouseUpFunc); + } + } +} diff --git a/src/core/search/providers/search-history.ts b/src/core/search/providers/search-history.ts new file mode 100644 index 000000000..b70e91cb7 --- /dev/null +++ b/src/core/search/providers/search-history.ts @@ -0,0 +1,176 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; +import { SQLiteDB } from '@classes/sqlitedb'; + +/** + * Search history item definition. + */ +export interface CoreSearchHistoryItem { + searcharea: string; // Search area where the search has been performed. + lastused?: number; // Timestamp of the last search. + searchedtext: string; // Text of the performed search. + times?: number; // Times search has been performed (if previously in history). +} + +/** + * Service that enables adding a history to a search box. + */ +@Injectable() +export class CoreSearchHistoryProvider { + + protected static HISTORY_TABLE = 'seach_history'; + protected static HISTORY_LIMIT = 10; + + protected siteSchema: CoreSiteSchema = { + name: 'CoreSearchHistoryProvider', + version: 1, + tables: [ + { + name: CoreSearchHistoryProvider.HISTORY_TABLE, + columns: [ + { + name: 'searcharea', + type: 'TEXT', + notNull: true, + }, + { + name: 'lastused', + type: 'INTEGER', + notNull: true, + }, + { + name: 'times', + type: 'INTEGER', + notNull: true, + }, + { + name: 'searchedtext', + type: 'TEXT', + notNull: true, + }, + ], + primaryKeys: ['searcharea', 'searchedtext'], + }, + ], + }; + + constructor(protected sitesProvider: CoreSitesProvider) { + + this.sitesProvider.registerSiteSchema(this.siteSchema); + } + + /** + * Get a search area history sorted by use. + * + * @param searchArea Search Area Name. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of items when done. + */ + async getSearchHistory(searchArea: string, siteId?: string): Promise { + const site = await this.sitesProvider.getSite(siteId); + const conditions: any = { + searcharea: searchArea, + }; + + const history = await site.getDb().getRecords(CoreSearchHistoryProvider.HISTORY_TABLE, conditions); + + // Sorting by last used DESC. + return history.sort((a, b) => { + return b.lastused - a.lastused; + }); + } + + /** + * Controls search limit and removes the last item if overflows. + * + * @param searchArea Search area to control + * @param db SQLite DB where to perform the search. + * @return Resolved when done. + */ + protected async controlSearchLimit(searchArea: string, db: SQLiteDB): Promise { + const items = await this.getSearchHistory(searchArea); + if (items.length > CoreSearchHistoryProvider.HISTORY_LIMIT) { + // Over the limit. Remove the last. + const lastItem = items.pop(); + + const searchItem: CoreSearchHistoryItem = { + searcharea: lastItem.searcharea, + searchedtext: lastItem.searchedtext, + }; + + await db.deleteRecords(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); + } + } + + /** + * Updates the search history item if exists. + * + * @param searchArea Area where the search has been performed. + * @param text Text of the performed text. + * @param db SQLite DB where to perform the search. + * @return True if exists, false otherwise. + */ + protected async updateExistingItem(searchArea: string, text: string, db: SQLiteDB): Promise { + const searchItem: CoreSearchHistoryItem = { + searcharea: searchArea, + searchedtext: text, + }; + + try { + const existingItem = await db.getRecord(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); + + // If item exist, update time and number of times searched. + existingItem.lastused = Date.now(); + existingItem.times++; + + await db.updateRecords(CoreSearchHistoryProvider.HISTORY_TABLE, existingItem, searchItem); + + return true; + } catch (e) { + return false; + } + } + + /** + * Inserts a searched term on the history. + * + * @param searchArea Area where the search has been performed. + * @param text Text of the performed text. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. + */ + async insertOrUpdateSearchText(searchArea: string, text: string, siteId?: string): Promise { + const site = await this.sitesProvider.getSite(siteId); + const db = site.getDb(); + + const exists = await this.updateExistingItem(searchArea, text, db); + + if (!exists) { + // If item is new, control the history does not goes over the limit. + const searchItem: CoreSearchHistoryItem = { + searcharea: searchArea, + searchedtext: text, + lastused: Date.now(), + times: 1, + }; + + await site.getDb().insertRecord(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); + + await this.controlSearchLimit(searchArea, db); + } + } +} diff --git a/src/core/search/search.module.ts b/src/core/search/search.module.ts new file mode 100644 index 000000000..4026fb898 --- /dev/null +++ b/src/core/search/search.module.ts @@ -0,0 +1,30 @@ +// (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 { NgModule } from '@angular/core'; +import { CoreSearchComponentsModule } from './components/components.module'; +import { CoreSearchHistoryProvider } from './providers/search-history'; + +@NgModule({ + declarations: [ + ], + imports: [ + CoreSearchComponentsModule, + ], + providers: [ + CoreSearchComponentsModule, + CoreSearchHistoryProvider, + ], +}) +export class CoreSearchModule {} diff --git a/src/core/tag/pages/search/search.module.ts b/src/core/tag/pages/search/search.module.ts index 084c9605c..f199aa6c6 100644 --- a/src/core/tag/pages/search/search.module.ts +++ b/src/core/tag/pages/search/search.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreTagSearchPage } from './search'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; @NgModule({ declarations: [ @@ -26,6 +27,7 @@ import { CoreDirectivesModule } from '@directives/directives.module'; imports: [ CoreComponentsModule, CoreDirectivesModule, + CoreSearchComponentsModule, IonicPageModule.forChild(CoreTagSearchPage), TranslateModule.forChild() ], diff --git a/src/core/user/components/components.module.ts b/src/core/user/components/components.module.ts index a2f772ed8..8c3029a51 100644 --- a/src/core/user/components/components.module.ts +++ b/src/core/user/components/components.module.ts @@ -22,6 +22,7 @@ import { CoreUserTagAreaComponent } from './tag-area/tag-area'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; @NgModule({ declarations: [ @@ -35,7 +36,8 @@ import { CorePipesModule } from '@pipes/pipes.module'; TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, - CorePipesModule + CorePipesModule, + CoreSearchComponentsModule, ], providers: [ ],