diff --git a/src/addons/mod/data/components/index/addon-mod-data-index.html b/src/addons/mod/data/components/index/addon-mod-data-index.html
index 8a1f45d10..1bb70403b 100644
--- a/src/addons/mod/data/components/index/addon-mod-data-index.html
+++ b/src/addons/mod/data/components/index/addon-mod-data-index.html
@@ -138,6 +138,9 @@
+
+
+
diff --git a/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html b/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html
index aacd8344e..c9cb04c87 100644
--- a/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html
+++ b/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html
@@ -55,6 +55,9 @@
+
+
+
diff --git a/src/addons/mod/folder/components/index/addon-mod-folder-index.html b/src/addons/mod/folder/components/index/addon-mod-folder-index.html
index cb1093a10..6c8d1aaeb 100644
--- a/src/addons/mod/folder/components/index/addon-mod-folder-index.html
+++ b/src/addons/mod/folder/components/index/addon-mod-folder-index.html
@@ -48,3 +48,6 @@
[message]=" 'addon.mod_folder.emptyfilelist' | translate">
+
+
+
diff --git a/src/addons/mod/forum/components/index/index.html b/src/addons/mod/forum/components/index/index.html
index d282b828e..63cd1b7b6 100644
--- a/src/addons/mod/forum/components/index/index.html
+++ b/src/addons/mod/forum/components/index/index.html
@@ -140,6 +140,9 @@
+
+
+
diff --git a/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html b/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html
index c8a816a25..d6f12a61e 100644
--- a/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html
+++ b/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html
@@ -96,6 +96,9 @@
+
+
+
diff --git a/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html b/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html
index 4c7b7cd88..300b5feb7 100644
--- a/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html
+++ b/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html
@@ -84,3 +84,6 @@
[trackComponent]="trackComponent" [contextId]="h5pActivity?.context">
+
+
+
diff --git a/src/addons/mod/h5pactivity/components/index/index.ts b/src/addons/mod/h5pactivity/components/index/index.ts
index cf7b0b60b..762c6fefe 100644
--- a/src/addons/mod/h5pactivity/components/index/index.ts
+++ b/src/addons/mod/h5pactivity/components/index/index.ts
@@ -45,7 +45,6 @@ import {
} from '../../services/h5pactivity-sync';
import { CoreFileHelper } from '@services/file-helper';
import { AddonModH5PActivityModuleHandlerService } from '../../services/handlers/module';
-import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu';
/**
* Component that displays an H5P activity entry page.
@@ -87,7 +86,6 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
protected messageListenerFunction: (event: MessageEvent) => Promise;
constructor(
- protected mainMenuPage: CoreMainMenuPage,
protected content?: IonContent,
@Optional() courseContentsPage?: CoreCourseContentsPage,
) {
diff --git a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html
index 4f15b3bc9..52a6d1eb9 100644
--- a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html
+++ b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html
@@ -47,3 +47,6 @@
+
+
+
diff --git a/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html
index a9eeb2a60..0219bef8c 100644
--- a/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html
+++ b/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html
@@ -297,3 +297,6 @@
+
+
+
diff --git a/src/addons/mod/lti/components/index/addon-mod-lti-index.html b/src/addons/mod/lti/components/index/addon-mod-lti-index.html
index ef10e579e..20cf1a86e 100644
--- a/src/addons/mod/lti/components/index/addon-mod-lti-index.html
+++ b/src/addons/mod/lti/components/index/addon-mod-lti-index.html
@@ -32,3 +32,6 @@
+
+
+
diff --git a/src/addons/mod/page/components/index/addon-mod-page-index.html b/src/addons/mod/page/components/index/addon-mod-page-index.html
index 2dcb76d66..078f5c5c9 100644
--- a/src/addons/mod/page/components/index/addon-mod-page-index.html
+++ b/src/addons/mod/page/components/index/addon-mod-page-index.html
@@ -48,3 +48,6 @@
+
+
+
diff --git a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html
index cf49f5b7b..5d39ece10 100644
--- a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html
+++ b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html
@@ -226,3 +226,6 @@
+
+
+
diff --git a/src/addons/mod/resource/components/index/addon-mod-resource-index.html b/src/addons/mod/resource/components/index/addon-mod-resource-index.html
index 4c8e7daf6..9638db04e 100644
--- a/src/addons/mod/resource/components/index/addon-mod-resource-index.html
+++ b/src/addons/mod/resource/components/index/addon-mod-resource-index.html
@@ -18,7 +18,7 @@
-
+
-
+
+
+
diff --git a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html
index f184db40a..37ae91bfa 100644
--- a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html
+++ b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html
@@ -236,3 +236,6 @@
+
+
+
diff --git a/src/addons/mod/survey/components/index/addon-mod-survey-index.html b/src/addons/mod/survey/components/index/addon-mod-survey-index.html
index b5042fbf5..345c10b5a 100644
--- a/src/addons/mod/survey/components/index/addon-mod-survey-index.html
+++ b/src/addons/mod/survey/components/index/addon-mod-survey-index.html
@@ -147,3 +147,6 @@
+
+
+
diff --git a/src/addons/mod/url/components/index/addon-mod-url-index.html b/src/addons/mod/url/components/index/addon-mod-url-index.html
index 491e186eb..7026fddf3 100644
--- a/src/addons/mod/url/components/index/addon-mod-url-index.html
+++ b/src/addons/mod/url/components/index/addon-mod-url-index.html
@@ -13,7 +13,7 @@
-
+
+
+
+
diff --git a/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html
index e6628246f..ee8fec538 100644
--- a/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html
+++ b/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html
@@ -89,6 +89,9 @@
+
+
+
diff --git a/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html b/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html
index 1aedc639d..8a5e5543d 100644
--- a/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html
+++ b/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html
@@ -253,3 +253,6 @@
+
+
+
diff --git a/src/core/features/course/classes/module-base-handler.ts b/src/core/features/course/classes/module-base-handler.ts
index f45aa6a53..474fb231c 100644
--- a/src/core/features/course/classes/module-base-handler.ts
+++ b/src/core/features/course/classes/module-base-handler.ts
@@ -45,13 +45,18 @@ export class CoreModuleHandlerBase implements Partial {
title: module.name,
class: 'addon-mod_' + module.modname + '-handler',
showDownloadButton: true,
- action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void => {
+ action: async (
+ event: Event,
+ module: CoreCourseModule,
+ courseId: number,
+ options?: CoreNavigationOptions,
+ ): Promise => {
options = options || {};
options.params = options.params || {};
Object.assign(options.params, { module });
const routeParams = '/' + courseId + '/' + module.id;
- CoreNavigator.navigateToSitePath(this.pageName + routeParams, options);
+ await CoreNavigator.navigateToSitePath(this.pageName + routeParams, options);
},
};
}
diff --git a/src/core/features/course/components/components.module.ts b/src/core/features/course/components/components.module.ts
index 3680f6f36..516a43111 100644
--- a/src/core/features/course/components/components.module.ts
+++ b/src/core/features/course/components/components.module.ts
@@ -26,6 +26,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup
import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-legacy/module-completion-legacy';
import { CoreCourseModuleInfoComponent } from './module-info/module-info';
import { CoreCourseModuleManualCompletionComponent } from './module-manual-completion/module-manual-completion';
+import { CoreCourseModuleNavigationComponent } from './module-navigation/module-navigation';
@NgModule({
declarations: [
@@ -39,6 +40,7 @@ import { CoreCourseModuleManualCompletionComponent } from './module-manual-compl
CoreCourseSectionSelectorComponent,
CoreCourseTagAreaComponent,
CoreCourseUnsupportedModuleComponent,
+ CoreCourseModuleNavigationComponent,
],
imports: [
CoreBlockComponentsModule,
@@ -55,6 +57,7 @@ import { CoreCourseModuleManualCompletionComponent } from './module-manual-compl
CoreCourseSectionSelectorComponent,
CoreCourseTagAreaComponent,
CoreCourseUnsupportedModuleComponent,
+ CoreCourseModuleNavigationComponent,
],
})
export class CoreCourseComponentsModule {}
diff --git a/src/core/features/course/components/module-navigation/core-course-module-navigation.html b/src/core/features/course/components/module-navigation/core-course-module-navigation.html
new file mode 100644
index 000000000..74fc6f584
--- /dev/null
+++ b/src/core/features/course/components/module-navigation/core-course-module-navigation.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/features/course/components/module-navigation/module-navigation.scss b/src/core/features/course/components/module-navigation/module-navigation.scss
new file mode 100644
index 000000000..2a8b74f54
--- /dev/null
+++ b/src/core/features/course/components/module-navigation/module-navigation.scss
@@ -0,0 +1,43 @@
+@import "~theme/globals";
+
+:host {
+ --height: var(--core-course-module-navigation-height, var(--core-course-module-navigation-max-height));
+ --background: var(--core-course-module-navigation-background);
+
+ height: var(--height);
+ width: 100%;
+ background-color: var(--background);
+ display: block;
+ bottom: 0;
+ z-index: 3;
+ box-shadow: 0px -3px 3px rgba(var(--drop-shadow));
+
+ @include core-transition(all, 200ms);
+
+ ion-col {
+ padding: 2px;
+ }
+
+ core-loading {
+ text-align: center;
+ }
+
+ ion-buttom {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ }
+
+ core-loading {
+ --loading-inline-min-height: var(--height);
+ }
+}
+
+:host-context(.core-iframe-fullscreen) {
+ opacity: 0 !important;
+ height: 0 !important;
+}
+
+:host-context(core-course-format.core-course-format-singleactivity) {
+ opacity: 0 !important;
+ height: 0 !important;
+}
diff --git a/src/core/features/course/components/module-navigation/module-navigation.ts b/src/core/features/course/components/module-navigation/module-navigation.ts
new file mode 100644
index 000000000..627f0bbd5
--- /dev/null
+++ b/src/core/features/course/components/module-navigation/module-navigation.ts
@@ -0,0 +1,335 @@
+// (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, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
+import { CoreCourse, CoreCourseProvider, CoreCourseWSSection } from '@features/course/services/course';
+import { CoreCourseModule } from '@features/course/services/course-helper';
+import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
+import { IonContent } from '@ionic/angular';
+import { ScrollDetail } from '@ionic/core';
+import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
+import { CoreDomUtils } from '@services/utils/dom';
+import { CoreUtils } from '@services/utils/utils';
+import { CoreEventObserver, CoreEvents } from '@singletons/events';
+
+/**
+ * Component to show a button to go to the next resource/activity.
+ *
+ * Example usage:
+ *
+ */
+@Component({
+ selector: 'core-course-module-navigation',
+ templateUrl: 'core-course-module-navigation.html',
+ styleUrls: ['module-navigation.scss'],
+})
+export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
+
+ @Input() courseId!: number; // Course ID.
+ @Input() currentModuleId!: number; // Current module ID.
+
+ nextModule?: CoreCourseModule;
+ previousModule?: CoreCourseModule;
+ loaded = false;
+
+ protected element: HTMLElement;
+ protected initialHeight = 0;
+ protected initialPaddingBottom = 0;
+ protected previousTop = 0;
+ protected content?: HTMLIonContentElement | null;
+ protected completionObserver: CoreEventObserver;
+
+ constructor(el: ElementRef, protected ionContent: IonContent) {
+ const siteId = CoreSites.getCurrentSiteId();
+
+ this.element = el.nativeElement;
+ this.element.setAttribute('slot', 'fixed');
+
+ this.completionObserver = CoreEvents.on(CoreEvents.COMPLETION_MODULE_VIEWED, async (data) => {
+ if (data && data.courseId == this.courseId) {
+ // Check if now there's a next module.
+ await this.setNextAndPreviousModules(
+ CoreSitesReadingStrategy.PREFER_NETWORK,
+ !this.nextModule,
+ !this.previousModule,
+ );
+ }
+ }, siteId);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async ngOnInit(): Promise {
+ try {
+ await this.setNextAndPreviousModules(CoreSitesReadingStrategy.PREFER_CACHE);
+ } finally {
+ this.loaded = true;
+
+ await CoreUtils.nextTicks(50);
+ this.listenScrollEvents();
+ }
+ }
+
+ /**
+ * Setup scroll event listener.
+ *
+ * @param retries Number of retries left.
+ */
+ protected async listenScrollEvents(retries = 3): Promise {
+ this.initialHeight = this.element.getBoundingClientRect().height;
+
+ if (this.initialHeight == 0 && retries > 0) {
+ await CoreUtils.nextTicks(50);
+
+ this.listenScrollEvents(retries - 1);
+
+ return;
+ }
+ // Set a minimum height value.
+ this.initialHeight = this.initialHeight || 56;
+
+ this.content = this.element.closest('ion-content');
+
+ if (!this.content) {
+ return;
+ }
+
+ // Special case where there's no navigation.
+ const courseFormat = this.element.closest('core-course-format.core-course-format-singleactivity');
+ if (courseFormat) {
+ this.element.remove();
+ this.ngOnDestroy();
+
+ return;
+ }
+
+ // Move element to the nearest ion-content if it's not the parent.
+ if (this.element.parentElement?.nodeName != 'ION-CONTENT') {
+ this.content.appendChild(this.element);
+ }
+
+ // Set a padding to not overlap elements.
+ this.initialPaddingBottom = parseFloat(this.content.style.getPropertyValue('--padding-bottom') || '0');
+ this.content.style.setProperty('--padding-bottom', this.initialPaddingBottom + this.initialHeight + 'px');
+ const scroll = await this.content.getScrollElement();
+ this.content.scrollEvents = true;
+
+ this.setBarHeight(this.initialHeight);
+ this.content.addEventListener('ionScroll', (e: CustomEvent): void => {
+ if (!this.content) {
+ return;
+ }
+
+ this.onScroll(e.detail.scrollTop, scroll.scrollHeight - scroll.offsetHeight);
+ });
+
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async ngOnDestroy(): Promise {
+ this.completionObserver.off();
+ this.content?.style.setProperty('--padding-bottom', this.initialPaddingBottom + 'px');
+ }
+
+ /**
+ * Set previous and next modules.
+ *
+ * @param readingStrategy Reading strategy.
+ * @param checkNext Check next module.
+ * @param checkPrevious Check previous module.
+ * @return Promise resolved when done.
+ */
+ protected async setNextAndPreviousModules(
+ readingStrategy: CoreSitesReadingStrategy,
+ checkNext = true,
+ checkPrevious = true,
+ ): Promise {
+ if (!checkNext && !checkPrevious) {
+ return;
+ }
+
+ const preSets = CoreSites.getReadingStrategyPreSets(readingStrategy);
+
+ const sections = await CoreCourse.getSections(this.courseId, false, true, preSets);
+
+ // Search the next module.
+ let currentModuleIndex = -1;
+
+ const currentSectionIndex = sections.findIndex((section) => {
+ if (!this.isSectionAvailable(section)) {
+ // User cannot view the section, skip it.
+ return false;
+ }
+
+ currentModuleIndex = section.modules.findIndex((module: CoreCourseModule) => module.id == this.currentModuleId);
+
+ return currentModuleIndex >= 0;
+ });
+
+ if (currentSectionIndex < 0) {
+ // Nothing found. Return.
+
+ return;
+ }
+
+ if (checkNext) {
+ // Find next Module.
+ this.nextModule = undefined;
+ for (let i = currentSectionIndex; i < sections.length && this.nextModule == undefined; i++) {
+ const section = sections[i];
+
+ if (!this.isSectionAvailable(section)) {
+ // User cannot view the section, skip it.
+ continue;
+ }
+
+ const startModule = i == currentSectionIndex ? currentModuleIndex + 1 : 0;
+ for (let j = startModule; j < section.modules.length && this.nextModule == undefined; j++) {
+ const module = section.modules[j];
+
+ const found = await this.isModuleAvailable(module, section.id);
+ if (found) {
+ this.nextModule = module;
+ }
+ }
+ }
+ }
+
+ if (checkPrevious) {
+ // Find previous Module.
+ this.previousModule = undefined;
+ for (let i = currentSectionIndex; i >= 0 && this.previousModule == undefined; i--) {
+ const section = sections[i];
+
+ if (!this.isSectionAvailable(section)) {
+ // User cannot view the section, skip it.
+ continue;
+ }
+
+ const startModule = i == currentSectionIndex ? currentModuleIndex - 1 : section.modules.length - 1;
+ for (let j = startModule; j >= 0 && this.previousModule == undefined; j--) {
+ const module = section.modules[j];
+
+ const found = await this.isModuleAvailable(module, section.id);
+ if (found) {
+ this.previousModule = module;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Module is visible by the user and it has a specific view (e.g. not a label).
+ *
+ * @param module Module to check.
+ * @param sectionId Section ID the module belongs to.
+ * @return Wether the module is available to the user or not.
+ */
+ protected async isModuleAvailable(module: CoreCourseModule, sectionId: number): Promise {
+ if (module.uservisible === false || !CoreCourse.instance.moduleHasView(module)) {
+ return false;
+ }
+
+ if (!module.handlerData) {
+ module.handlerData =
+ await CoreCourseModuleDelegate.getModuleDataFor(module.modname, module, this.courseId, sectionId);
+ }
+
+ return !!module.handlerData?.action;
+ }
+
+ /**
+ * Section is visible by the user and its not stealth
+ *
+ * @param section Section to check.
+ * @return Wether the module is available to the user or not.
+ */
+ protected isSectionAvailable(section: CoreCourseWSSection): boolean {
+ return section.uservisible !== false && section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
+ }
+
+ /**
+ * Go to next/previous module.
+ *
+ * @return Promise resolved when done.
+ */
+ async goToActivity(next = true): Promise {
+ if (!this.loaded) {
+ return;
+ }
+
+ const modal = await CoreDomUtils.showModalLoading();
+
+ // Re-calculate module in case a new module was made visible.
+ await CoreUtils.ignoreErrors(this.setNextAndPreviousModules(CoreSitesReadingStrategy.PREFER_NETWORK, next, !next));
+
+ modal.dismiss();
+
+ const module = next ? this.nextModule : this.previousModule;
+ if (!module) {
+ // It seems the module was hidden. Show a message.
+ CoreDomUtils.instance.showErrorModal(
+ next ? 'core.course.gotonextactivitynotfound' : 'core.course.gotopreviousactivitynotfound',
+ true,
+ );
+
+ return;
+ }
+
+ if (!module.handlerData?.action) {
+ return;
+ }
+
+ module.handlerData.action(new Event('click'), module, this.courseId, { replace: true });
+ }
+
+ /**
+ * On scroll function.
+ *
+ * @param top Scroll top measure.
+ * @param maxScroll Scroll height.
+ */
+ protected onScroll(top: number, maxScroll: number): void {
+ if (top == 0 || top == maxScroll) {
+ // Reset.
+ this.setBarHeight(this.initialHeight);
+ } else {
+ const diffHeight = this.element.clientHeight - (top - this.previousTop);
+ this.setBarHeight(diffHeight);
+ }
+
+ this.previousTop = top;
+ }
+
+ /**
+ * Sets the bar height.
+ *
+ * @param height The new bar height.
+ */
+ protected setBarHeight(height: number): void {
+ if (height <= 0) {
+ height = 0;
+ } else if (height > this.initialHeight) {
+ height = this.initialHeight;
+ }
+
+ this.element.style.opacity = height == 0 ? '0' : '1';
+ this.content?.style.setProperty('--core-course-module-navigation-height', height + 'px');
+ }
+
+}
diff --git a/src/core/features/course/components/module/module.ts b/src/core/features/course/components/module/module.ts
index 5431e5114..b077bd66f 100644
--- a/src/core/features/course/components/module/module.ts
+++ b/src/core/features/course/components/module/module.ts
@@ -161,7 +161,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
event.preventDefault();
event.stopPropagation();
- button.action(event, this.module!, this.courseId!);
+ button.action(event, this.module, this.courseId!);
}
/**
diff --git a/src/core/features/course/lang.json b/src/core/features/course/lang.json
index 7afa1a068..bba7ba758 100644
--- a/src/core/features/course/lang.json
+++ b/src/core/features/course/lang.json
@@ -27,6 +27,10 @@
"confirmpartialdownloadsize": "You are about to download at least {{size}}.{{availableSpace}} Are you sure you want to continue?",
"confirmlimiteddownload": "You are not currently connected to Wi-Fi. ",
"contents": "Contents",
+ "gotonextactivity": "Continue to next activity",
+ "gotonextactivitynotfound": "Next activity not found. It's possible that it has been hidden or deleted.",
+ "gotopreviousactivity": "Continue to previous activity",
+ "gotopreviousactivitynotfound": "Previous activity not found. It's possible that it has been hidden or deleted.",
"couldnotloadsectioncontent": "Could not load the section content. Please try again later.",
"couldnotloadsections": "Could not load the sections. Please try again later.",
"coursesummary": "Course summary",
diff --git a/src/core/features/course/services/module-delegate.ts b/src/core/features/course/services/module-delegate.ts
index e5f918d1f..66dec4184 100644
--- a/src/core/features/course/services/module-delegate.ts
+++ b/src/core/features/course/services/module-delegate.ts
@@ -167,8 +167,9 @@ export interface CoreCourseModuleHandlerData {
* @param module The module object.
* @param courseId The course ID.
* @param options Options for the navigation.
+ * @return Promise resolved when done.
*/
- action?(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void;
+ action?(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): Promise | void;
/**
* Updates the status of the module.
@@ -236,8 +237,10 @@ export interface CoreCourseModuleHandlerButton {
* @param event The click event.
* @param module The module object.
* @param courseId The course ID.
+ * @param options Options for the navigation.
+ * @return Promise resolved when done.
*/
- action(event: Event, module: CoreCourseModule, courseId: number): void;
+ action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): Promise | void;
}
/**
diff --git a/src/core/features/siteplugins/components/components.module.ts b/src/core/features/siteplugins/components/components.module.ts
index 2969000e5..b9482e300 100644
--- a/src/core/features/siteplugins/components/components.module.ts
+++ b/src/core/features/siteplugins/components/components.module.ts
@@ -28,6 +28,7 @@ import { CoreSitePluginsAssignSubmissionComponent } from './assign-submission/as
import { CoreSitePluginsWorkshopAssessmentStrategyComponent } from './workshop-assessment-strategy/workshop-assessment-strategy';
import { CoreSitePluginsBlockComponent } from './block/block';
import { CoreSitePluginsOnlyTitleBlockComponent } from './only-title-block/only-title-block';
+import { CoreCourseComponentsModule } from '@features/course/components/components.module';
@NgModule({
declarations: [
@@ -47,6 +48,7 @@ import { CoreSitePluginsOnlyTitleBlockComponent } from './only-title-block/only-
imports: [
CoreSharedModule,
CoreCompileHtmlComponentModule,
+ CoreCourseComponentsModule,
],
exports: [
CoreSitePluginsPluginContentComponent,
diff --git a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html
index 930a6e0a0..ac5c6b837 100644
--- a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html
+++ b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html
@@ -11,8 +11,7 @@
+ [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
+
+
diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss
index fa001efb9..a4b0b935e 100644
--- a/src/theme/theme.light.scss
+++ b/src/theme/theme.light.scss
@@ -257,6 +257,9 @@
--core-courseimage-on-course-height: 150px;
+ --core-course-module-navigation-max-height: 56px;
+ --core-course-module-navigation-background: var(--contrast-background);
+
--addon-calendar-event-category-color: var(--purple);
--addon-calendar-event-course-color: var(--red);
--addon-calendar-event-group-color: var(--yellow);