diff --git a/src/core/features/course/components/components.module.ts b/src/core/features/course/components/components.module.ts index 02d524304..866296a27 100644 --- a/src/core/features/course/components/components.module.ts +++ b/src/core/features/course/components/components.module.ts @@ -24,6 +24,7 @@ import { CoreCourseModuleComponent } from './module/module'; import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion'; import { CoreCourseModuleDescriptionComponent } from './module-description/module-description'; import { CoreCourseSectionSelectorComponent } from './section-selector/section-selector'; +import { CoreCourseTagAreaComponent } from './tag-area/tag-area'; import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module'; @NgModule({ @@ -33,6 +34,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup CoreCourseModuleCompletionComponent, CoreCourseModuleDescriptionComponent, CoreCourseSectionSelectorComponent, + CoreCourseTagAreaComponent, CoreCourseUnsupportedModuleComponent, ], imports: [ @@ -48,6 +50,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup CoreCourseModuleCompletionComponent, CoreCourseModuleDescriptionComponent, CoreCourseSectionSelectorComponent, + CoreCourseTagAreaComponent, CoreCourseUnsupportedModuleComponent, ], }) diff --git a/src/core/features/course/components/tag-area/core-course-tag-area.html b/src/core/features/course/components/tag-area/core-course-tag-area.html new file mode 100644 index 000000000..d7863d9e8 --- /dev/null +++ b/src/core/features/course/components/tag-area/core-course-tag-area.html @@ -0,0 +1,7 @@ + + + +

{{ item.courseName }}

+

{{ 'core.category' | translate }}: {{ item.categoryName }}

+
+
\ No newline at end of file diff --git a/src/core/features/course/components/tag-area/tag-area.ts b/src/core/features/course/components/tag-area/tag-area.ts new file mode 100644 index 000000000..401ba9b64 --- /dev/null +++ b/src/core/features/course/components/tag-area/tag-area.ts @@ -0,0 +1,42 @@ +// (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 } from '@angular/core'; + +import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { CoreCouseTagItems } from '@features/course/services/handlers/course-tag-area'; + +/** + * Component that renders the course tag area. + */ +@Component({ + selector: 'core-course-tag-area', + templateUrl: 'core-course-tag-area.html', +}) +export class CoreCourseTagAreaComponent { + + @Input() items?: CoreCouseTagItems[]; // Area items to render. + + /** + * Open a course. + * + * @param courseId The course to open. + */ + openCourse(courseId: number): void { + // @todo If this component is inside a split view, use the master nav to open it. + // const navCtrl = this.splitviewCtrl ? this.splitviewCtrl.getMasterNav() : this.navCtrl; + CoreCourseHelper.instance.getAndOpenCourse(courseId); + } + +} diff --git a/src/core/features/course/course.module.ts b/src/core/features/course/course.module.ts index bd623076c..4d1563d58 100644 --- a/src/core/features/course/course.module.ts +++ b/src/core/features/course/course.module.ts @@ -28,6 +28,10 @@ import { CoreCourseModulePrefetchDelegate } from './services/module-prefetch-del import { CoreCronDelegate } from '@services/cron'; import { CoreCourseLogCronHandler } from './services/handlers/log-cron'; import { CoreCourseSyncCronHandler } from './services/handlers/sync-cron'; +import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate'; +import { CoreCourseTagAreaHandler } from './services/handlers/course-tag-area'; +import { CoreCourseModulesTagAreaHandler } from './services/handlers/modules-tag-area'; +import { CoreCourse } from './services/course'; const routes: Routes = [ { @@ -65,7 +69,10 @@ const courseIndexRoutes: Routes = [ useFactory: () => () => { CoreCronDelegate.instance.register(CoreCourseSyncCronHandler.instance); CoreCronDelegate.instance.register(CoreCourseLogCronHandler.instance); + CoreTagAreaDelegate.instance.registerHandler(CoreCourseTagAreaHandler.instance); + CoreTagAreaDelegate.instance.registerHandler(CoreCourseModulesTagAreaHandler.instance); + CoreCourse.instance.initialize(); CoreCourseModulePrefetchDelegate.instance.initialize(); }, }, diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 989682fe6..9982e3d00 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -23,7 +23,7 @@ import { CoreTimeUtils } from '@services/utils/time'; import { CoreUtils } from '@services/utils/utils'; import { CoreSiteWSPreSets, CoreSite } from '@classes/site'; import { CoreConstants } from '@/core/constants'; -import { makeSingleton, Translate } from '@singletons'; +import { makeSingleton, Platform, Translate } from '@singletons'; import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile } from '@services/ws'; import { CoreCourseStatusDBRecord, COURSE_STATUS_TABLE } from './database/course'; @@ -39,6 +39,8 @@ import { CoreWSError } from '@classes/errors/wserror'; import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; import { CoreCourseHelper, CoreCourseModuleCompletionData } from './course-helper'; import { CoreCourseFormatDelegate } from './format-delegate'; +import { CoreCronDelegate } from '@services/cron'; +import { CoreCourseLogCronHandler } from './handlers/log-cron'; const ROOT_CACHE_KEY = 'mmCourse:'; @@ -77,6 +79,27 @@ export class CoreCourseProvider { this.logger = CoreLogger.getInstance('CoreCourseProvider'); } + /** + * Initialize. + */ + initialize(): void { + Platform.instance.resume.subscribe(() => { + // Run the handler the app is open to keep user in online status. + setTimeout(() => { + CoreCronDelegate.instance.forceCronHandlerExecution(CoreCourseLogCronHandler.instance.name); + }, 1000); + }); + + CoreEvents.on(CoreEvents.LOGIN, () => { + setTimeout(() => { + // Ignore errors here, since probably login is not complete: it happens on token invalid. + CoreUtils.instance.ignoreErrors( + CoreCronDelegate.instance.forceCronHandlerExecution(CoreCourseLogCronHandler.instance.name), + ); + }, 1000); + }); + } + /** * Check if the get course blocks WS is available in current site. * diff --git a/src/core/features/course/services/handlers/course-tag-area.ts b/src/core/features/course/services/handlers/course-tag-area.ts new file mode 100644 index 000000000..216bf0479 --- /dev/null +++ b/src/core/features/course/services/handlers/course-tag-area.ts @@ -0,0 +1,85 @@ +// (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, Type } from '@angular/core'; + +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate'; +import { CoreCourseTagAreaComponent } from '../../components/tag-area/tag-area'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support tags. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCourseTagAreaHandlerService implements CoreTagAreaHandler { + + name = 'CoreCourseTagAreaHandler'; + type = 'core/course'; + + /** + * Whether or not the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param content Rendered content. + * @return Area items (or promise resolved with the items). + */ + parseContent(content: string): CoreCouseTagItems[] { + const items: CoreCouseTagItems[] = []; + const element = CoreDomUtils.instance.convertToElement(content); + + Array.from(element.querySelectorAll('div.coursebox')).forEach((coursebox) => { + const courseId = parseInt(coursebox.getAttribute('data-courseid') || '', 10); + const courseLink = coursebox.querySelector('.coursename > a'); + const categoryLink = coursebox.querySelector('.coursecat > a'); + + if (courseId > 0 && courseLink) { + items.push({ + courseId, + courseName: courseLink.innerHTML, + categoryName: categoryLink ? categoryLink.innerHTML : null, + }); + } + }); + + return items; + } + + /** + * Get the component to use to display items. + * + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(): Type | Promise> { + return CoreCourseTagAreaComponent; + } + +} + +export class CoreCourseTagAreaHandler extends makeSingleton(CoreCourseTagAreaHandlerService) {} + +export type CoreCouseTagItems = { + courseId: number; + courseName: string; + categoryName: string | null; +}; diff --git a/src/core/features/course/services/handlers/modules-tag-area.ts b/src/core/features/course/services/handlers/modules-tag-area.ts new file mode 100644 index 000000000..21a786cc9 --- /dev/null +++ b/src/core/features/course/services/handlers/modules-tag-area.ts @@ -0,0 +1,62 @@ +// (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, Type } from '@angular/core'; + +import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate'; +import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper'; +import { CoreTagFeedComponent } from '@features/tag/components/feed/feed'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support tags. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCourseModulesTagAreaHandlerService implements CoreTagAreaHandler { + + name = 'CoreCourseModulesTagAreaHandler'; + type = 'core/course_modules'; + + /** + * Whether or not the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param content Rendered content. + * @return Area items (or promise resolved with the items). + */ + parseContent(content: string): CoreTagFeedElement[] | Promise { + return CoreTagHelper.instance.parseFeedContent(content); + } + + /** + * Get the component to use to display items. + * + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(): Type | Promise> { + return CoreTagFeedComponent; + } + +} + +export class CoreCourseModulesTagAreaHandler extends makeSingleton(CoreCourseModulesTagAreaHandlerService) {} diff --git a/src/core/features/tag/components/components.module.ts b/src/core/features/tag/components/components.module.ts new file mode 100644 index 000000000..595a5d3e3 --- /dev/null +++ b/src/core/features/tag/components/components.module.ts @@ -0,0 +1,39 @@ +// (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 { CoreTagFeedComponent } from './feed/feed'; +import { CoreSharedModule } from '@/core/shared.module'; + +@NgModule({ + declarations: [ + CoreTagFeedComponent, + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreSharedModule, + ], + providers: [ + ], + exports: [ + CoreTagFeedComponent, + ], +}) +export class CoreTagComponentsModule {} diff --git a/src/core/features/tag/components/feed/core-tag-feed.html b/src/core/features/tag/components/feed/core-tag-feed.html new file mode 100644 index 000000000..322459677 --- /dev/null +++ b/src/core/features/tag/components/feed/core-tag-feed.html @@ -0,0 +1,12 @@ + + + + + + +

{{ item.heading }}

+

{{ text }}

+
+
\ No newline at end of file diff --git a/src/core/features/tag/components/feed/feed.ts b/src/core/features/tag/components/feed/feed.ts new file mode 100644 index 000000000..8d69e3af3 --- /dev/null +++ b/src/core/features/tag/components/feed/feed.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 { Component, Input } from '@angular/core'; + +import { CoreTagFeedElement } from '@features/tag/services/tag-helper'; + +/** + * Component to render a tag area that uses the "core_tag/tagfeed" web template. + */ +@Component({ + selector: 'core-tag-feed', + templateUrl: 'core-tag-feed.html', +}) +export class CoreTagFeedComponent { + + @Input() items?: CoreTagFeedElement[]; // Area items to render. + +} diff --git a/src/core/features/tag/pages/index-area/index-area.page.ts b/src/core/features/tag/pages/index-area/index-area.page.ts index 46ea0ed39..d5b86177c 100644 --- a/src/core/features/tag/pages/index-area/index-area.page.ts +++ b/src/core/features/tag/pages/index-area/index-area.page.ts @@ -16,7 +16,6 @@ import { Component, OnInit, Type } from '@angular/core'; import { IonInfiniteScroll, IonRefresher } from '@ionic/angular'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreTag } from '@features/tag/services/tag'; -import { CoreTagFeedElement } from '../../services/tag-helper'; import { ActivatedRoute } from '@angular/router'; import { CoreTagAreaDelegate } from '../../services/tag-area-delegate'; import { Translate } from '@singletons'; @@ -45,7 +44,7 @@ export class CoreTagIndexAreaPage implements OnInit { loaded = false; componentName?: string; itemType?: string; - items: CoreTagFeedElement[] = []; + items: unknown[] = []; nextPage = 0; canLoadMore = false; areaComponent?: Type; diff --git a/src/core/features/tag/pages/index/index.page.ts b/src/core/features/tag/pages/index/index.page.ts index fc2c91fce..d007dd6db 100644 --- a/src/core/features/tag/pages/index/index.page.ts +++ b/src/core/features/tag/pages/index/index.page.ts @@ -19,7 +19,6 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreTag } from '@features/tag/services/tag'; import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate'; import { ActivatedRoute, Router } from '@angular/router'; -import { CoreTagFeedElement } from '../../services/tag-helper'; import { CoreNavigator } from '@services/navigator'; /** @@ -180,7 +179,7 @@ export type CoreTagAreaDisplay = { componentName: string; itemType: string; nameKey: string; - items: CoreTagFeedElement[]; + items: unknown[]; canLoadMore: boolean; badge: string; }; diff --git a/src/core/features/tag/services/tag-area-delegate.ts b/src/core/features/tag/services/tag-area-delegate.ts index a0daaf087..fbe1f14c3 100644 --- a/src/core/features/tag/services/tag-area-delegate.ts +++ b/src/core/features/tag/services/tag-area-delegate.ts @@ -15,7 +15,6 @@ import { Injectable, Type } from '@angular/core'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { makeSingleton } from '@singletons'; -import { CoreTagFeedElement } from './tag-helper'; /** * Interface that all tag area handlers must implement. @@ -32,7 +31,7 @@ export interface CoreTagAreaHandler extends CoreDelegateHandler { * @param content Rendered content. * @return Area items (or promise resolved with the items). */ - parseContent(content: string): CoreTagFeedElement[] | Promise; + parseContent(content: string): unknown[] | Promise; /** * Get the component to use to display items. @@ -74,7 +73,7 @@ export class CoreTagAreaDelegateService extends CoreDelegate * @param content Rendered content. * @return Promise resolved with the area items, or undefined if not found. */ - async parseContent(component: string, itemType: string, content: string): Promise { + async parseContent(component: string, itemType: string, content: string): Promise { const type = component + '/' + itemType; return await this.executeFunctionOnEnabled(type, 'parseContent', [content]); diff --git a/src/core/features/tag/tag.module.ts b/src/core/features/tag/tag.module.ts index 45db517af..706f0a26c 100644 --- a/src/core/features/tag/tag.module.ts +++ b/src/core/features/tag/tag.module.ts @@ -20,6 +20,7 @@ import { CoreContentLinksDelegate } from '@features/contentlinks/services/conten import { CoreTagMainMenuHandler, CoreTagMainMenuHandlerService } from './services/handlers/mainmenu'; import { CoreTagIndexLinkHandler } from './services/handlers/index-link'; import { CoreTagSearchLinkHandler } from './services/handlers/search-link'; +import { CoreTagComponentsModule } from './components/components.module'; const routes: Routes = [ { @@ -31,6 +32,7 @@ const routes: Routes = [ @NgModule({ imports: [ CoreMainMenuRoutingModule.forChild({ children: routes }), + CoreTagComponentsModule, ], exports: [CoreMainMenuRoutingModule], providers: [