commit
566db5205e
|
@ -66,6 +66,7 @@
|
||||||
"addon.block_recentlyaccesseditems.noitems": "block_recentlyaccesseditems",
|
"addon.block_recentlyaccesseditems.noitems": "block_recentlyaccesseditems",
|
||||||
"addon.block_recentlyaccesseditems.pluginname": "block_recentlyaccesseditems",
|
"addon.block_recentlyaccesseditems.pluginname": "block_recentlyaccesseditems",
|
||||||
"addon.block_rssclient.pluginname": "block_rss_client",
|
"addon.block_rssclient.pluginname": "block_rss_client",
|
||||||
|
"addon.block_searchforums.pluginname": "block_search_forums",
|
||||||
"addon.block_selfcompletion.pluginname": "block_selfcompletion",
|
"addon.block_selfcompletion.pluginname": "block_selfcompletion",
|
||||||
"addon.block_sitemainmenu.pluginname": "block_site_main_menu",
|
"addon.block_sitemainmenu.pluginname": "block_site_main_menu",
|
||||||
"addon.block_starredcourses.nocourses": "block_starredcourses",
|
"addon.block_starredcourses.nocourses": "block_starredcourses",
|
||||||
|
@ -684,6 +685,7 @@
|
||||||
"addon.mod_forum.removefromfavourites": "forum",
|
"addon.mod_forum.removefromfavourites": "forum",
|
||||||
"addon.mod_forum.reply": "forum",
|
"addon.mod_forum.reply": "forum",
|
||||||
"addon.mod_forum.replyplaceholder": "forum",
|
"addon.mod_forum.replyplaceholder": "forum",
|
||||||
|
"addon.mod_forum.searchresults": "course",
|
||||||
"addon.mod_forum.subject": "forum",
|
"addon.mod_forum.subject": "forum",
|
||||||
"addon.mod_forum.tagarea_forum_posts": "forum",
|
"addon.mod_forum.tagarea_forum_posts": "forum",
|
||||||
"addon.mod_forum.thisforumhasduedate": "forum",
|
"addon.mod_forum.thisforumhasduedate": "forum",
|
||||||
|
|
|
@ -42,6 +42,7 @@ import { AddonBlockStarredCoursesModule } from './starredcourses/starredcourses.
|
||||||
import { AddonBlockTagsModule } from './tags/tags.module';
|
import { AddonBlockTagsModule } from './tags/tags.module';
|
||||||
import { AddonBlockTimelineModule } from './timeline/timeline.module';
|
import { AddonBlockTimelineModule } from './timeline/timeline.module';
|
||||||
import { AddonBlockGlobalSearchModule } from '@addons/block/globalsearch/globalsearch.module';
|
import { AddonBlockGlobalSearchModule } from '@addons/block/globalsearch/globalsearch.module';
|
||||||
|
import { AddonBlockSearchForumsModule } from '@addons/block/searchforums/searchforums.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -68,6 +69,7 @@ import { AddonBlockGlobalSearchModule } from '@addons/block/globalsearch/globals
|
||||||
AddonBlockRecentlyAccessedCoursesModule,
|
AddonBlockRecentlyAccessedCoursesModule,
|
||||||
AddonBlockRecentlyAccessedItemsModule,
|
AddonBlockRecentlyAccessedItemsModule,
|
||||||
AddonBlockRssClientModule,
|
AddonBlockRssClientModule,
|
||||||
|
AddonBlockSearchForumsModule,
|
||||||
AddonBlockSelfCompletionModule,
|
AddonBlockSelfCompletionModule,
|
||||||
AddonBlockSiteMainMenuModule,
|
AddonBlockSiteMainMenuModule,
|
||||||
AddonBlockStarredCoursesModule,
|
AddonBlockStarredCoursesModule,
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"pluginname": "Search forums"
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreBlockDelegate } from '@features/block/services/block-delegate';
|
||||||
|
import { AddonBlockSearchForumsHandler } from './services/block-handler';
|
||||||
|
import { CoreBlockComponentsModule } from '@features/block/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CoreBlockComponentsModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
multi: true,
|
||||||
|
useValue: () => {
|
||||||
|
CoreBlockDelegate.registerHandler(AddonBlockSearchForumsHandler.instance);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonBlockSearchForumsModule {}
|
|
@ -0,0 +1,68 @@
|
||||||
|
// (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 { CoreBlockHandlerData } from '@features/block/services/block-delegate';
|
||||||
|
import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block';
|
||||||
|
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { FORUM_SEARCH_PAGE_NAME } from '@addons/mod/forum/forum.module';
|
||||||
|
import { CoreCourseBlock } from '@features/course/services/course';
|
||||||
|
import { CoreSearchGlobalSearch } from '@features/search/services/global-search';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block handler.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonBlockSearchForumsHandlerService extends CoreBlockBaseHandler {
|
||||||
|
|
||||||
|
name = 'AddonBlockSearchForums';
|
||||||
|
blockName = 'search_forums';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
const enabled = await CoreSearchGlobalSearch.isEnabled();
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const forumSearchAreas = ['mod_forum-activity', 'mod_forum-post'];
|
||||||
|
const searchAreas = await CoreSearchGlobalSearch.getSearchAreas();
|
||||||
|
|
||||||
|
return searchAreas.some(({ id }) => forumSearchAreas.includes(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData | undefined {
|
||||||
|
if (contextLevel !== 'course') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'addon.block_searchforums.pluginname',
|
||||||
|
class: 'addon-block-search-forums',
|
||||||
|
component: CoreBlockOnlyTitleComponent,
|
||||||
|
link: FORUM_SEARCH_PAGE_NAME,
|
||||||
|
linkParams: { courseId: instanceId },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddonBlockSearchForumsHandler = makeSingleton(AddonBlockSearchForumsHandlerService);
|
|
@ -1,5 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
|
<ion-button fill="clear" (click)="openSearch()" [attr.aria-label]="'core.search' | translate">
|
||||||
|
<ion-icon name="fas-magnifying-glass" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<ion-icon name="fas-circle-info" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-circle-info" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
|
@ -57,6 +57,8 @@ import { AddonModForumDiscussionItem, AddonModForumDiscussionsSource } from '../
|
||||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
import { CorePromisedValue } from '@classes/promised-value';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { FORUM_SEARCH_PAGE_NAME } from '@addons/mod/forum/forum.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays a forum entry page.
|
* Component that displays a forum entry page.
|
||||||
|
@ -332,6 +334,22 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
this.discussions?.destroy();
|
this.discussions?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open search page.
|
||||||
|
*/
|
||||||
|
async openSearch(): Promise<void> {
|
||||||
|
if (!this.forum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CoreNavigator.navigateToSitePath(FORUM_SEARCH_PAGE_NAME, {
|
||||||
|
params: {
|
||||||
|
courseId: this.courseId,
|
||||||
|
forumId: this.forum.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -36,4 +36,4 @@ const routes: Routes = [{
|
||||||
AddonModForumDiscussionPage,
|
AddonModForumDiscussionPage,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AddonForumDiscussionLazyModule {}
|
export class AddonModForumDiscussionLazyModule {}
|
||||||
|
|
|
@ -29,15 +29,15 @@ const mobileRoutes: Routes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':courseId/:cmId/new/:timeCreated',
|
path: ':courseId/:cmId/new/:timeCreated',
|
||||||
loadChildren: () => import('./forum-new-discussion-lazy.module').then(m => m.AddonForumNewDiscussionLazyModule),
|
loadChildren: () => import('./forum-new-discussion-lazy.module').then(m => m.AddonModForumNewDiscussionLazyModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':courseId/:cmId/:discussionId',
|
path: ':courseId/:cmId/:discussionId',
|
||||||
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonForumDiscussionLazyModule),
|
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonModForumDiscussionLazyModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'discussion/:discussionId', // Only for discussion link handling.
|
path: 'discussion/:discussionId', // Only for discussion link handling.
|
||||||
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonForumDiscussionLazyModule),
|
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonModForumDiscussionLazyModule),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -48,11 +48,11 @@ const tabletRoutes: Routes = [
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'new/:timeCreated',
|
path: 'new/:timeCreated',
|
||||||
loadChildren: () => import('./forum-new-discussion-lazy.module').then(m => m.AddonForumNewDiscussionLazyModule),
|
loadChildren: () => import('./forum-new-discussion-lazy.module').then(m => m.AddonModForumNewDiscussionLazyModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':discussionId',
|
path: ':discussionId',
|
||||||
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonForumDiscussionLazyModule),
|
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonModForumDiscussionLazyModule),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,4 +38,4 @@ const routes: Routes = [{
|
||||||
AddonModForumNewDiscussionPage,
|
AddonModForumNewDiscussionPage,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AddonForumNewDiscussionLazyModule {}
|
export class AddonModForumNewDiscussionLazyModule {}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
// (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 { AddonModForumSearchPage } from '@addons/mod/forum/pages/search/search';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
|
||||||
|
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||||
|
|
||||||
|
const routes: Routes = [{
|
||||||
|
path: '',
|
||||||
|
component: AddonModForumSearchPage,
|
||||||
|
}];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CoreSharedModule,
|
||||||
|
CoreSearchComponentsModule,
|
||||||
|
CoreMainMenuComponentsModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonModForumSearchPage,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonModForumSearchLazyModule {}
|
|
@ -52,10 +52,16 @@ export const ADDON_MOD_FORUM_SERVICES: Type<unknown>[] = [
|
||||||
AddonModForumSyncProvider,
|
AddonModForumSyncProvider,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const FORUM_SEARCH_PAGE_NAME = 'forum/search';
|
||||||
|
|
||||||
const mainMenuRoutes: Routes = [
|
const mainMenuRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: FORUM_SEARCH_PAGE_NAME,
|
||||||
|
loadChildren: () => import('./forum-search-lazy.module').then(m => m.AddonModForumSearchLazyModule),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `${AddonModForumModuleHandlerService.PAGE_NAME}/discussion/:discussionId`,
|
path: `${AddonModForumModuleHandlerService.PAGE_NAME}/discussion/:discussionId`,
|
||||||
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonForumDiscussionLazyModule),
|
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonModForumDiscussionLazyModule),
|
||||||
data: { swipeEnabled: false },
|
data: { swipeEnabled: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -66,12 +72,12 @@ const mainMenuRoutes: Routes = [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
path: `${COURSE_CONTENTS_PATH}/${AddonModForumModuleHandlerService.PAGE_NAME}/new/:timeCreated`,
|
path: `${COURSE_CONTENTS_PATH}/${AddonModForumModuleHandlerService.PAGE_NAME}/new/:timeCreated`,
|
||||||
loadChildren: () => import('./forum-new-discussion-lazy.module').then(m => m.AddonForumNewDiscussionLazyModule),
|
loadChildren: () => import('./forum-new-discussion-lazy.module').then(m => m.AddonModForumNewDiscussionLazyModule),
|
||||||
data: { discussionsPathPrefix: `${AddonModForumModuleHandlerService.PAGE_NAME}/` },
|
data: { discussionsPathPrefix: `${AddonModForumModuleHandlerService.PAGE_NAME}/` },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${COURSE_CONTENTS_PATH}/${AddonModForumModuleHandlerService.PAGE_NAME}/:discussionId`,
|
path: `${COURSE_CONTENTS_PATH}/${AddonModForumModuleHandlerService.PAGE_NAME}/:discussionId`,
|
||||||
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonForumDiscussionLazyModule),
|
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonModForumDiscussionLazyModule),
|
||||||
data: { discussionsPathPrefix: `${AddonModForumModuleHandlerService.PAGE_NAME}/` },
|
data: { discussionsPathPrefix: `${AddonModForumModuleHandlerService.PAGE_NAME}/` },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -83,12 +89,12 @@ const courseContentsRoutes: Routes = conditionalRoutes(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
path: `${AddonModForumModuleHandlerService.PAGE_NAME}/new/:timeCreated`,
|
path: `${AddonModForumModuleHandlerService.PAGE_NAME}/new/:timeCreated`,
|
||||||
loadChildren: () => import('./forum-new-discussion-lazy.module').then(m => m.AddonForumNewDiscussionLazyModule),
|
loadChildren: () => import('./forum-new-discussion-lazy.module').then(m => m.AddonModForumNewDiscussionLazyModule),
|
||||||
data: { discussionsPathPrefix: `${AddonModForumModuleHandlerService.PAGE_NAME}/` },
|
data: { discussionsPathPrefix: `${AddonModForumModuleHandlerService.PAGE_NAME}/` },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${AddonModForumModuleHandlerService.PAGE_NAME}/:discussionId`,
|
path: `${AddonModForumModuleHandlerService.PAGE_NAME}/:discussionId`,
|
||||||
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonForumDiscussionLazyModule),
|
loadChildren: () => import('./forum-discussion-lazy.module').then(m => m.AddonModForumDiscussionLazyModule),
|
||||||
data: { discussionsPathPrefix: `${AddonModForumModuleHandlerService.PAGE_NAME}/` },
|
data: { discussionsPathPrefix: `${AddonModForumModuleHandlerService.PAGE_NAME}/` },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
"removefromfavourites": "Unstar this discussion",
|
"removefromfavourites": "Unstar this discussion",
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
"replyplaceholder": "Write your reply...",
|
"replyplaceholder": "Write your reply...",
|
||||||
|
"searchresults": "Search results: {{$a}}",
|
||||||
"subject": "Subject",
|
"subject": "Subject",
|
||||||
"tagarea_forum_posts": "Forum posts",
|
"tagarea_forum_posts": "Forum posts",
|
||||||
"thisforumhasduedate": "The due date for posting to this forum is {{$a}}.",
|
"thisforumhasduedate": "The due date for posting to this forum is {{$a}}.",
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
<h1 *ngIf="forum">{{ forum.name }}</h1>
|
||||||
|
<h1 *ngIf="!forum">{{ 'addon.block_searchforums.pluginname' | translate }}</h1>
|
||||||
|
</ion-title>
|
||||||
|
</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.search' | translate"
|
||||||
|
[searchLabel]="'core.search' | translate" [autoFocus]="true" [searchArea]="searchAreaId"></core-search-box>
|
||||||
|
|
||||||
|
<div *ngIf="!resultsSource.isEmpty()" class="results-count">
|
||||||
|
{{ 'addon.mod_forum.searchresults' | translate: { $a: resultsSource.getTotalResults() } }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-list *ngIf="resultsSource.isLoaded()">
|
||||||
|
<core-search-global-search-result *ngFor="let result of resultsSource.getItems()" [result]="result" [showCourse]="false"
|
||||||
|
(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="resultsSource.isEmpty()" icon="fas-magnifying-glass" [dimmed]="!resultsSource.isLoaded()">
|
||||||
|
<p *ngIf="!resultsSource.isLoaded()">{{ 'core.search.empty' | translate }}</p>
|
||||||
|
<ng-container *ngIf="resultsSource.isLoaded()">
|
||||||
|
<p><strong>{{ 'core.search.noresults' | translate: { $a: resultsSource.getQuery() } }}</strong></p>
|
||||||
|
<p><small>{{ 'core.search.noresultshelp' | translate }}</small></p>
|
||||||
|
</ng-container>
|
||||||
|
</core-empty-box>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,15 @@
|
||||||
|
:host {
|
||||||
|
--results-count-text-color: var(--gray-700);
|
||||||
|
|
||||||
|
.results-count {
|
||||||
|
color: var(--results-count-text-color);
|
||||||
|
min-height: 0px;
|
||||||
|
margin: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(html.dark) {
|
||||||
|
--results-count-text-color: var(--gray-400);
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
// (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 { AddonModForum, AddonModForumData } from '@addons/mod/forum/services/forum';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { CorePromisedValue } from '@classes/promised-value';
|
||||||
|
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreSearchGlobalSearchResultsSource } from '@features/search/classes/global-search-results-source';
|
||||||
|
import {
|
||||||
|
CoreSearchGlobalSearch,
|
||||||
|
CoreSearchGlobalSearchFilters,
|
||||||
|
CoreSearchGlobalSearchResult,
|
||||||
|
} from '@features/search/services/global-search';
|
||||||
|
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreUrlUtils } from '@services/utils/url';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { Translate } from '@singletons';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-mod-forum-search',
|
||||||
|
templateUrl: 'search.html',
|
||||||
|
styleUrls: ['search.scss'],
|
||||||
|
})
|
||||||
|
export class AddonModForumSearchPage implements OnInit {
|
||||||
|
|
||||||
|
loadMoreError: string | null = null;
|
||||||
|
searchBanner: string | null = null;
|
||||||
|
resultsSource = new CoreSearchGlobalSearchResultsSource('', {});
|
||||||
|
forum?: AddonModForumData;
|
||||||
|
searchAreaId?: string;
|
||||||
|
|
||||||
|
private ready = new CorePromisedValue<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const site = CoreSites.getRequiredCurrentSite();
|
||||||
|
const searchBanner = site.config?.searchbanner?.trim() ?? '';
|
||||||
|
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||||
|
const forumId = CoreNavigator.getRouteNumberParam('forumId');
|
||||||
|
const filters: CoreSearchGlobalSearchFilters = {
|
||||||
|
courseIds: [courseId],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (CoreUtils.isTrueOrOne(site.config?.searchbannerenable) && searchBanner.length > 0) {
|
||||||
|
this.searchBanner = searchBanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forumId) {
|
||||||
|
this.forum = await AddonModForum.getForumById(courseId, forumId);
|
||||||
|
const module = await CoreCourse.getModule(this.forum.cmid, courseId);
|
||||||
|
|
||||||
|
filters.searchAreaIds = ['mod_forum-post'];
|
||||||
|
|
||||||
|
if (module.contextid) {
|
||||||
|
filters.contextIds = [module.contextid];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.searchAreaId = `AddonModForumSearch-${courseId}-${this.forum.id}`;
|
||||||
|
} else {
|
||||||
|
filters.searchAreaIds = ['mod_forum-activity', 'mod_forum-post'];
|
||||||
|
this.searchAreaId = `AddonModForumSearch-${courseId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resultsSource.setFilters(filters);
|
||||||
|
this.ready.resolve();
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
CoreNavigator.back();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a new search.
|
||||||
|
*
|
||||||
|
* @param query Search query.
|
||||||
|
*/
|
||||||
|
async search(query: string): Promise<void> {
|
||||||
|
await this.ready;
|
||||||
|
|
||||||
|
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()),
|
||||||
|
);
|
||||||
|
|
||||||
|
CoreAnalytics.logEvent({
|
||||||
|
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
|
||||||
|
ws: 'core_search_view_results',
|
||||||
|
name: Translate.instant('core.search.globalsearch'),
|
||||||
|
data: {
|
||||||
|
query,
|
||||||
|
filters: JSON.stringify(this.resultsSource.getFilters()),
|
||||||
|
},
|
||||||
|
url: CoreUrlUtils.addParamsToUrl('/search/index.php', {
|
||||||
|
q: query,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear search results.
|
||||||
|
*/
|
||||||
|
clearSearch(): void {
|
||||||
|
this.loadMoreError = null;
|
||||||
|
|
||||||
|
this.resultsSource.setQuery('');
|
||||||
|
this.resultsSource.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
@mod @mod_forum @app @javascript @lms_from4.3
|
||||||
|
Feature: Test Forum Search
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given solr is installed
|
||||||
|
And the following config values are set as admin:
|
||||||
|
| enableglobalsearch | 1 |
|
||||||
|
| searchengine | solr |
|
||||||
|
And the following "courses" exist:
|
||||||
|
| fullname | shortname |
|
||||||
|
| Course 1 | C1 |
|
||||||
|
| Course 2 | C2 |
|
||||||
|
And the following "users" exist:
|
||||||
|
| username |
|
||||||
|
| student1 |
|
||||||
|
And the following "course enrolments" exist:
|
||||||
|
| user | course | role |
|
||||||
|
| student1 | C1 | student |
|
||||||
|
| student1 | C2 | student |
|
||||||
|
And the following "activities" exist:
|
||||||
|
| activity | name | intro | course | idnumber |
|
||||||
|
| forum | Test forum 1 | Test forum 1 intro | C1 | forum1 |
|
||||||
|
| forum | Test forum 2 | Test forum 2 intro | C1 | forum2 |
|
||||||
|
| forum | Test forum 3 | Test forum 3 intro | C2 | forum3 |
|
||||||
|
And the following "mod_forum > discussions" exist:
|
||||||
|
| forum | name | subject | message |
|
||||||
|
| forum1 | Initial discussion 1 | Initial discussion 1 | Initial discussion message 1 |
|
||||||
|
| forum2 | Initial discussion 2 | Initial discussion 2 | Initial discussion message 2 |
|
||||||
|
| forum3 | Initial discussion 3 | Initial discussion 3 | Initial discussion message 3 |
|
||||||
|
|
||||||
|
# TODO test single forum search (lacking generators for post search results)
|
||||||
|
|
||||||
|
Scenario: Search in side block
|
||||||
|
Given global search expects the query "message" and will return:
|
||||||
|
| type | idnumber |
|
||||||
|
| activity | forum1 |
|
||||||
|
| activity | forum2 |
|
||||||
|
And the following "blocks" exist:
|
||||||
|
| blockname | contextlevel | reference |
|
||||||
|
| search_forums | Course | C1 |
|
||||||
|
And I entered the course "Course 1" as "student1" in the app
|
||||||
|
When I press "Open block drawer" in the app
|
||||||
|
And I press "Search forums" in the app
|
||||||
|
Then I should find "What are you searching for?" in the app
|
||||||
|
And I should find "Search forums" in the app
|
||||||
|
|
||||||
|
When I set the field "Search" to "message" in the app
|
||||||
|
And I press "Search" "button" in the app
|
||||||
|
Then I should find "Search results: 2" in the app
|
||||||
|
And I should find "Test forum 1" in the app
|
||||||
|
And I should find "Test forum 2" in the app
|
|
@ -28,6 +28,7 @@ export class CoreSearchGlobalSearchResultsSource extends CorePaginatedItemsManag
|
||||||
private query: string;
|
private query: string;
|
||||||
private filters: CoreSearchGlobalSearchFilters;
|
private filters: CoreSearchGlobalSearchFilters;
|
||||||
private pagesLoaded = 0;
|
private pagesLoaded = 0;
|
||||||
|
private totalResults?: number;
|
||||||
private topResultsIds?: number[];
|
private topResultsIds?: number[];
|
||||||
|
|
||||||
constructor(query: string, filters: CoreSearchGlobalSearchFilters) {
|
constructor(query: string, filters: CoreSearchGlobalSearchFilters) {
|
||||||
|
@ -93,6 +94,15 @@ export class CoreSearchGlobalSearchResultsSource extends CorePaginatedItemsManag
|
||||||
return this.pagesLoaded;
|
return this.pagesLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total results with the given filter.
|
||||||
|
*
|
||||||
|
* @returns Total results.
|
||||||
|
*/
|
||||||
|
getTotalResults(): number | null {
|
||||||
|
return this.totalResults ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -107,6 +117,7 @@ export class CoreSearchGlobalSearchResultsSource extends CorePaginatedItemsManag
|
||||||
*/
|
*/
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.pagesLoaded = 0;
|
this.pagesLoaded = 0;
|
||||||
|
delete this.totalResults;
|
||||||
delete this.topResultsIds;
|
delete this.topResultsIds;
|
||||||
|
|
||||||
super.reset();
|
super.reset();
|
||||||
|
@ -130,6 +141,8 @@ export class CoreSearchGlobalSearchResultsSource extends CorePaginatedItemsManag
|
||||||
|
|
||||||
const pageResults = await CoreSearchGlobalSearch.getResults(this.query, this.filters, page);
|
const pageResults = await CoreSearchGlobalSearch.getResults(this.query, this.filters, page);
|
||||||
|
|
||||||
|
this.totalResults = pageResults.total;
|
||||||
|
|
||||||
results.push(...pageResults.results.filter(result => !this.topResultsIds?.includes(result.id)));
|
results.push(...pageResults.results.filter(result => !this.topResultsIds?.includes(result.id)));
|
||||||
|
|
||||||
return { items: results, hasMoreItems: pageResults.canLoadMore };
|
return { items: results, hasMoreItems: pageResults.canLoadMore };
|
||||||
|
|
|
@ -16,16 +16,19 @@ import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreSearchBoxComponent } from './search-box/search-box';
|
import { CoreSearchBoxComponent } from './search-box/search-box';
|
||||||
|
import { CoreSearchGlobalSearchResultComponent } from '@features/search/components/global-search-result/global-search-result';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreSearchBoxComponent,
|
CoreSearchBoxComponent,
|
||||||
|
CoreSearchGlobalSearchResultComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreSharedModule,
|
CoreSharedModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CoreSearchBoxComponent,
|
CoreSearchBoxComponent,
|
||||||
|
CoreSearchGlobalSearchResultComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreSearchComponentsModule {}
|
export class CoreSearchComponentsModule {}
|
||||||
|
|
|
@ -11,14 +11,14 @@
|
||||||
<core-format-text [text]="result.title"></core-format-text>
|
<core-format-text [text]="result.title"></core-format-text>
|
||||||
</h3>
|
</h3>
|
||||||
<core-format-text *ngIf="result.content && !result.course && !result.user" [text]="result.content"></core-format-text>
|
<core-format-text *ngIf="result.content && !result.course && !result.user" [text]="result.content"></core-format-text>
|
||||||
<div *ngIf="result.context" class="flex-row">
|
<div *ngIf="renderedContext" class="flex-row">
|
||||||
<div *ngIf="result.context.courseName" class="result-context">
|
<div *ngIf="renderedContext.courseName" class="result-context">
|
||||||
<ion-icon name="fas-graduation-cap" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-graduation-cap" aria-hidden="true"></ion-icon>
|
||||||
<core-format-text [text]="result.context.courseName"></core-format-text>
|
<core-format-text [text]="renderedContext.courseName"></core-format-text>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="result.context.userName" class="result-context">
|
<div *ngIf="renderedContext.userName" class="result-context">
|
||||||
<ion-icon name="fas-user" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-user" aria-hidden="true"></ion-icon>
|
||||||
<span>{{ 'core.search.resultby' | translate: { $a: result.context.userName } }}</span>
|
<span>{{ 'core.search.resultby' | translate: { $a: renderedContext.userName } }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
|
||||||
import { CoreSearchGlobalSearchResult } from '@features/search/services/global-search';
|
import { CoreSearchGlobalSearchResult, CoreSearchGlobalSearchResultContext } from '@features/search/services/global-search';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-search-global-search-result',
|
selector: 'core-search-global-search-result',
|
||||||
|
@ -23,6 +23,9 @@ import { CoreSearchGlobalSearchResult } from '@features/search/services/global-s
|
||||||
export class CoreSearchGlobalSearchResultComponent implements OnChanges {
|
export class CoreSearchGlobalSearchResultComponent implements OnChanges {
|
||||||
|
|
||||||
@Input() result!: CoreSearchGlobalSearchResult;
|
@Input() result!: CoreSearchGlobalSearchResult;
|
||||||
|
@Input() showCourse?: boolean;
|
||||||
|
|
||||||
|
renderedContext: CoreSearchGlobalSearchResultContext | null = null;
|
||||||
renderedIcon: string | null = null;
|
renderedIcon: string | null = null;
|
||||||
|
|
||||||
@Output() onClick = new EventEmitter();
|
@Output() onClick = new EventEmitter();
|
||||||
|
@ -31,9 +34,25 @@ export class CoreSearchGlobalSearchResultComponent implements OnChanges {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
|
this.renderedContext = this.computeRenderedContext();
|
||||||
this.renderedIcon = this.computeRenderedIcon();
|
this.renderedIcon = this.computeRenderedIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the value of the context to render.
|
||||||
|
*
|
||||||
|
* @returns Rendered context.
|
||||||
|
*/
|
||||||
|
private computeRenderedContext(): CoreSearchGlobalSearchResultContext | null {
|
||||||
|
const context = { ...this.result.context } ?? {};
|
||||||
|
|
||||||
|
if (this.showCourse === false) {
|
||||||
|
delete context.courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(context).length > 0 ? context : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the value of the icon to render.
|
* Calculate the value of the icon to render.
|
||||||
*
|
*
|
||||||
|
|
|
@ -19,7 +19,6 @@ import { CoreSearchGlobalSearchPage } from './pages/global-search/global-search'
|
||||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||||
import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
|
import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
|
||||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.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.
|
* Build module routes.
|
||||||
|
@ -42,7 +41,6 @@ function buildRoutes(injector: Injector): Routes {
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreSearchGlobalSearchPage,
|
CoreSearchGlobalSearchPage,
|
||||||
CoreSearchGlobalSearchResultComponent,
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -78,7 +78,9 @@ export type CoreSearchGlobalSearchSearchArea = {
|
||||||
|
|
||||||
export interface CoreSearchGlobalSearchFilters {
|
export interface CoreSearchGlobalSearchFilters {
|
||||||
searchAreaCategoryIds?: string[];
|
searchAreaCategoryIds?: string[];
|
||||||
|
searchAreaIds?: string[];
|
||||||
courseIds?: number[];
|
courseIds?: number[];
|
||||||
|
contextIds?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,10 +118,11 @@ export class CoreSearchGlobalSearchService {
|
||||||
query: string,
|
query: string,
|
||||||
filters: CoreSearchGlobalSearchFilters,
|
filters: CoreSearchGlobalSearchFilters,
|
||||||
page: number,
|
page: number,
|
||||||
): Promise<{ results: CoreSearchGlobalSearchResult[]; canLoadMore: boolean }> {
|
): Promise<{ results: CoreSearchGlobalSearchResult[]; total: number; canLoadMore: boolean }> {
|
||||||
if (this.filtersYieldEmptyResults(filters)) {
|
if (this.filtersYieldEmptyResults(filters)) {
|
||||||
return {
|
return {
|
||||||
results: [],
|
results: [],
|
||||||
|
total: 0,
|
||||||
canLoadMore: false,
|
canLoadMore: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -128,7 +131,7 @@ export class CoreSearchGlobalSearchService {
|
||||||
const params: CoreSearchGetResultsWSParams = {
|
const params: CoreSearchGetResultsWSParams = {
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
filters: await this.prepareWSFilters(filters),
|
filters: await this.prepareAdvancedWSFilters(filters),
|
||||||
};
|
};
|
||||||
const preSets = CoreSites.getReadingStrategyPreSets(CoreSitesReadingStrategy.PREFER_NETWORK);
|
const preSets = CoreSites.getReadingStrategyPreSets(CoreSitesReadingStrategy.PREFER_NETWORK);
|
||||||
|
|
||||||
|
@ -136,6 +139,7 @@ export class CoreSearchGlobalSearchService {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
results: await Promise.all((results ?? []).map(result => this.formatWSResult(result))),
|
results: await Promise.all((results ?? []).map(result => this.formatWSResult(result))),
|
||||||
|
total: totalcount,
|
||||||
canLoadMore: totalcount > (page + 1) * CORE_SEARCH_GLOBAL_SEARCH_PAGE_LENGTH,
|
canLoadMore: totalcount > (page + 1) * CORE_SEARCH_GLOBAL_SEARCH_PAGE_LENGTH,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -155,7 +159,7 @@ export class CoreSearchGlobalSearchService {
|
||||||
const site = CoreSites.getRequiredCurrentSite();
|
const site = CoreSites.getRequiredCurrentSite();
|
||||||
const params: CoreSearchGetTopResultsWSParams = {
|
const params: CoreSearchGetTopResultsWSParams = {
|
||||||
query,
|
query,
|
||||||
filters: await this.prepareWSFilters(filters),
|
filters: await this.prepareAdvancedWSFilters(filters),
|
||||||
};
|
};
|
||||||
const preSets = CoreSites.getReadingStrategyPreSets(CoreSitesReadingStrategy.PREFER_NETWORK);
|
const preSets = CoreSites.getReadingStrategyPreSets(CoreSitesReadingStrategy.PREFER_NETWORK);
|
||||||
|
|
||||||
|
@ -207,7 +211,7 @@ export class CoreSearchGlobalSearchService {
|
||||||
const site = CoreSites.getRequiredCurrentSite();
|
const site = CoreSites.getRequiredCurrentSite();
|
||||||
const params: CoreSearchViewResultsWSParams = {
|
const params: CoreSearchViewResultsWSParams = {
|
||||||
query,
|
query,
|
||||||
filters: await this.prepareWSFilters(filters),
|
filters: await this.prepareBasicWSFilters(filters),
|
||||||
};
|
};
|
||||||
|
|
||||||
await site.write<CoreSearchViewResultsWSResponse>('core_search_view_results', params);
|
await site.write<CoreSearchViewResultsWSResponse>('core_search_view_results', params);
|
||||||
|
@ -269,33 +273,62 @@ export class CoreSearchGlobalSearchService {
|
||||||
* @returns Whether the given filters will return 0 results.
|
* @returns Whether the given filters will return 0 results.
|
||||||
*/
|
*/
|
||||||
protected filtersYieldEmptyResults(filters: CoreSearchGlobalSearchFilters): boolean {
|
protected filtersYieldEmptyResults(filters: CoreSearchGlobalSearchFilters): boolean {
|
||||||
return filters.courseIds?.length === 0 || filters.searchAreaCategoryIds?.length === 0;
|
return filters.courseIds?.length === 0
|
||||||
|
|| filters.contextIds?.length === 0
|
||||||
|
|| filters.searchAreaIds?.length === 0
|
||||||
|
|| filters.searchAreaCategoryIds?.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare search filters before sending to WS.
|
* Prepare basic search filters before sending to WS.
|
||||||
*
|
*
|
||||||
* @param filters App filters.
|
* @param filters App filters.
|
||||||
* @returns WS filters.
|
* @returns Basic WS filters.
|
||||||
*/
|
*/
|
||||||
protected async prepareWSFilters(filters: CoreSearchGlobalSearchFilters): Promise<CoreSearchBasicWSFilters> {
|
protected async prepareBasicWSFilters(filters: CoreSearchGlobalSearchFilters): Promise<CoreSearchBasicWSFilters> {
|
||||||
const wsFilters: CoreSearchBasicWSFilters = {};
|
const wsFilters: CoreSearchBasicWSFilters = {};
|
||||||
|
|
||||||
if (filters.courseIds) {
|
if (filters.courseIds) {
|
||||||
wsFilters.courseids = filters.courseIds;
|
wsFilters.courseids = filters.courseIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filters.searchAreaIds) {
|
||||||
|
wsFilters.areaids = filters.searchAreaIds;
|
||||||
|
}
|
||||||
|
|
||||||
if (filters.searchAreaCategoryIds) {
|
if (filters.searchAreaCategoryIds) {
|
||||||
const searchAreas = await this.getSearchAreas();
|
const searchAreas = await this.getSearchAreas();
|
||||||
|
|
||||||
wsFilters.areaids = searchAreas
|
wsFilters.areaids = searchAreas
|
||||||
.filter(({ category }) => filters.searchAreaCategoryIds?.includes(category.id))
|
.filter(({ id, category }) => {
|
||||||
|
if (filters.searchAreaIds && !filters.searchAreaIds.includes(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.searchAreaCategoryIds?.includes(category.id);
|
||||||
|
})
|
||||||
.map(({ id }) => id);
|
.map(({ id }) => id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return wsFilters;
|
return wsFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare advanced search filters before sending to WS.
|
||||||
|
*
|
||||||
|
* @param filters App filters.
|
||||||
|
* @returns Advanced WS filters.
|
||||||
|
*/
|
||||||
|
protected async prepareAdvancedWSFilters(filters: CoreSearchGlobalSearchFilters): Promise<CoreSearchAdvancedWSFilters> {
|
||||||
|
const wsFilters: CoreSearchAdvancedWSFilters = await this.prepareBasicWSFilters(filters);
|
||||||
|
|
||||||
|
if (filters.contextIds) {
|
||||||
|
wsFilters.contextids = filters.contextIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wsFilters;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreSearchGlobalSearch = makeSingleton(CoreSearchGlobalSearchService);
|
export const CoreSearchGlobalSearch = makeSingleton(CoreSearchGlobalSearchService);
|
||||||
|
|
|
@ -37,6 +37,7 @@ interface Args {
|
||||||
module: 'forum-activity' | 'forum-post' | 'assign' | 'none';
|
module: 'forum-activity' | 'forum-post' | 'assign' | 'none';
|
||||||
courseContext: boolean;
|
courseContext: boolean;
|
||||||
userContext: boolean;
|
userContext: boolean;
|
||||||
|
showCourse: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default <Meta<Args>> {
|
export default <Meta<Args>> {
|
||||||
|
@ -79,6 +80,7 @@ export default <Meta<Args>> {
|
||||||
module: 'none',
|
module: 'none',
|
||||||
courseContext: false,
|
courseContext: false,
|
||||||
userContext: false,
|
userContext: false,
|
||||||
|
showCourse: true,
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
design: {
|
design: {
|
||||||
|
@ -88,7 +90,7 @@ export default <Meta<Args>> {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = story<Args>(({ image, courseContext, userContext, module, ...args }) => {
|
const Template = story<Args>(({ image, courseContext, userContext, module, showCourse, ...args }) => {
|
||||||
const result: CoreSearchGlobalSearchResult = {
|
const result: CoreSearchGlobalSearchResult = {
|
||||||
...args,
|
...args,
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -126,7 +128,7 @@ const Template = story<Args>(({ image, courseContext, userContext, module, ...ar
|
||||||
|
|
||||||
return {
|
return {
|
||||||
component: CoreSearchGlobalSearchResultComponent,
|
component: CoreSearchGlobalSearchResultComponent,
|
||||||
props: { result },
|
props: { result, showCourse },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue