forked from EVOgeek/Vmeda.Online
		
	
						commit
						2d19033bb8
					
				| @ -28,6 +28,7 @@ import { CALENDAR_SITE_SCHEMA } from './services/database/calendar'; | |||||||
| import { CALENDAR_OFFLINE_SITE_SCHEMA } from './services/database/calendar-offline'; | import { CALENDAR_OFFLINE_SITE_SCHEMA } from './services/database/calendar-offline'; | ||||||
| import { AddonCalendarComponentsModule } from './components/components.module'; | import { AddonCalendarComponentsModule } from './components/components.module'; | ||||||
| import { AddonCalendar } from './services/calendar'; | import { AddonCalendar } from './services/calendar'; | ||||||
|  | import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; | ||||||
| 
 | 
 | ||||||
| const mainMenuChildrenRoutes: Routes = [ | const mainMenuChildrenRoutes: Routes = [ | ||||||
|     { |     { | ||||||
| @ -38,6 +39,7 @@ const mainMenuChildrenRoutes: Routes = [ | |||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     imports: [ |     imports: [ | ||||||
|  |         CoreMainMenuTabRoutingModule.forChild(mainMenuChildrenRoutes), | ||||||
|         CoreMainMenuRoutingModule.forChild({ children: mainMenuChildrenRoutes }), |         CoreMainMenuRoutingModule.forChild({ children: mainMenuChildrenRoutes }), | ||||||
|         AddonCalendarComponentsModule, |         AddonCalendarComponentsModule, | ||||||
|     ], |     ], | ||||||
|  | |||||||
| @ -40,6 +40,7 @@ import { CoreNavigator } from '@services/navigator'; | |||||||
| import { Params } from '@angular/router'; | import { Params } from '@angular/router'; | ||||||
| import { Subscription } from 'rxjs'; | import { Subscription } from 'rxjs'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the calendar events for a certain day. |  * Page that displays the calendar events for a certain day. | ||||||
| @ -85,7 +86,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | |||||||
|     loaded = false; |     loaded = false; | ||||||
|     hasOffline = false; |     hasOffline = false; | ||||||
|     isOnline = false; |     isOnline = false; | ||||||
|     syncIcon = 'spinner'; |     syncIcon = CoreConstants.ICON_LOADING; | ||||||
|     isCurrentDay = false; |     isCurrentDay = false; | ||||||
|     isPastDay = false; |     isPastDay = false; | ||||||
|     currentMoment!: moment.Moment; |     currentMoment!: moment.Moment; | ||||||
| @ -260,7 +261,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | |||||||
|      */ |      */ | ||||||
|     async fetchData(sync?: boolean): Promise<void> { |     async fetchData(sync?: boolean): Promise<void> { | ||||||
| 
 | 
 | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.isOnline = CoreApp.instance.isOnline(); |         this.isOnline = CoreApp.instance.isOnline(); | ||||||
| 
 | 
 | ||||||
|         if (sync) { |         if (sync) { | ||||||
| @ -320,7 +321,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.loaded = true; |         this.loaded = true; | ||||||
|         this.syncIcon = 'fas-sync-alt'; |         this.syncIcon = CoreConstants.ICON_SYNC; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -450,7 +451,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     async refreshData(sync?: boolean, afterChange?: boolean): Promise<void> { |     async refreshData(sync?: boolean, afterChange?: boolean): Promise<void> { | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         const promises: Promise<void>[] = []; |         const promises: Promise<void>[] = []; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -45,6 +45,7 @@ import { CoreUtils } from '@services/utils/utils'; | |||||||
| import { AddonCalendarReminderDBRecord } from '../../services/database/calendar'; | import { AddonCalendarReminderDBRecord } from '../../services/database/calendar'; | ||||||
| import { ActivatedRoute } from '@angular/router'; | import { ActivatedRoute } from '@angular/router'; | ||||||
| import { CoreScreen } from '@services/screen'; | import { CoreScreen } from '@services/screen'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays a single calendar event. |  * Page that displays a single calendar event. | ||||||
| @ -84,7 +85,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|     canDelete = false; |     canDelete = false; | ||||||
|     hasOffline = false; |     hasOffline = false; | ||||||
|     isOnline = false; |     isOnline = false; | ||||||
|     syncIcon = 'spinner'; // Sync icon.
 |     syncIcon = CoreConstants.ICON_LOADING; // Sync icon.
 | ||||||
|     isSplitViewOn = false; |     isSplitViewOn = false; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
| @ -163,7 +164,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             this.eventId = eventId; |             this.eventId = eventId; | ||||||
|             this.syncIcon = 'spinner'; |             this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|             this.fetchEvent(); |             this.fetchEvent(); | ||||||
|         }); |         }); | ||||||
| @ -338,7 +339,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.eventLoaded = true; |         this.eventLoaded = true; | ||||||
|         this.syncIcon = 'fas-sync-alt'; |         this.syncIcon = CoreConstants.ICON_SYNC; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -417,7 +418,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     async refreshEvent(sync = false, showErrors = false): Promise<void> { |     async refreshEvent(sync = false, showErrors = false): Promise<void> { | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         const promises: Promise<void>[] = []; |         const promises: Promise<void>[] = []; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -32,6 +32,7 @@ import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming- | |||||||
| import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; | import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; | ||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreLocalNotifications } from '@services/local-notifications'; | import { CoreLocalNotifications } from '@services/local-notifications'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -68,7 +69,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { | |||||||
|     loaded = false; |     loaded = false; | ||||||
|     hasOffline = false; |     hasOffline = false; | ||||||
|     isOnline = false; |     isOnline = false; | ||||||
|     syncIcon = 'spinner'; |     syncIcon = CoreConstants.ICON_LOADING; | ||||||
|     showCalendar = true; |     showCalendar = true; | ||||||
|     loadUpcoming = false; |     loadUpcoming = false; | ||||||
|     filter: AddonCalendarFilter = { |     filter: AddonCalendarFilter = { | ||||||
| @ -194,7 +195,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { | |||||||
|      */ |      */ | ||||||
|     async fetchData(sync?: boolean, showErrors?: boolean): Promise<void> { |     async fetchData(sync?: boolean, showErrors?: boolean): Promise<void> { | ||||||
| 
 | 
 | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.isOnline = CoreApp.instance.isOnline(); |         this.isOnline = CoreApp.instance.isOnline(); | ||||||
| 
 | 
 | ||||||
|         if (sync) { |         if (sync) { | ||||||
| @ -254,7 +255,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.loaded = true; |         this.loaded = true; | ||||||
|         this.syncIcon = 'fas-sync-alt'; |         this.syncIcon = CoreConstants.ICON_SYNC; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -285,7 +286,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     async refreshData(sync = false, showErrors = false): Promise<void> { |     async refreshData(sync = false, showErrors = false): Promise<void> { | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         const promises: Promise<void>[] = []; |         const promises: Promise<void>[] = []; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -89,7 +89,7 @@ export class AddonCalendarListPage implements OnInit, OnDestroy { | |||||||
|     canCreate = false; |     canCreate = false; | ||||||
|     hasOffline = false; |     hasOffline = false; | ||||||
|     isOnline = false; |     isOnline = false; | ||||||
|     syncIcon = 'spinner'; |     syncIcon = CoreConstants.ICON_LOADING; | ||||||
|     filter: AddonCalendarFilter = { |     filter: AddonCalendarFilter = { | ||||||
|         filtered: false, |         filtered: false, | ||||||
|         courseId: -1, |         courseId: -1, | ||||||
| @ -251,7 +251,7 @@ export class AddonCalendarListPage implements OnInit, OnDestroy { | |||||||
|             this.gotoEvent(this.eventId); |             this.gotoEvent(this.eventId); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         await this.fetchData(false, true, false); |         await this.fetchData(false, true, false); | ||||||
| 
 | 
 | ||||||
| @ -361,7 +361,7 @@ export class AddonCalendarListPage implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.eventsLoaded = true; |         this.eventsLoaded = true; | ||||||
|         this.syncIcon = 'fas-sync-alt'; |         this.syncIcon = CoreConstants.ICON_SYNC; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -567,7 +567,7 @@ export class AddonCalendarListPage implements OnInit, OnDestroy { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     async refreshEvents(sync?: boolean, showErrors?: boolean): Promise<void> { |     async refreshEvents(sync?: boolean, showErrors?: boolean): Promise<void> { | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         const promises: Promise<void>[] = []; |         const promises: Promise<void>[] = []; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -49,6 +49,7 @@ import { CoreNavigator } from '@services/navigator'; | |||||||
| import { CoreIonLoadingElement } from '@classes/ion-loading'; | import { CoreIonLoadingElement } from '@classes/ion-loading'; | ||||||
| import { ActivatedRoute } from '@angular/router'; | import { ActivatedRoute } from '@angular/router'; | ||||||
| import { AddonMessagesConversationInfoComponent } from '../../components/conversation-info/conversation-info'; | import { AddonMessagesConversationInfoComponent } from '../../components/conversation-info/conversation-info'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays a message discussion page. |  * Page that displays a message discussion page. | ||||||
| @ -1352,7 +1353,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.favouriteIcon = 'spinner'; |         this.favouriteIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await AddonMessages.instance.setFavouriteConversation(this.conversation.id, !this.conversation.isfavourite); |             await AddonMessages.instance.setFavouriteConversation(this.conversation.id, !this.conversation.isfavourite); | ||||||
| @ -1386,7 +1387,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.muteIcon = 'spinner'; |         this.muteIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await AddonMessages.instance.muteConversation(this.conversation.id, !this.conversation.ismuted); |             await AddonMessages.instance.muteConversation(this.conversation.id, !this.conversation.ismuted); | ||||||
| @ -1461,7 +1462,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await CoreDomUtils.instance.showConfirm(template, undefined, okText); |             await CoreDomUtils.instance.showConfirm(template, undefined, okText); | ||||||
|             this.blockIcon = 'spinner'; |             this.blockIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); |             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); | ||||||
|             this.showLoadingModal = true; |             this.showLoadingModal = true; | ||||||
| @ -1497,7 +1498,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await CoreDomUtils.instance.showDeleteConfirm(confirmMessage); |             await CoreDomUtils.instance.showDeleteConfirm(confirmMessage); | ||||||
|             this.deleteIcon = 'spinner'; |             this.deleteIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|             try { |             try { | ||||||
|                 try { |                 try { | ||||||
| @ -1543,7 +1544,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
|         try { |         try { | ||||||
|             await CoreDomUtils.instance.showConfirm(template, undefined, okText); |             await CoreDomUtils.instance.showConfirm(template, undefined, okText); | ||||||
| 
 | 
 | ||||||
|             this.blockIcon = 'spinner'; |             this.blockIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); |             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); | ||||||
|             this.showLoadingModal = true; |             this.showLoadingModal = true; | ||||||
| @ -1582,7 +1583,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
|         try { |         try { | ||||||
|             await CoreDomUtils.instance.showConfirm(template, undefined, okText); |             await CoreDomUtils.instance.showConfirm(template, undefined, okText); | ||||||
| 
 | 
 | ||||||
|             this.addRemoveIcon = 'spinner'; |             this.addRemoveIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); |             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); | ||||||
|             this.showLoadingModal = true; |             this.showLoadingModal = true; | ||||||
| @ -1673,7 +1674,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
|         try { |         try { | ||||||
|             await CoreDomUtils.instance.showConfirm(template, undefined, okText); |             await CoreDomUtils.instance.showConfirm(template, undefined, okText); | ||||||
| 
 | 
 | ||||||
|             this.addRemoveIcon = 'spinner'; |             this.addRemoveIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); |             const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); | ||||||
|             this.showLoadingModal = true; |             this.showLoadingModal = true; | ||||||
|  | |||||||
							
								
								
									
										28
									
								
								src/addons/mod/book/book-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/addons/mod/book/book-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | |||||||
|  | // (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 { RouterModule, Routes } from '@angular/router'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: ':courseId/:cmdId', | ||||||
|  |         loadChildren: () => import('./pages/index/index.module').then( m => m.AddonModBookIndexPageModule), | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [RouterModule.forChild(routes)], | ||||||
|  | }) | ||||||
|  | export class AddonModBookLazyModule {} | ||||||
							
								
								
									
										57
									
								
								src/addons/mod/book/book.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/addons/mod/book/book.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | // (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 { Routes } from '@angular/router'; | ||||||
|  | import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; | ||||||
|  | import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||||
|  | import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; | ||||||
|  | import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; | ||||||
|  | import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate'; | ||||||
|  | import { AddonModBookComponentsModule } from './components/components.module'; | ||||||
|  | import { AddonModBookModuleHandler, AddonModBookModuleHandlerService } from './services/handlers/module'; | ||||||
|  | import { AddonModBookIndexLinkHandler } from './services/handlers/index-link'; | ||||||
|  | import { AddonModBookListLinkHandler } from './services/handlers/list-link'; | ||||||
|  | import { AddonModBookPrefetchHandler } from './services/handlers/prefetch'; | ||||||
|  | import { AddonModBookTagAreaHandler } from './services/handlers/tag-area'; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: AddonModBookModuleHandlerService.PAGE_NAME, | ||||||
|  |         loadChildren: () => import('./book-lazy.module').then(m => m.AddonModBookLazyModule), | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [ | ||||||
|  |         CoreMainMenuTabRoutingModule.forChild(routes), | ||||||
|  |         AddonModBookComponentsModule, | ||||||
|  |     ], | ||||||
|  |     providers: [ | ||||||
|  |         { | ||||||
|  |             provide: APP_INITIALIZER, | ||||||
|  |             multi: true, | ||||||
|  |             deps: [], | ||||||
|  |             useFactory: () => () => { | ||||||
|  |                 CoreCourseModuleDelegate.instance.registerHandler(AddonModBookModuleHandler.instance); | ||||||
|  |                 CoreContentLinksDelegate.instance.registerHandler(AddonModBookIndexLinkHandler.instance); | ||||||
|  |                 CoreContentLinksDelegate.instance.registerHandler(AddonModBookListLinkHandler.instance); | ||||||
|  |                 CoreCourseModulePrefetchDelegate.instance.registerHandler(AddonModBookPrefetchHandler.instance); | ||||||
|  |                 CoreTagAreaDelegate.instance.registerHandler(AddonModBookTagAreaHandler.instance); | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |     ], | ||||||
|  | }) | ||||||
|  | export class AddonModBookModule {} | ||||||
							
								
								
									
										47
									
								
								src/addons/mod/book/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/addons/mod/book/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | // (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 { FormsModule } from '@angular/forms'; | ||||||
|  | import { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | 
 | ||||||
|  | import { CoreSharedModule } from '@/core/shared.module'; | ||||||
|  | import { CoreCourseComponentsModule } from '@features/course/components/components.module'; | ||||||
|  | import { CoreTagComponentsModule } from '@features/tag/components/components.module'; | ||||||
|  | 
 | ||||||
|  | import { AddonModBookIndexComponent } from './index/index'; | ||||||
|  | import { AddonModBookTocComponent } from './toc/toc'; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     declarations: [ | ||||||
|  |         AddonModBookIndexComponent, | ||||||
|  |         AddonModBookTocComponent, | ||||||
|  |     ], | ||||||
|  |     imports: [ | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         FormsModule, | ||||||
|  |         CoreSharedModule, | ||||||
|  |         CoreCourseComponentsModule, | ||||||
|  |         CoreTagComponentsModule, | ||||||
|  |     ], | ||||||
|  |     exports: [ | ||||||
|  |         AddonModBookIndexComponent, | ||||||
|  |         AddonModBookTocComponent, | ||||||
|  |     ], | ||||||
|  | }) | ||||||
|  | export class AddonModBookComponentsModule {} | ||||||
| @ -0,0 +1,52 @@ | |||||||
|  | <!-- Buttons to add to the header. --> | ||||||
|  | <core-navbar-buttons slot="end"> | ||||||
|  |     <ion-button (click)="showToc()" [attr.aria-label]="'addon.mod_book.toc' | translate" aria-haspopup="true" *ngIf="loaded"> | ||||||
|  |         <ion-icon name="fas-bookmark" slot="icon-only"></ion-icon> | ||||||
|  |     </ion-button> | ||||||
|  |     <core-context-menu> | ||||||
|  |         <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" | ||||||
|  |             [href]="externalUrl" iconAction="fas-external-link-alt"></core-context-menu-item> | ||||||
|  |         <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" | ||||||
|  |             (action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item> | ||||||
|  |         <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" | ||||||
|  |             iconAction="far-newspaper" (action)="gotoBlog()"></core-context-menu-item> | ||||||
|  |         <core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" | ||||||
|  |             [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item> | ||||||
|  |         <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)" | ||||||
|  |             [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item> | ||||||
|  |         <core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}" | ||||||
|  |             iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false"> | ||||||
|  |         </core-context-menu-item> | ||||||
|  |     </core-context-menu> | ||||||
|  | </core-navbar-buttons> | ||||||
|  | 
 | ||||||
|  | <!-- Content. --> | ||||||
|  | <core-loading [hideUntil]="loaded" class="core-loading-center"> | ||||||
|  | 
 | ||||||
|  |     <core-course-module-description [description]="description" [component]="component" [componentId]="componentId" | ||||||
|  |         contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"></core-course-module-description> | ||||||
|  | 
 | ||||||
|  |     <ion-card class="core-warning-card" *ngIf="warning"> | ||||||
|  |         <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon> | ||||||
|  |         <span [innerHTML]="warning"></span> | ||||||
|  |     </ion-card> | ||||||
|  | 
 | ||||||
|  |     <div class="ion-padding safe-padding-horizontal"> | ||||||
|  |         <core-navigation-bar *ngIf="displayNavBar" [previous]="previousChapter?.id" | ||||||
|  |             [previousTitle]="previousNavBarTitle" [next]="nextChapter?.id" [nextTitle]="nextNavBarTitle" | ||||||
|  |             (action)="changeChapter($event)"> | ||||||
|  |         </core-navigation-bar> | ||||||
|  | 
 | ||||||
|  |         <core-format-text [component]="component" [componentId]="componentId" [text]="chapterContent" contextLevel="module" | ||||||
|  |             [contextInstanceId]="module?.id" [courseId]="courseId"></core-format-text> | ||||||
|  |         <div class="ion-margin-top" *ngIf="tagsEnabled && tags?.length > 0"> | ||||||
|  |             <strong>{{ 'core.tag.tags' | translate }}: </strong> | ||||||
|  |             <core-tag-list [tags]="tags"></core-tag-list> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <core-navigation-bar *ngIf="displayNavBar" [previous]="previousChapter?.id" | ||||||
|  |             [previousTitle]="previousNavBarTitle" [next]="nextChapter?.id" [nextTitle]="nextNavBarTitle" | ||||||
|  |             (action)="changeChapter($event)"></core-navigation-bar> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  | </core-loading> | ||||||
							
								
								
									
										251
									
								
								src/addons/mod/book/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								src/addons/mod/book/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,251 @@ | |||||||
|  | // (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, Optional, Input, OnInit } from '@angular/core'; | ||||||
|  | import { IonContent } from '@ionic/angular'; | ||||||
|  | import { | ||||||
|  |     CoreCourseModuleMainResourceComponent, CoreCourseResourceDownloadResult, | ||||||
|  | } from '@features/course/classes/main-resource-component'; | ||||||
|  | import { | ||||||
|  |     AddonModBookProvider, | ||||||
|  |     AddonModBookContentsMap, | ||||||
|  |     AddonModBookTocChapter, | ||||||
|  |     AddonModBookNavStyle, | ||||||
|  |     AddonModBook, | ||||||
|  |     AddonModBookBookWSData, | ||||||
|  | } from '../../services/book'; | ||||||
|  | import { CoreTag, CoreTagItem } from '@features/tag/services/tag'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | ||||||
|  | import { ModalController, Translate } from '@singletons'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreCourse } from '@features/course/services/course'; | ||||||
|  | import { AddonModBookTocComponent } from '../toc/toc'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Component that displays a book. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'addon-mod-book-index', | ||||||
|  |     templateUrl: 'addon-mod-book-index.html', | ||||||
|  | }) | ||||||
|  | export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { | ||||||
|  | 
 | ||||||
|  |     @Input() initialChapterId?: number; // The initial chapter ID to load.
 | ||||||
|  | 
 | ||||||
|  |     component = AddonModBookProvider.COMPONENT; | ||||||
|  |     chapterContent?: string; | ||||||
|  |     previousChapter?: AddonModBookTocChapter; | ||||||
|  |     nextChapter?: AddonModBookTocChapter; | ||||||
|  |     tagsEnabled = false; | ||||||
|  |     displayNavBar = true; | ||||||
|  |     previousNavBarTitle?: string; | ||||||
|  |     nextNavBarTitle?: string; | ||||||
|  |     warning = ''; | ||||||
|  |     tags?: CoreTagItem[]; | ||||||
|  | 
 | ||||||
|  |     protected chapters: AddonModBookTocChapter[] = []; | ||||||
|  |     protected currentChapter?: number; | ||||||
|  |     protected book?: AddonModBookBookWSData; | ||||||
|  |     protected displayTitlesInNavBar = false; | ||||||
|  |     protected contentsMap: AddonModBookContentsMap = {}; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         protected content?: IonContent, | ||||||
|  |         @Optional() courseContentsPage?: CoreCourseContentsPage, | ||||||
|  |     ) { | ||||||
|  |         super('AddonModBookIndexComponent', courseContentsPage); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     async ngOnInit(): Promise<void> { | ||||||
|  |         super.ngOnInit(); | ||||||
|  | 
 | ||||||
|  |         this.tagsEnabled = CoreTag.instance.areTagsAvailableInSite(); | ||||||
|  |         this.loadContent(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Show the TOC. | ||||||
|  |      */ | ||||||
|  |     async showToc(): Promise<void> { | ||||||
|  |         // Create the toc modal.
 | ||||||
|  |         const modal = await ModalController.instance.create({ | ||||||
|  |             component: AddonModBookTocComponent, | ||||||
|  |             componentProps: { | ||||||
|  |                 moduleId: this.module!.id, | ||||||
|  |                 chapters: this.chapters, | ||||||
|  |                 selected: this.currentChapter, | ||||||
|  |                 courseId: this.courseId, | ||||||
|  |                 book: this.book, | ||||||
|  |             }, | ||||||
|  |             cssClass: 'core-modal-lateral', | ||||||
|  |             showBackdrop: true, | ||||||
|  |             backdropDismiss: true, | ||||||
|  |             // @todo enterAnimation: 'core-modal-lateral-transition',
 | ||||||
|  |             // @todo leaveAnimation: 'core-modal-lateral-transition',
 | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         await modal.present(); | ||||||
|  | 
 | ||||||
|  |         const result = await modal.onDidDismiss(); | ||||||
|  | 
 | ||||||
|  |         if (result.data) { | ||||||
|  |             this.changeChapter(result.data); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Change the current chapter. | ||||||
|  |      * | ||||||
|  |      * @param chapterId Chapter to load. | ||||||
|  |      * @return Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     changeChapter(chapterId: number): void { | ||||||
|  |         if (chapterId && chapterId != this.currentChapter) { | ||||||
|  |             this.loaded = false; | ||||||
|  |             this.refreshIcon = CoreConstants.ICON_LOADING; | ||||||
|  |             this.loadChapter(chapterId, true); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Perform the invalidate content function. | ||||||
|  |      * | ||||||
|  |      * @return Resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected invalidateContent(): Promise<void> { | ||||||
|  |         return AddonModBook.instance.invalidateContent(this.module!.id, this.courseId!); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Download book contents and load the current chapter. | ||||||
|  |      * | ||||||
|  |      * @param refresh Whether we're refreshing data. | ||||||
|  |      * @return Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected async fetchContent(refresh = false): Promise<void> { | ||||||
|  |         const promises: Promise<void>[] = []; | ||||||
|  |         let downloadResult: CoreCourseResourceDownloadResult | undefined; | ||||||
|  | 
 | ||||||
|  |         // Try to get the book data. Ignore errors since this WS isn't available in some Moodle versions.
 | ||||||
|  |         promises.push(CoreUtils.instance.ignoreErrors(AddonModBook.instance.getBook(this.courseId!, this.module!.id)) | ||||||
|  |             .then((book) => { | ||||||
|  |                 if (!book) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 this.book = book; | ||||||
|  |                 this.dataRetrieved.emit(book); | ||||||
|  | 
 | ||||||
|  |                 this.description = book.intro; | ||||||
|  |                 this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY; | ||||||
|  |                 this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT; | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         // Get module status to determine if it needs to be downloaded.
 | ||||||
|  |         promises.push(this.downloadResourceIfNeeded(refresh).then((result) => { | ||||||
|  |             downloadResult = result; | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             await Promise.all(promises); | ||||||
|  | 
 | ||||||
|  |             this.contentsMap = AddonModBook.instance.getContentsMap(this.module!.contents); | ||||||
|  |             this.chapters = AddonModBook.instance.getTocList(this.module!.contents); | ||||||
|  | 
 | ||||||
|  |             if (typeof this.currentChapter == 'undefined' && typeof this.initialChapterId != 'undefined' && this.chapters) { | ||||||
|  |                 // Initial chapter set. Validate that the chapter exists.
 | ||||||
|  |                 const chapter = this.chapters.find((chapter) => chapter.id == this.initialChapterId); | ||||||
|  | 
 | ||||||
|  |                 if (chapter) { | ||||||
|  |                     this.currentChapter = this.initialChapterId; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (typeof this.currentChapter == 'undefined') { | ||||||
|  |                 // Load the first chapter.
 | ||||||
|  |                 this.currentChapter = AddonModBook.instance.getFirstChapter(this.chapters); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Show chapter.
 | ||||||
|  |             try { | ||||||
|  |                 await this.loadChapter(this.currentChapter!, refresh); | ||||||
|  | 
 | ||||||
|  |                 this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : ''; | ||||||
|  |             } catch { | ||||||
|  |                 // Ignore errors, they're handled inside the loadChapter function.
 | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             this.fillContextMenu(refresh); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Load a book chapter. | ||||||
|  |      * | ||||||
|  |      * @param chapterId Chapter to load. | ||||||
|  |      * @param logChapterId Whether chapter ID should be passed to the log view function. | ||||||
|  |      * @return Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected async loadChapter(chapterId: number, logChapterId: boolean): Promise<void> { | ||||||
|  |         this.currentChapter = chapterId; | ||||||
|  |         this.content?.scrollToTop(); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             const content = await AddonModBook.instance.getChapterContent(this.contentsMap, chapterId, this.module!.id); | ||||||
|  | 
 | ||||||
|  |             this.tags = this.tagsEnabled ? this.contentsMap[this.currentChapter].tags : []; | ||||||
|  | 
 | ||||||
|  |             this.chapterContent = content; | ||||||
|  |             this.previousChapter = AddonModBook.instance.getPreviousChapter(this.chapters, chapterId); | ||||||
|  |             this.nextChapter = AddonModBook.instance.getNextChapter(this.chapters, chapterId); | ||||||
|  | 
 | ||||||
|  |             this.previousNavBarTitle = this.previousChapter && this.displayTitlesInNavBar | ||||||
|  |                 ? Translate.instance.instant('addon.mod_book.navprevtitle', { $a: this.previousChapter.title }) | ||||||
|  |                 : ''; | ||||||
|  |             this.nextNavBarTitle = this.nextChapter && this.displayTitlesInNavBar | ||||||
|  |                 ? Translate.instance.instant('addon.mod_book.navnexttitle', { $a: this.nextChapter.title }) | ||||||
|  |                 : ''; | ||||||
|  | 
 | ||||||
|  |             // Chapter loaded, log view. We don't return the promise because we don't want to block the user for this.
 | ||||||
|  |             await CoreUtils.instance.ignoreErrors(AddonModBook.instance.logView( | ||||||
|  |                 this.module!.instance!, | ||||||
|  |                 logChapterId ? chapterId : undefined, | ||||||
|  |                 this.module!.name, | ||||||
|  |             )); | ||||||
|  | 
 | ||||||
|  |             // Module is completed when last chapter is viewed, so we only check completion if the last is reached.
 | ||||||
|  |             if (!this.nextChapter) { | ||||||
|  |                 CoreCourse.instance.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||||
|  |             } | ||||||
|  |         } catch (error) { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_book.errorchapter', true); | ||||||
|  | 
 | ||||||
|  |             throw error; | ||||||
|  |         } finally { | ||||||
|  |             this.loaded = true; | ||||||
|  |             this.refreshIcon = CoreConstants.ICON_REFRESH; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								src/addons/mod/book/components/toc/toc.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/addons/mod/book/components/toc/toc.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | <ion-header> | ||||||
|  |     <ion-toolbar> | ||||||
|  |         <ion-buttons slot="start"> | ||||||
|  |             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||||
|  |         </ion-buttons> | ||||||
|  |         <ion-title>{{ 'addon.mod_book.toc' | translate }}</ion-title> | ||||||
|  |         <ion-buttons slot="end"> | ||||||
|  |             <ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate"> | ||||||
|  |                 <ion-icon name="fas-times" slot="icon-only"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |         </ion-buttons> | ||||||
|  |     </ion-toolbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <nav> | ||||||
|  |         <ion-list> | ||||||
|  |             <ion-item class="ion-text-wrap" *ngFor="let chapter of chapters" (click)="loadChapter(chapter.id)" | ||||||
|  |                 [class.core-nav-item-selected]="selected == chapter.id" | ||||||
|  |                 [class.item-dimmed]="chapter.hidden"> | ||||||
|  |                 <ion-label> | ||||||
|  |                     <p [class.ion-padding-left]="addPadding && chapter.level == 1 ? true : null"> | ||||||
|  |                         <span *ngIf="showNumbers" class="addon-mod-book-number">{{chapter.indexNumber}}</span> | ||||||
|  |                         <span *ngIf="showBullets" class="addon-mod-book-bullet">•</span> | ||||||
|  |                         <core-format-text [text]="chapter.title" contextLevel="module" [contextInstanceId]="moduleId" | ||||||
|  |                             [courseId]="courseId"> | ||||||
|  |                         </core-format-text> | ||||||
|  |                     </p> | ||||||
|  |                 </ion-label> | ||||||
|  |             </ion-item> | ||||||
|  |         </ion-list> | ||||||
|  |     </nav> | ||||||
|  | </ion-content> | ||||||
							
								
								
									
										5
									
								
								src/addons/mod/book/components/toc/toc.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/addons/mod/book/components/toc/toc.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | .addon-mod-book-bullet { | ||||||
|  |     font-weight: bold; | ||||||
|  |     font-size: 1.5em; | ||||||
|  |     margin-right: 3px; | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								src/addons/mod/book/components/toc/toc.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/addons/mod/book/components/toc/toc.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | // (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, OnInit } from '@angular/core'; | ||||||
|  | import { ModalController } from '@singletons'; | ||||||
|  | import { AddonModBookTocChapter, AddonModBookBookWSData, AddonModBookNumbering } from '../../services/book'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Modal to display the TOC of a book. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'addon-mod-book-toc', | ||||||
|  |     templateUrl: 'toc.html', | ||||||
|  |     styleUrls: ['toc.scss'], | ||||||
|  | }) | ||||||
|  | export class AddonModBookTocComponent implements OnInit { | ||||||
|  | 
 | ||||||
|  |     @Input() moduleId?: number; | ||||||
|  |     @Input() chapters: AddonModBookTocChapter[] = []; | ||||||
|  |     @Input() selected?: number; | ||||||
|  |     @Input() courseId?: number; | ||||||
|  |     showNumbers = true; | ||||||
|  |     addPadding = true; | ||||||
|  |     showBullets = false; | ||||||
|  | 
 | ||||||
|  |     @Input() protected book?: AddonModBookBookWSData; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component loaded. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  |         if (this.book) { | ||||||
|  |             this.showNumbers = this.book.numbering == AddonModBookNumbering.NUMBERS; | ||||||
|  |             this.showBullets = this.book.numbering == AddonModBookNumbering.BULLETS; | ||||||
|  |             this.addPadding = this.book.numbering != AddonModBookNumbering.NONE; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Function called when a course is clicked. | ||||||
|  |      * | ||||||
|  |      * @param id ID of the clicked chapter. | ||||||
|  |      */ | ||||||
|  |     loadChapter(id: number): void { | ||||||
|  |         ModalController.instance.dismiss(id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Close modal. | ||||||
|  |      */ | ||||||
|  |     closeModal(): void { | ||||||
|  |         ModalController.instance.dismiss(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								src/addons/mod/book/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/addons/mod/book/lang.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | { | ||||||
|  |     "errorchapter": "Error reading chapter of book.", | ||||||
|  |     "modulenameplural": "Books", | ||||||
|  |     "navnexttitle": "Next: {{$a}}", | ||||||
|  |     "navprevtitle": "Previous: {{$a}}", | ||||||
|  |     "tagarea_book_chapters": "Book chapters", | ||||||
|  |     "toc": "Table of contents" | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/addons/mod/book/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/addons/mod/book/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | <ion-header> | ||||||
|  |     <ion-toolbar> | ||||||
|  |         <ion-buttons slot="start"> | ||||||
|  |             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||||
|  |         </ion-buttons> | ||||||
|  |         <ion-title> | ||||||
|  |             <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||||
|  |             </core-format-text> | ||||||
|  |         </ion-title> | ||||||
|  |         <ion-buttons slot="end"> | ||||||
|  |             <!-- The buttons defined by the component will be added in here. --> | ||||||
|  |         </ion-buttons> | ||||||
|  |     </ion-toolbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <ion-refresher slot="fixed" [disabled]="!bookComponent?.loaded" (ionRefresh)="bookComponent?.doRefresh($event)"> | ||||||
|  |         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |     </ion-refresher> | ||||||
|  | 
 | ||||||
|  |     <addon-mod-book-index [module]="module" [courseId]="courseId" [initialChapterId]="chapterId" | ||||||
|  |         (dataRetrieved)="updateData($event)"> | ||||||
|  |     </addon-mod-book-index> | ||||||
|  | </ion-content> | ||||||
							
								
								
									
										46
									
								
								src/addons/mod/book/pages/index/index.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/addons/mod/book/pages/index/index.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | // (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 { RouterModule, Routes } from '@angular/router'; | ||||||
|  | import { CommonModule } from '@angular/common'; | ||||||
|  | import { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | 
 | ||||||
|  | import { CoreSharedModule } from '@/core/shared.module'; | ||||||
|  | import { AddonModBookComponentsModule } from '../../components/components.module'; | ||||||
|  | import { AddonModBookIndexPage } from './index'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         component: AddonModBookIndexPage, | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreSharedModule, | ||||||
|  |         AddonModBookComponentsModule, | ||||||
|  |     ], | ||||||
|  |     declarations: [ | ||||||
|  |         AddonModBookIndexPage, | ||||||
|  |     ], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class AddonModBookIndexPageModule {} | ||||||
							
								
								
									
										57
									
								
								src/addons/mod/book/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/addons/mod/book/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||||
|  | //
 | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||||
|  | // you may not use this file except in compliance with the License.
 | ||||||
|  | // You may obtain a copy of the License at
 | ||||||
|  | //
 | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | //
 | ||||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||||
|  | // See the License for the specific language governing permissions and
 | ||||||
|  | // limitations under the License.
 | ||||||
|  | 
 | ||||||
|  | import { Component, OnInit, ViewChild } from '@angular/core'; | ||||||
|  | import { CoreCourseWSModule } from '@features/course/services/course'; | ||||||
|  | import { CoreNavigator } from '@services/navigator'; | ||||||
|  | import { AddonModBookIndexComponent } from '../../components/index/index'; | ||||||
|  | import { AddonModBookBookWSData } from '../../services/book'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Page that displays a book. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'page-addon-mod-book-index', | ||||||
|  |     templateUrl: 'index.html', | ||||||
|  | }) | ||||||
|  | export class AddonModBookIndexPage implements OnInit { | ||||||
|  | 
 | ||||||
|  |     @ViewChild(AddonModBookIndexComponent) bookComponent?: AddonModBookIndexComponent; | ||||||
|  | 
 | ||||||
|  |     title?: string; | ||||||
|  |     module?: CoreCourseWSModule; | ||||||
|  |     courseId?: number; | ||||||
|  |     chapterId?: number; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  |         this.module = CoreNavigator.instance.getRouteParam('module'); | ||||||
|  |         this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId'); | ||||||
|  |         this.chapterId = CoreNavigator.instance.getRouteNumberParam('chapterId'); | ||||||
|  |         this.title = this.module?.name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Update some data based on the book instance. | ||||||
|  |      * | ||||||
|  |      * @param book Book instance. | ||||||
|  |      */ | ||||||
|  |     updateData(book: AddonModBookBookWSData): void { | ||||||
|  |         this.title = book.name || this.title; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										479
									
								
								src/addons/mod/book/services/book.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								src/addons/mod/book/services/book.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,479 @@ | |||||||
|  | // (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 { CoreSites, CoreSitesCommonWSOptions } from '@services/sites'; | ||||||
|  | import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||||
|  | import { CoreTagItem } from '@features/tag/services/tag'; | ||||||
|  | import { CoreWSExternalWarning, CoreWSExternalFile, CoreWS } from '@services/ws'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreCourseLogHelper } from '@features/course/services/log-helper'; | ||||||
|  | import { CoreCourse, CoreCourseModuleContentFile } from '@features/course/services/course'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreFilepool } from '@services/filepool'; | ||||||
|  | import { CoreTextUtils } from '@services/utils/text'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { CoreFile } from '@services/file'; | ||||||
|  | import { CoreWSError } from '@classes/errors/wserror'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Constants to define how the chapters and subchapters of a book should be displayed in that table of contents. | ||||||
|  |  */ | ||||||
|  | export const enum AddonModBookNumbering { | ||||||
|  |     NONE = 0, | ||||||
|  |     NUMBERS = 1, | ||||||
|  |     BULLETS = 2, | ||||||
|  |     INDENTED = 3, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Constants to define the navigation style used within a book. | ||||||
|  |  */ | ||||||
|  | export const enum AddonModBookNavStyle { | ||||||
|  |     TOC_ONLY = 0, | ||||||
|  |     IMAGE = 1, | ||||||
|  |     TEXT = 2, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ROOT_CACHE_KEY = 'mmaModBook:'; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Service that provides some features for books. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class AddonModBookProvider { | ||||||
|  | 
 | ||||||
|  |     static readonly COMPONENT = 'mmaModBook'; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get a book by course module ID. | ||||||
|  |      * | ||||||
|  |      * @param courseId Course ID. | ||||||
|  |      * @param cmId Course module ID. | ||||||
|  |      * @param options Other options. | ||||||
|  |      * @return Promise resolved when the book is retrieved. | ||||||
|  |      */ | ||||||
|  |     getBook(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModBookBookWSData> { | ||||||
|  |         return this.getBookByField(courseId, 'coursemodule', cmId, options); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get a book with key=value. If more than one is found, only the first will be returned. | ||||||
|  |      * | ||||||
|  |      * @param courseId Course ID. | ||||||
|  |      * @param key Name of the property to check. | ||||||
|  |      * @param value Value to search. | ||||||
|  |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved when the book is retrieved. | ||||||
|  |      */ | ||||||
|  |     protected async getBookByField( | ||||||
|  |         courseId: number, | ||||||
|  |         key: string, | ||||||
|  |         value: number, | ||||||
|  |         options: CoreSitesCommonWSOptions = {}, | ||||||
|  |     ): Promise<AddonModBookBookWSData> { | ||||||
|  | 
 | ||||||
|  |         const site = await CoreSites.instance.getSite(options.siteId); | ||||||
|  |         const params: AddonModBookGetBooksByCoursesWSParams = { | ||||||
|  |             courseids: [courseId], | ||||||
|  |         }; | ||||||
|  |         const preSets: CoreSiteWSPreSets = { | ||||||
|  |             cacheKey: this.getBookDataCacheKey(courseId), | ||||||
|  |             updateFrequency: CoreSite.FREQUENCY_RARELY, | ||||||
|  |             component: AddonModBookProvider.COMPONENT, | ||||||
|  |             ...CoreSites.instance.getReadingStrategyPreSets(options.readingStrategy), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         const response: AddonModBookGetBooksByCoursesWSResponse = await site.read('mod_book_get_books_by_courses', params, preSets); | ||||||
|  | 
 | ||||||
|  |         // Search the book.
 | ||||||
|  |         const book = response.books.find((book) => book[key] == value); | ||||||
|  |         if (book) { | ||||||
|  |             return book; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         throw new CoreWSError('Book not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get cache key for get book data WS calls. | ||||||
|  |      * | ||||||
|  |      * @param courseId Course ID. | ||||||
|  |      * @return Cache key. | ||||||
|  |      */ | ||||||
|  |     protected getBookDataCacheKey(courseId: number): string { | ||||||
|  |         return ROOT_CACHE_KEY + 'book:' + courseId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets a chapter contents. | ||||||
|  |      * | ||||||
|  |      * @param contentsMap Contents map returned by getContentsMap. | ||||||
|  |      * @param chapterId Chapter to retrieve. | ||||||
|  |      * @param moduleId The module ID. | ||||||
|  |      * @return Promise resolved with the contents. | ||||||
|  |      */ | ||||||
|  |     async getChapterContent(contentsMap: AddonModBookContentsMap, chapterId: number, moduleId: number): Promise<string> { | ||||||
|  | 
 | ||||||
|  |         const indexUrl = contentsMap[chapterId] ? contentsMap[chapterId].indexUrl : undefined; | ||||||
|  |         if (!indexUrl) { | ||||||
|  |             // It shouldn't happen.
 | ||||||
|  |             throw new CoreWSError('Could not locate the index chapter.'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!CoreFile.instance.isAvailable()) { | ||||||
|  |             // We return the live URL.
 | ||||||
|  |             return CoreSites.instance.getCurrentSite()!.checkAndFixPluginfileURL(indexUrl); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const siteId = CoreSites.instance.getCurrentSiteId(); | ||||||
|  | 
 | ||||||
|  |         const url = await CoreFilepool.instance.downloadUrl(siteId, indexUrl, false, AddonModBookProvider.COMPONENT, moduleId); | ||||||
|  | 
 | ||||||
|  |         const content = await CoreWS.instance.getText(url); | ||||||
|  | 
 | ||||||
|  |         // Now that we have the content, we update the SRC to point back to the external resource.
 | ||||||
|  |         return CoreDomUtils.instance.restoreSourcesInHtml(content, contentsMap[chapterId].paths); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Convert an array of book contents into an object where contents are organized in chapters. | ||||||
|  |      * Each chapter has an indexUrl and the list of contents in that chapter. | ||||||
|  |      * | ||||||
|  |      * @param contents The module contents. | ||||||
|  |      * @return Contents map. | ||||||
|  |      */ | ||||||
|  |     getContentsMap(contents: CoreCourseModuleContentFile[]): AddonModBookContentsMap { | ||||||
|  |         const map: AddonModBookContentsMap = {}; | ||||||
|  | 
 | ||||||
|  |         if (!contents) { | ||||||
|  |             return map; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         contents.forEach((content) => { | ||||||
|  |             if (!this.isFileDownloadable(content)) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Search the chapter number in the filepath.
 | ||||||
|  |             const matches = content.filepath.match(/\/(\d+)\//); | ||||||
|  |             if (!matches || !matches[1]) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             let key: string; | ||||||
|  |             const chapter: string = matches[1]; | ||||||
|  |             const filepathIsChapter = content.filepath == '/' + chapter + '/'; | ||||||
|  | 
 | ||||||
|  |             // Init the chapter if it's not defined yet.
 | ||||||
|  |             map[chapter] = map[chapter] || { paths: {} }; | ||||||
|  | 
 | ||||||
|  |             if (content.filename == 'index.html' && filepathIsChapter) { | ||||||
|  |                 // Index of the chapter, set indexUrl and tags of the chapter.
 | ||||||
|  |                 map[chapter].indexUrl = content.fileurl; | ||||||
|  |                 map[chapter].tags = content.tags; | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (filepathIsChapter) { | ||||||
|  |                 // It's a file in the root folder OR the WS isn't returning the filepath as it should (MDL-53671).
 | ||||||
|  |                 // Try to get the path to the file from the URL.
 | ||||||
|  |                 const split = content.fileurl.split('mod_book/chapter' + content.filepath); | ||||||
|  |                 key = split[1] || content.filename; // Use filename if we couldn't find the path.
 | ||||||
|  |             } else { | ||||||
|  |                 // Remove the chapter folder from the path and add the filename.
 | ||||||
|  |                 key = content.filepath.replace('/' + chapter + '/', '') + content.filename; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             map[chapter].paths[CoreTextUtils.instance.decodeURIComponent(key)] = content.fileurl; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return map; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the first chapter of a book. | ||||||
|  |      * | ||||||
|  |      * @param chapters The chapters list. | ||||||
|  |      * @return The chapter id. | ||||||
|  |      */ | ||||||
|  |     getFirstChapter(chapters: AddonModBookTocChapter[]): number | undefined { | ||||||
|  |         if (!chapters || !chapters.length) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return chapters[0].id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the next chapter to the given one. | ||||||
|  |      * | ||||||
|  |      * @param chapters The chapters list. | ||||||
|  |      * @param chapterId The current chapter. | ||||||
|  |      * @return The next chapter. | ||||||
|  |      */ | ||||||
|  |     getNextChapter(chapters: AddonModBookTocChapter[], chapterId: number): AddonModBookTocChapter | undefined { | ||||||
|  |         const currentChapterIndex = chapters.findIndex((chapter) => chapter.id == chapterId); | ||||||
|  | 
 | ||||||
|  |         if (currentChapterIndex >= 0 && typeof chapters[currentChapterIndex + 1] != 'undefined') { | ||||||
|  |             return chapters[currentChapterIndex + 1]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the previous chapter to the given one. | ||||||
|  |      * | ||||||
|  |      * @param chapters The chapters list. | ||||||
|  |      * @param chapterId The current chapter. | ||||||
|  |      * @return The next chapter. | ||||||
|  |      */ | ||||||
|  |     getPreviousChapter(chapters: AddonModBookTocChapter[], chapterId: number): AddonModBookTocChapter | undefined { | ||||||
|  |         const currentChapterIndex = chapters.findIndex((chapter) => chapter.id == chapterId); | ||||||
|  | 
 | ||||||
|  |         if (currentChapterIndex > 0) { | ||||||
|  |             return chapters[currentChapterIndex - 1]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the book toc as an array. | ||||||
|  |      * | ||||||
|  |      * @param contents The module contents. | ||||||
|  |      * @return The toc. | ||||||
|  |      */ | ||||||
|  |     getToc(contents: CoreCourseModuleContentFile[]): AddonModBookTocChapterParsed[] { | ||||||
|  |         if (!contents || !contents.length || typeof contents[0].content == 'undefined') { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return CoreTextUtils.instance.parseJSON(contents[0].content, []); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the book toc as an array of chapters (not nested). | ||||||
|  |      * | ||||||
|  |      * @param contents The module contents. | ||||||
|  |      * @return The toc as a list. | ||||||
|  |      */ | ||||||
|  |     getTocList(contents: CoreCourseModuleContentFile[]): AddonModBookTocChapter[] { | ||||||
|  |         // Convenience function to get chapter info.
 | ||||||
|  |         const getChapterInfo = ( | ||||||
|  |             chapter: AddonModBookTocChapterParsed, | ||||||
|  |             chapterNumber: number, | ||||||
|  |             previousNumber: string = '', | ||||||
|  |         ): AddonModBookTocChapter => { | ||||||
|  |             const hidden = !!parseInt(chapter.hidden, 10); | ||||||
|  | 
 | ||||||
|  |             const fullChapterNumber = previousNumber + (hidden ? 'x.' : chapterNumber + '.'); | ||||||
|  | 
 | ||||||
|  |             return { | ||||||
|  |                 id: parseInt(chapter.href.replace('/index.html', ''), 10), | ||||||
|  |                 title: chapter.title, | ||||||
|  |                 level: chapter.level, | ||||||
|  |                 indexNumber: fullChapterNumber, | ||||||
|  |                 hidden: hidden, | ||||||
|  |             }; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         const chapters: AddonModBookTocChapter[] = []; | ||||||
|  |         const toc = this.getToc(contents); | ||||||
|  | 
 | ||||||
|  |         let chapterNumber = 1; | ||||||
|  |         toc.forEach((chapter) => { | ||||||
|  |             const tocChapter = getChapterInfo(chapter, chapterNumber); | ||||||
|  | 
 | ||||||
|  |             // Add the chapter to the list.
 | ||||||
|  |             chapters.push(tocChapter); | ||||||
|  | 
 | ||||||
|  |             if (chapter.subitems) { | ||||||
|  |                 let subChapterNumber = 1; | ||||||
|  |                 // Add all the subchapters to the list.
 | ||||||
|  |                 chapter.subitems.forEach((subChapter) => { | ||||||
|  |                     chapters.push(getChapterInfo(subChapter, subChapterNumber, tocChapter.indexNumber)); | ||||||
|  |                     subChapterNumber++; | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             chapterNumber++; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return chapters; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Invalidates book data. | ||||||
|  |      * | ||||||
|  |      * @param courseId Course ID. | ||||||
|  |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved when the data is invalidated. | ||||||
|  |      */ | ||||||
|  |     async invalidateBookData(courseId: number, siteId?: string): Promise<void> { | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  | 
 | ||||||
|  |         await site.invalidateWsCacheForKey(this.getBookDataCacheKey(courseId)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Invalidate the prefetched content. | ||||||
|  |      * | ||||||
|  |      * @param moduleId The module ID. | ||||||
|  |      * @param courseId Course ID of the module. | ||||||
|  |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved when the data is invalidated. | ||||||
|  |      */ | ||||||
|  |     invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<void> { | ||||||
|  |         siteId = siteId || CoreSites.instance.getCurrentSiteId(); | ||||||
|  | 
 | ||||||
|  |         const promises: Promise<void>[] = []; | ||||||
|  | 
 | ||||||
|  |         promises.push(this.invalidateBookData(courseId, siteId)); | ||||||
|  |         promises.push(CoreFilepool.instance.invalidateFilesByComponent(siteId, AddonModBookProvider.COMPONENT, moduleId)); | ||||||
|  |         promises.push(CoreCourse.instance.invalidateModule(moduleId, siteId)); | ||||||
|  | 
 | ||||||
|  |         return CoreUtils.instance.allPromises(promises); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if a file is downloadable. The file param must have a 'type' attribute like in core_course_get_contents response. | ||||||
|  |      * | ||||||
|  |      * @param file File to check. | ||||||
|  |      * @return Whether it's downloadable. | ||||||
|  |      */ | ||||||
|  |     isFileDownloadable(file: CoreCourseModuleContentFile): boolean { | ||||||
|  |         return file.type === 'file'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Return whether or not the plugin is enabled. | ||||||
|  |      * | ||||||
|  |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. | ||||||
|  |      */ | ||||||
|  |     async isPluginEnabled(siteId?: string): Promise<boolean> { | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  | 
 | ||||||
|  |         return site.canDownloadFiles(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Report a book as being viewed. | ||||||
|  |      * | ||||||
|  |      * @param id Module ID. | ||||||
|  |      * @param chapterId Chapter ID. | ||||||
|  |      * @param name Name of the book. | ||||||
|  |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved when the WS call is successful. | ||||||
|  |      */ | ||||||
|  |     logView(id: number, chapterId?: number, name?: string, siteId?: string): Promise<void> { | ||||||
|  |         const params: AddonModBookViewBookWSParams = { | ||||||
|  |             bookid: id, | ||||||
|  |             chapterid: chapterId, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         return CoreCourseLogHelper.instance.logSingle( | ||||||
|  |             'mod_book_view_book', | ||||||
|  |             params, | ||||||
|  |             AddonModBookProvider.COMPONENT, | ||||||
|  |             id, | ||||||
|  |             name, | ||||||
|  |             'book', | ||||||
|  |             { chapterid: chapterId }, | ||||||
|  |             siteId, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AddonModBook extends makeSingleton(AddonModBookProvider) {} | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * A book chapter inside the toc list. | ||||||
|  |  */ | ||||||
|  | export type AddonModBookTocChapter = { | ||||||
|  |     id: number; // ID to identify the chapter.
 | ||||||
|  |     title: string; // Chapter's title.
 | ||||||
|  |     level: number; // The chapter's level.
 | ||||||
|  |     hidden: boolean; // The chapter is hidden.
 | ||||||
|  |     indexNumber: string; // The chapter's number'.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * A book chapter parsed from JSON. | ||||||
|  |  */ | ||||||
|  | type AddonModBookTocChapterParsed = { | ||||||
|  |     title: string; // Chapter's title.
 | ||||||
|  |     level: number; // The chapter's level.
 | ||||||
|  |     hidden: string; // The chapter is hidden.
 | ||||||
|  |     href: string; | ||||||
|  |     subitems: AddonModBookTocChapterParsed[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Map of book contents. For each chapter it has its index URL and the list of paths of the files the chapter has. Each path | ||||||
|  |  * is identified by the relative path in the book, and the value is the URL of the file. | ||||||
|  |  */ | ||||||
|  | export type AddonModBookContentsMap = { | ||||||
|  |     [chapter: string]: { | ||||||
|  |         indexUrl?: string; | ||||||
|  |         paths: {[path: string]: string}; | ||||||
|  |         tags?: CoreTagItem[]; | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Book returned by mod_book_get_books_by_courses. | ||||||
|  |  */ | ||||||
|  | export type AddonModBookBookWSData = { | ||||||
|  |     id: number; // Book id.
 | ||||||
|  |     coursemodule: number; // Course module id.
 | ||||||
|  |     course: number; // Course id.
 | ||||||
|  |     name: string; // Book name.
 | ||||||
|  |     intro: string; // The Book intro.
 | ||||||
|  |     introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|  |     introfiles?: CoreWSExternalFile[]; // @since 3.2.
 | ||||||
|  |     numbering: number; // Book numbering configuration.
 | ||||||
|  |     navstyle: number; // Book navigation style configuration.
 | ||||||
|  |     customtitles: number; // Book custom titles type.
 | ||||||
|  |     revision?: number; // Book revision.
 | ||||||
|  |     timecreated?: number; // Time of creation.
 | ||||||
|  |     timemodified?: number; // Time of last modification.
 | ||||||
|  |     section?: number; // Course section id.
 | ||||||
|  |     visible?: boolean; // Visible.
 | ||||||
|  |     groupmode?: number; // Group mode.
 | ||||||
|  |     groupingid?: number; // Group id.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Params of mod_book_get_books_by_courses WS. | ||||||
|  |  */ | ||||||
|  | type AddonModBookGetBooksByCoursesWSParams = { | ||||||
|  |     courseids?: number[]; // Array of course ids.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Data returned by mod_book_get_books_by_courses WS. | ||||||
|  |  */ | ||||||
|  | type AddonModBookGetBooksByCoursesWSResponse = { | ||||||
|  |     books: AddonModBookBookWSData[]; | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Params of mod_book_view_book WS. | ||||||
|  |  */ | ||||||
|  | type AddonModBookViewBookWSParams = { | ||||||
|  |     bookid: number; // Book instance id.
 | ||||||
|  |     chapterid?: number; // Chapter id.
 | ||||||
|  | }; | ||||||
							
								
								
									
										55
									
								
								src/addons/mod/book/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/addons/mod/book/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | // (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 { Params } from '@angular/router'; | ||||||
|  | import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { AddonModBook } from '../book'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Handler to treat links to book. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class AddonModBookIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler { | ||||||
|  | 
 | ||||||
|  |     name = 'AddonModBookLinkHandler'; | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         super('AddonModBook', 'book', 'b'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the mod params necessary to open an activity. | ||||||
|  |      * | ||||||
|  |      * @param  url      The URL to treat. | ||||||
|  |      * @param  params   The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} | ||||||
|  |      * @return List of params to pass to navigateToModule / navigateToModuleByInstance. | ||||||
|  |      */ | ||||||
|  |     getPageParams(url: string, params: Record<string, string>): Params { | ||||||
|  |         return params.chapterid ? { chapterId: parseInt(params.chapterid, 10) } : {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if the handler is enabled for a certain site (site + user) and a URL. | ||||||
|  |      * | ||||||
|  |      * @return Whether the handler is enabled for the URL and site. | ||||||
|  |      */ | ||||||
|  |     isEnabled(siteId: string): Promise<boolean> { | ||||||
|  |         return AddonModBook.instance.isPluginEnabled(siteId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AddonModBookIndexLinkHandler extends makeSingleton(AddonModBookIndexLinkHandlerService) {} | ||||||
							
								
								
									
										44
									
								
								src/addons/mod/book/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/addons/mod/book/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | // (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 { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { AddonModBook } from '../book'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Handler to treat links to book list page. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class AddonModBookListLinkHandlerService extends CoreContentLinksModuleListHandler { | ||||||
|  | 
 | ||||||
|  |     name = 'AddonModBookListLinkHandler'; | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         super('AddonModBook', 'book'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if the handler is enabled for a certain site (site + user) and a URL. | ||||||
|  |      * If not defined, defaults to true. | ||||||
|  |      * | ||||||
|  |      * @return Whether the handler is enabled for the URL and site. | ||||||
|  |      */ | ||||||
|  |     isEnabled(): Promise<boolean> { | ||||||
|  |         return AddonModBook.instance.isPluginEnabled(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AddonModBookListLinkHandler extends makeSingleton(AddonModBookListLinkHandlerService) {} | ||||||
							
								
								
									
										94
									
								
								src/addons/mod/book/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/addons/mod/book/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | |||||||
|  | // (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 { AddonModBookIndexComponent } from '../../components/index'; | ||||||
|  | import { AddonModBook } from '../book'; | ||||||
|  | import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; | ||||||
|  | import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||||
|  | import { CoreCourseModule } from '@features/course/services/course-helper'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
|  | import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Handler to support book modules. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class AddonModBookModuleHandlerService implements CoreCourseModuleHandler { | ||||||
|  | 
 | ||||||
|  |     static readonly PAGE_NAME = 'mod_book'; | ||||||
|  | 
 | ||||||
|  |     name = 'AddonModBook'; | ||||||
|  |     modName = 'book'; | ||||||
|  | 
 | ||||||
|  |     supportedFeatures = { | ||||||
|  |         [CoreConstants.FEATURE_MOD_ARCHETYPE]: CoreConstants.MOD_ARCHETYPE_RESOURCE, | ||||||
|  |         [CoreConstants.FEATURE_GROUPS]: false, | ||||||
|  |         [CoreConstants.FEATURE_GROUPINGS]: false, | ||||||
|  |         [CoreConstants.FEATURE_MOD_INTRO]: true, | ||||||
|  |         [CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true, | ||||||
|  |         [CoreConstants.FEATURE_GRADE_HAS_GRADE]: false, | ||||||
|  |         [CoreConstants.FEATURE_GRADE_OUTCOMES]: false, | ||||||
|  |         [CoreConstants.FEATURE_BACKUP_MOODLE2]: true, | ||||||
|  |         [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if the handler is enabled on a site level. | ||||||
|  |      * | ||||||
|  |      * @return Whether or not the handler is enabled on a site level. | ||||||
|  |      */ | ||||||
|  |     isEnabled(): Promise<boolean> { | ||||||
|  |         return AddonModBook.instance.isPluginEnabled(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the data required to display the module in the course contents view. | ||||||
|  |      * | ||||||
|  |      * @param module The module object. | ||||||
|  |      * @param courseId The course ID. | ||||||
|  |      * @param sectionId The section ID. | ||||||
|  |      * @return Data to render the module. | ||||||
|  |      */ | ||||||
|  |     getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData { | ||||||
|  |         return { | ||||||
|  |             icon: CoreCourse.instance.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined), | ||||||
|  |             title: module.name, | ||||||
|  |             class: 'addon-mod_book-handler', | ||||||
|  |             showDownloadButton: true, | ||||||
|  |             action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void { | ||||||
|  |                 options = options || {}; | ||||||
|  |                 options.params = options.params || {}; | ||||||
|  |                 Object.assign(options.params, { module }); | ||||||
|  |                 const routeParams = '/' + courseId + '/' + module.id; | ||||||
|  | 
 | ||||||
|  |                 CoreNavigator.instance.navigateToSitePath(AddonModBookModuleHandlerService.PAGE_NAME + routeParams, options); | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the component to render the module. This is needed to support singleactivity course format. | ||||||
|  |      * The component returned must implement CoreCourseModuleMainComponent. | ||||||
|  |      * It's recommended to return the class of the component, but you can also return an instance of the component. | ||||||
|  |      * | ||||||
|  |      * @return The component (or promise resolved with component) to use, undefined if not found. | ||||||
|  |      */ | ||||||
|  |     async getMainComponent(): Promise<Type<unknown> | undefined> { | ||||||
|  |         return AddonModBookIndexComponent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | export class AddonModBookModuleHandler extends makeSingleton(AddonModBookModuleHandlerService) {} | ||||||
							
								
								
									
										86
									
								
								src/addons/mod/book/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/addons/mod/book/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | |||||||
|  | // (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 { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler'; | ||||||
|  | import { CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreWSExternalFile } from '@services/ws'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { AddonModBook, AddonModBookProvider } from '../book'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Handler to prefetch books. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class AddonModBookPrefetchHandlerService extends CoreCourseResourcePrefetchHandlerBase { | ||||||
|  | 
 | ||||||
|  |     name = 'AddonModBook'; | ||||||
|  |     modName = 'book'; | ||||||
|  |     component = AddonModBookProvider.COMPONENT; | ||||||
|  |     updatesNames = /^configuration$|^.*files$|^entries$/; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Download or prefetch the content. | ||||||
|  |      * | ||||||
|  |      * @param module The module object returned by WS. | ||||||
|  |      * @param courseId Course ID. | ||||||
|  |      * @param prefetch True to prefetch, false to download right away. | ||||||
|  |      * @return Promise resolved when all content is downloaded. Data returned is not reliable. | ||||||
|  |      */ | ||||||
|  |     async downloadOrPrefetch(module: CoreCourseWSModule, courseId: number, prefetch?: boolean): Promise<void> { | ||||||
|  |         const promises: Promise<unknown>[] = []; | ||||||
|  | 
 | ||||||
|  |         promises.push(super.downloadOrPrefetch(module, courseId, prefetch)); | ||||||
|  |         // Ignore errors since this WS isn't available in some Moodle versions.
 | ||||||
|  |         promises.push(CoreUtils.instance.ignoreErrors(AddonModBook.instance.getBook(courseId, module.id))); | ||||||
|  |         await Promise.all(promises); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns module intro files. | ||||||
|  |      * | ||||||
|  |      * @param module The module object returned by WS. | ||||||
|  |      * @param courseId Course ID. | ||||||
|  |      * @return Promise resolved with list of intro files. | ||||||
|  |      */ | ||||||
|  |     async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> { | ||||||
|  |         const book = await CoreUtils.instance.ignoreErrors(AddonModBook.instance.getBook(courseId, module.id)); | ||||||
|  | 
 | ||||||
|  |         return this.getIntroFilesFromInstance(module, book); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Invalidate the prefetched content. | ||||||
|  |      * | ||||||
|  |      * @param moduleId The module ID. | ||||||
|  |      * @param courseId Course ID the module belongs to. | ||||||
|  |      * @return Promise resolved when the data is invalidated. | ||||||
|  |      */ | ||||||
|  |     async invalidateContent(moduleId: number, courseId: number): Promise<void> { | ||||||
|  |         await AddonModBook.instance.invalidateContent(moduleId, courseId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Whether or not the handler is enabled on a site level. | ||||||
|  |      * | ||||||
|  |      * @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. | ||||||
|  |      */ | ||||||
|  |     isEnabled(): Promise<boolean> { | ||||||
|  |         return AddonModBook.instance.isPluginEnabled(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AddonModBookPrefetchHandler extends makeSingleton(AddonModBookPrefetchHandlerService) {} | ||||||
							
								
								
									
										79
									
								
								src/addons/mod/book/services/handlers/tag-area.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/addons/mod/book/services/handlers/tag-area.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | |||||||
|  | // (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 { CoreCourse } from '@features/course/services/course'; | ||||||
|  | import { CoreTagFeedComponent } from '@features/tag/components/feed/feed'; | ||||||
|  | import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate'; | ||||||
|  | import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper'; | ||||||
|  | import { CoreUrlUtils } from '@services/utils/url'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { AddonModBook } from '../book'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Handler to support tags. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class AddonModBookTagAreaHandlerService implements CoreTagAreaHandler { | ||||||
|  | 
 | ||||||
|  |     name = 'AddonModBookTagAreaHandler'; | ||||||
|  |     type = 'mod_book/book_chapters'; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Whether or not the handler is enabled on a site level. | ||||||
|  |      * | ||||||
|  |      * @return Whether or not the handler is enabled on a site level. | ||||||
|  |      */ | ||||||
|  |     isEnabled(): Promise<boolean> { | ||||||
|  |         return AddonModBook.instance.isPluginEnabled(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 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). | ||||||
|  |      */ | ||||||
|  |     async parseContent(content: string): Promise<CoreTagFeedElement[]> { | ||||||
|  |         const items = CoreTagHelper.instance.parseFeedContent(content); | ||||||
|  | 
 | ||||||
|  |         // Find module ids of the returned books, they are needed by the link delegate.
 | ||||||
|  |         await Promise.all(items.map((item) => { | ||||||
|  |             const params = item.url ? CoreUrlUtils.instance.extractUrlParams(item.url) : {}; | ||||||
|  |             if (params.b && !params.id) { | ||||||
|  |                 const bookId = parseInt(params.b, 10); | ||||||
|  | 
 | ||||||
|  |                 return CoreCourse.instance.getModuleBasicInfoByInstance(bookId, 'book').then((module) => { | ||||||
|  |                     item.url += '&id=' + module.id; | ||||||
|  | 
 | ||||||
|  |                     return; | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         return items; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the component to use to display items. | ||||||
|  |      * | ||||||
|  |      * @return The component (or promise resolved with component) to use, undefined if not found. | ||||||
|  |      */ | ||||||
|  |     getComponent(): Type<unknown> | Promise<Type<unknown>> { | ||||||
|  |         return CoreTagFeedComponent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AddonModBookTagAreaHandler extends makeSingleton(AddonModBookTagAreaHandlerService) {} | ||||||
| @ -20,7 +20,7 @@ | |||||||
|             [iconAction]="prefetchStatusIcon" [closeOnClick]="false"> |             [iconAction]="prefetchStatusIcon" [closeOnClick]="false"> | ||||||
|         </core-context-menu-item> |         </core-context-menu-item> | ||||||
|         <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}" |         <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}" | ||||||
|             iconDescription="fas-cube" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false"> |             iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false"> | ||||||
|         </core-context-menu-item> |         </core-context-menu-item> | ||||||
|     </core-context-menu> |     </core-context-menu> | ||||||
| </core-navbar-buttons> | </core-navbar-buttons> | ||||||
| @ -161,7 +161,9 @@ | |||||||
|                         </ion-label> |                         </ion-label> | ||||||
|                         <ion-select [(ngModel)]="group" (ionChange)="setGroup(group)" aria-labelledby="addon-mod_lesson-groupslabel" |                         <ion-select [(ngModel)]="group" (ionChange)="setGroup(group)" aria-labelledby="addon-mod_lesson-groupslabel" | ||||||
|                             interface="action-sheet"> |                             interface="action-sheet"> | ||||||
|                             <ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">{{groupOpt.name}}</ion-select-option> |                             <ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id"> | ||||||
|  |                                 {{groupOpt.name}} | ||||||
|  |                             </ion-select-option> | ||||||
|                         </ion-select> |                         </ion-select> | ||||||
|                     </ion-item> |                     </ion-item> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -624,8 +624,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.loaded = false; |         this.loaded = false; | ||||||
|         this.refreshIcon = 'spinner'; |         this.refreshIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await this.validatePassword(<string> password); |             await this.validatePassword(<string> password); | ||||||
| @ -643,8 +643,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | |||||||
|             CoreDomUtils.instance.showErrorModal(error); |             CoreDomUtils.instance.showErrorModal(error); | ||||||
|         } finally { |         } finally { | ||||||
|             this.loaded = true; |             this.loaded = true; | ||||||
|             this.refreshIcon = 'refresh'; |             this.refreshIcon = CoreConstants.ICON_REFRESH; | ||||||
|             this.syncIcon = 'sync'; |             this.syncIcon = CoreConstants.ICON_SYNC; | ||||||
| 
 | 
 | ||||||
|             CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, true, this.siteId); |             CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, true, this.siteId); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -17,12 +17,7 @@ import { RouterModule, Routes } from '@angular/router'; | |||||||
| 
 | 
 | ||||||
| const routes: Routes = [ | const routes: Routes = [ | ||||||
|     { |     { | ||||||
|         path: '', |         path: ':courseId/:cmdId', | ||||||
|         redirectTo: 'index', |  | ||||||
|         pathMatch: 'full', |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         path: 'index', |  | ||||||
|         loadChildren: () => import('./pages/index/index.module').then( m => m.AddonModLessonIndexPageModule), |         loadChildren: () => import('./pages/index/index.module').then( m => m.AddonModLessonIndexPageModule), | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  | |||||||
| @ -111,7 +111,9 @@ | |||||||
|                                         <!-- Content page, display a button and the content. --> |                                         <!-- Content page, display a button and the content. --> | ||||||
|                                         <ion-row> |                                         <ion-row> | ||||||
|                                             <ion-col> |                                             <ion-col> | ||||||
|                                                 <ion-button expand="block" class="ion-text-wrap" color="light" [disabled]="true">{{ answer[0].buttonText }}</ion-button> |                                                 <ion-button expand="block" class="ion-text-wrap" color="light" [disabled]="true"> | ||||||
|  |                                                     {{ answer[0].buttonText }} | ||||||
|  |                                                 </ion-button> | ||||||
|                                             </ion-col> |                                             </ion-col> | ||||||
|                                             <ion-col> |                                             <ion-col> | ||||||
|                                                 <p [innerHTML]="answer[0].content"></p> |                                                 <p [innerHTML]="answer[0].content"></p> | ||||||
|  | |||||||
| @ -79,9 +79,10 @@ export class AddonModLessonModuleHandlerService implements CoreCourseModuleHandl | |||||||
|             action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => { |             action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => { | ||||||
|                 options = options || {}; |                 options = options || {}; | ||||||
|                 options.params = options.params || {}; |                 options.params = options.params || {}; | ||||||
|                 Object.assign(options.params, { module, courseId }); |                 Object.assign(options.params, { module }); | ||||||
|  |                 const routeParams = '/' + courseId + '/' + module.id; | ||||||
| 
 | 
 | ||||||
|                 CoreNavigator.instance.navigateToSitePath(AddonModLessonModuleHandlerService.PAGE_NAME, options); |                 CoreNavigator.instance.navigateToSitePath(AddonModLessonModuleHandlerService.PAGE_NAME + routeParams, options); | ||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -14,11 +14,13 @@ | |||||||
| 
 | 
 | ||||||
| import { NgModule } from '@angular/core'; | import { NgModule } from '@angular/core'; | ||||||
| 
 | 
 | ||||||
|  | import { AddonModBookModule } from './book/book.module'; | ||||||
| import { AddonModLessonModule } from './lesson/lesson.module'; | import { AddonModLessonModule } from './lesson/lesson.module'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     declarations: [], |     declarations: [], | ||||||
|     imports: [ |     imports: [ | ||||||
|  |         AddonModBookModule, | ||||||
|         AddonModLessonModule, |         AddonModLessonModule, | ||||||
|     ], |     ], | ||||||
|     providers: [], |     providers: [], | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ import { Routes } from '@angular/router'; | |||||||
| import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate'; | import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate'; | ||||||
| import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; | import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; | ||||||
| import { AddonPrivateFilesMainMenuHandler, AddonPrivateFilesMainMenuHandlerService } from './services/handlers/mainmenu'; | import { AddonPrivateFilesMainMenuHandler, AddonPrivateFilesMainMenuHandlerService } from './services/handlers/mainmenu'; | ||||||
|  | import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; | ||||||
| 
 | 
 | ||||||
| const routes: Routes = [ | const routes: Routes = [ | ||||||
|     { |     { | ||||||
| @ -27,7 +28,10 @@ const routes: Routes = [ | |||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     imports: [CoreMainMenuRoutingModule.forChild({ children: routes })], |     imports: [ | ||||||
|  |         CoreMainMenuTabRoutingModule.forChild(routes), | ||||||
|  |         CoreMainMenuRoutingModule.forChild({ children: routes }), | ||||||
|  |     ], | ||||||
|     exports: [CoreMainMenuRoutingModule], |     exports: [CoreMainMenuRoutingModule], | ||||||
|     providers: [ |     providers: [ | ||||||
|         { |         { | ||||||
|  | |||||||
| @ -44,6 +44,7 @@ import { CoreDynamicComponent } from './dynamic-component/dynamic-component'; | |||||||
| import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons'; | import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons'; | ||||||
| import { CoreSendMessageFormComponent } from './send-message-form/send-message-form'; | import { CoreSendMessageFormComponent } from './send-message-form/send-message-form'; | ||||||
| import { CoreTimerComponent } from './timer/timer'; | import { CoreTimerComponent } from './timer/timer'; | ||||||
|  | import { CoreNavigationBarComponent } from './navigation-bar/navigation-bar'; | ||||||
| 
 | 
 | ||||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
| import { CorePipesModule } from '@pipes/pipes.module'; | import { CorePipesModule } from '@pipes/pipes.module'; | ||||||
| @ -76,6 +77,7 @@ import { CorePipesModule } from '@pipes/pipes.module'; | |||||||
|         CoreDynamicComponent, |         CoreDynamicComponent, | ||||||
|         CoreSendMessageFormComponent, |         CoreSendMessageFormComponent, | ||||||
|         CoreTimerComponent, |         CoreTimerComponent, | ||||||
|  |         CoreNavigationBarComponent, | ||||||
|     ], |     ], | ||||||
|     imports: [ |     imports: [ | ||||||
|         CommonModule, |         CommonModule, | ||||||
| @ -112,6 +114,7 @@ import { CorePipesModule } from '@pipes/pipes.module'; | |||||||
|         CoreDynamicComponent, |         CoreDynamicComponent, | ||||||
|         CoreSendMessageFormComponent, |         CoreSendMessageFormComponent, | ||||||
|         CoreTimerComponent, |         CoreTimerComponent, | ||||||
|  |         CoreNavigationBarComponent, | ||||||
|     ], |     ], | ||||||
| }) | }) | ||||||
| export class CoreComponentsModule {} | export class CoreComponentsModule {} | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
| import { Component } from '@angular/core'; | import { Component } from '@angular/core'; | ||||||
| import { NavParams } from '@ionic/angular'; | import { NavParams } from '@ionic/angular'; | ||||||
| import { PopoverController } from '@singletons'; | import { PopoverController } from '@singletons'; | ||||||
| @ -58,7 +59,7 @@ export class CoreContextMenuPopoverComponent { | |||||||
|             event.preventDefault(); |             event.preventDefault(); | ||||||
|             event.stopPropagation(); |             event.stopPropagation(); | ||||||
| 
 | 
 | ||||||
|             if (item.iconAction == 'spinner') { |             if (item.iconAction == CoreConstants.ICON_LOADING) { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								src/core/components/navigation-bar/core-navigation-bar.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/core/components/navigation-bar/core-navigation-bar.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | <ion-grid class="ion-no-padding ion-padding-bottom" *ngIf="previous || info || next"> | ||||||
|  |     <ion-row> | ||||||
|  |         <ion-col class="ion-text-start" size="4"> | ||||||
|  |             <ion-button *ngIf="previous" class="core-navigation-bar-arrow" color="light" | ||||||
|  |                 [title]="previousTitle || ('core.previous' | translate)" (click)="action?.emit(previous)"> | ||||||
|  |                 <ion-icon name="fas-arrow-left" [slot]="previousTitle ? 'start' : 'icon-only'"></ion-icon> | ||||||
|  |                 <core-format-text *ngIf="previousTitle" [text]="previousTitle" [component]="component" [componentId]="componentId" | ||||||
|  |                     [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text> | ||||||
|  |             </ion-button> | ||||||
|  |         </ion-col> | ||||||
|  |         <ion-col class="ion-text-center ion-padding-horizontal" size="4"> | ||||||
|  |             <ion-button fill="clear" *ngIf="info" (click)="showInfo()" [title]="title"> | ||||||
|  |                 <ion-icon slot="icon-only" name="fas-info-circle"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |         </ion-col> | ||||||
|  |         <ion-col class="ion-text-end" size="4"> | ||||||
|  |             <ion-button *ngIf="next" class="core-navigation-bar-arrow" [title]="nextTitle || ('core.next' | translate)" | ||||||
|  |                 (click)="action?.emit(next)"> | ||||||
|  |                 <core-format-text *ngIf="nextTitle" [text]="nextTitle" [component]="component" [componentId]="componentId" | ||||||
|  |                     [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text> | ||||||
|  |                 <ion-icon name="fas-arrow-right" [slot]="nextTitle ? 'end' : 'icon-only'"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |         </ion-col> | ||||||
|  |     </ion-row> | ||||||
|  | </ion-grid> | ||||||
							
								
								
									
										12
									
								
								src/core/components/navigation-bar/navigation-bar.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/core/components/navigation-bar/navigation-bar.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | .core-navigation-bar-arrow { | ||||||
|  |     text-transform: none; | ||||||
|  |     max-width: 100%; | ||||||
|  |     ion-icon { | ||||||
|  |         flex-shrink: 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     core-format-text { | ||||||
|  |         overflow: hidden; | ||||||
|  |         text-overflow: ellipsis; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								src/core/components/navigation-bar/navigation-bar.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/core/components/navigation-bar/navigation-bar.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | // (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, EventEmitter, Input, Output } from '@angular/core'; | ||||||
|  | import { CoreTextUtils } from '@services/utils/text'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Component to show a "bar" with arrows to navigate forward/backward and a "info" icon to display more data. | ||||||
|  |  * | ||||||
|  |  * This directive will show two arrows at the left and right of the screen to navigate to previous/next item when clicked. | ||||||
|  |  * If no previous/next item is defined, that arrow won't be shown. It will also show a button to show more info. | ||||||
|  |  * | ||||||
|  |  * Example usage: | ||||||
|  |  * <core-navigation-bar [previous]="prevItem" [next]="nextItem" (action)="goTo($event)"></core-navigation-bar> | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'core-navigation-bar', | ||||||
|  |     templateUrl: 'core-navigation-bar.html', | ||||||
|  |     styleUrls: ['navigation-bar.scss'], | ||||||
|  | }) | ||||||
|  | export class CoreNavigationBarComponent { | ||||||
|  | 
 | ||||||
|  |     @Input() previous?: unknown; // Previous item. If not defined, the previous arrow won't be shown.
 | ||||||
|  |     @Input() previousTitle?: string; // Previous item title. If not defined, only the arrow will be shown.
 | ||||||
|  |     @Input() next?: unknown; // Next item. If not defined, the next arrow won't be shown.
 | ||||||
|  |     @Input() nextTitle?: string; // Next item title. If not defined, only the arrow will be shown.
 | ||||||
|  |     @Input() info = ''; // Info to show when clicking the info button. If not defined, the info button won't be shown.
 | ||||||
|  |     @Input() title = ''; // Title to show when seeing the info (new page).
 | ||||||
|  |     @Input() component?: string; // Component the bar belongs to.
 | ||||||
|  |     @Input() componentId?: number; // Component ID.
 | ||||||
|  |     @Input() contextLevel?: string; // The context level.
 | ||||||
|  |     @Input() contextInstanceId?: number; // The instance ID related to the context.
 | ||||||
|  |     @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters.
 | ||||||
|  |     @Output() action?: EventEmitter<unknown> = | ||||||
|  |         new EventEmitter<unknown>(); // Function to call when arrow is clicked. Will receive as a param the item to load.
 | ||||||
|  | 
 | ||||||
|  |     showInfo(): void { | ||||||
|  |         CoreTextUtils.instance.viewText(this.title, this.info, { | ||||||
|  |             component: this.component, | ||||||
|  |             componentId: this.componentId, | ||||||
|  |             filter: true, | ||||||
|  |             contextLevel: this.contextLevel, | ||||||
|  |             instanceId: this.contextInstanceId, | ||||||
|  |             courseId: this.courseId, | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -77,11 +77,17 @@ export class CoreConstants { | |||||||
|     static readonly OUTDATED = 'outdated'; |     static readonly OUTDATED = 'outdated'; | ||||||
|     static readonly NOT_DOWNLOADABLE = 'notdownloadable'; |     static readonly NOT_DOWNLOADABLE = 'notdownloadable'; | ||||||
| 
 | 
 | ||||||
|     static readonly DOWNLOADED_ICON = 'cloud-done'; |     // Download / prefetch status icon.
 | ||||||
|     static readonly DOWNLOADING_ICON = 'spinner'; |     static readonly ICON_DOWNLOADED = 'cloud-done'; | ||||||
|     static readonly NOT_DOWNLOADED_ICON = 'cloud-download'; |     static readonly ICON_DOWNLOADING = 'spinner'; | ||||||
|     static readonly OUTDATED_ICON = 'fas-redo-alt'; |     static readonly ICON_NOT_DOWNLOADED = 'cloud-download'; | ||||||
|     static readonly NOT_DOWNLOADABLE_ICON = ''; |     static readonly ICON_OUTDATED = 'fas-redo-alt'; | ||||||
|  |     static readonly ICON_NOT_DOWNLOADABLE = ''; | ||||||
|  | 
 | ||||||
|  |     // General download and sync icons.
 | ||||||
|  |     static readonly ICON_LOADING = 'spinner'; | ||||||
|  |     static readonly ICON_REFRESH = 'fas-redo-alt'; | ||||||
|  |     static readonly ICON_SYNC = 'fas-sync-alt'; | ||||||
| 
 | 
 | ||||||
|     // Constants from Moodle's resourcelib.
 |     // Constants from Moodle's resourcelib.
 | ||||||
|     static readonly RESOURCELIB_DISPLAY_AUTO = 0; // Try the best way.
 |     static readonly RESOURCELIB_DISPLAY_AUTO = 0; // Try the best way.
 | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ import { CoreUtils } from '@services/utils/utils'; | |||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreWSExternalWarning } from '@services/ws'; | import { CoreWSExternalWarning } from '@services/ws'; | ||||||
| import { CoreCourseContentsPage } from '../pages/contents/contents'; | import { CoreCourseContentsPage } from '../pages/contents/contents'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Template class to easily create CoreCourseModuleMainComponent of activities. |  * Template class to easily create CoreCourseModuleMainComponent of activities. | ||||||
| @ -70,7 +71,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR | |||||||
|         super.ngOnInit(); |         super.ngOnInit(); | ||||||
| 
 | 
 | ||||||
|         this.hasOffline = false; |         this.hasOffline = false; | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.moduleName = CoreCourse.instance.translateModuleName(this.moduleName || ''); |         this.moduleName = CoreCourse.instance.translateModuleName(this.moduleName || ''); | ||||||
| 
 | 
 | ||||||
|         if (this.syncEventName) { |         if (this.syncEventName) { | ||||||
| @ -117,16 +118,16 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.refreshIcon = 'spinner'; |         this.refreshIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await CoreUtils.instance.ignoreErrors(this.invalidateContent()); |             await CoreUtils.instance.ignoreErrors(this.invalidateContent()); | ||||||
| 
 | 
 | ||||||
|             await this.loadContent(true, sync, showErrors); |             await this.loadContent(true, sync, showErrors); | ||||||
|         } finally  { |         } finally  { | ||||||
|             this.refreshIcon = 'fas-redo'; |             this.refreshIcon = CoreConstants.ICON_REFRESH; | ||||||
|             this.syncIcon = 'fas-sync'; |             this.syncIcon = CoreConstants.ICON_SYNC; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -138,16 +139,16 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR | |||||||
|      * @return Resolved when done. |      * @return Resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected async showLoadingAndFetch(sync: boolean = false, showErrors: boolean = false): Promise<void> { |     protected async showLoadingAndFetch(sync: boolean = false, showErrors: boolean = false): Promise<void> { | ||||||
|         this.refreshIcon = 'spinner'; |         this.refreshIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.loaded = false; |         this.loaded = false; | ||||||
|         this.content?.scrollToTop(); |         this.content?.scrollToTop(); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await this.loadContent(false, sync, showErrors); |             await this.loadContent(false, sync, showErrors); | ||||||
|         } finally { |         } finally { | ||||||
|             this.refreshIcon = 'fas-redo'; |             this.refreshIcon = CoreConstants.ICON_REFRESH; | ||||||
|             this.syncIcon = 'fas-sync'; |             this.syncIcon = CoreConstants.ICON_REFRESH; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -159,8 +160,8 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR | |||||||
|      * @return Resolved when done. |      * @return Resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected showLoadingAndRefresh(sync: boolean = false, showErrors: boolean = false): Promise<void> { |     protected showLoadingAndRefresh(sync: boolean = false, showErrors: boolean = false): Promise<void> { | ||||||
|         this.refreshIcon = 'spinner'; |         this.refreshIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.syncIcon = 'spinner'; |         this.syncIcon = CoreConstants.ICON_LOADING; | ||||||
|         this.loaded = false; |         this.loaded = false; | ||||||
|         this.content?.scrollToTop(); |         this.content?.scrollToTop(); | ||||||
| 
 | 
 | ||||||
| @ -207,8 +208,8 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR | |||||||
|             CoreDomUtils.instance.showErrorModalDefault(error, this.fetchContentDefaultError, true); |             CoreDomUtils.instance.showErrorModalDefault(error, this.fetchContentDefaultError, true); | ||||||
|         } finally { |         } finally { | ||||||
|             this.loaded = true; |             this.loaded = true; | ||||||
|             this.refreshIcon = 'fas-redo'; |             this.refreshIcon = CoreConstants.ICON_REFRESH; | ||||||
|             this.syncIcon = 'fas-sync'; |             this.syncIcon = CoreConstants.ICON_REFRESH; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -58,7 +58,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | |||||||
|     // Data for context menu.
 |     // Data for context menu.
 | ||||||
|     externalUrl?: string; // External URL to open in browser.
 |     externalUrl?: string; // External URL to open in browser.
 | ||||||
|     description?: string; // Module description.
 |     description?: string; // Module description.
 | ||||||
|     refreshIcon = 'spinner'; // Refresh icon, normally spinner or refresh.
 |     refreshIcon = CoreConstants.ICON_LOADING; // Refresh icon, normally spinner or refresh.
 | ||||||
|     prefetchStatusIcon?: string; // Used when calling fillContextMenu.
 |     prefetchStatusIcon?: string; // Used when calling fillContextMenu.
 | ||||||
|     prefetchStatus?: string; // Used when calling fillContextMenu.
 |     prefetchStatus?: string; // Used when calling fillContextMenu.
 | ||||||
|     prefetchText?: string; // Used when calling fillContextMenu.
 |     prefetchText?: string; // Used when calling fillContextMenu.
 | ||||||
| @ -132,14 +132,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.refreshIcon = 'spinner'; |         this.refreshIcon = CoreConstants.ICON_LOADING; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await CoreUtils.instance.ignoreErrors(this.invalidateContent()); |             await CoreUtils.instance.ignoreErrors(this.invalidateContent()); | ||||||
| 
 | 
 | ||||||
|             await this.loadContent(true); |             await this.loadContent(true); | ||||||
|         } finally  { |         } finally  { | ||||||
|             this.refreshIcon = 'fas-redo'; |             this.refreshIcon = CoreConstants.ICON_REFRESH; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -181,7 +181,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | |||||||
|             CoreDomUtils.instance.showErrorModalDefault(error, this.fetchContentDefaultError, true); |             CoreDomUtils.instance.showErrorModalDefault(error, this.fetchContentDefaultError, true); | ||||||
|         } finally { |         } finally { | ||||||
|             this.loaded = true; |             this.loaded = true; | ||||||
|             this.refreshIcon = 'fas-redo'; |             this.refreshIcon = CoreConstants.ICON_REFRESH; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -45,6 +45,7 @@ import { | |||||||
|     CoreEventCompletionModuleViewedData, |     CoreEventCompletionModuleViewedData, | ||||||
| } from '@singletons/events'; | } from '@singletons/events'; | ||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the contents of a course. |  * Page that displays the contents of a course. | ||||||
| @ -71,7 +72,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | |||||||
|     displayEnableDownload = false; |     displayEnableDownload = false; | ||||||
|     displayRefresher = false; |     displayRefresher = false; | ||||||
|     prefetchCourseData: CorePrefetchStatusInfo = { |     prefetchCourseData: CorePrefetchStatusInfo = { | ||||||
|         icon: 'spinner', |         icon: CoreConstants.ICON_LOADING, | ||||||
|         statusTranslatable: 'core.course.downloadcourse', |         statusTranslatable: 'core.course.downloadcourse', | ||||||
|         status: '', |         status: '', | ||||||
|         loading: true, |         loading: true, | ||||||
| @ -171,7 +172,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | |||||||
|         // Determine the course prefetch status.
 |         // Determine the course prefetch status.
 | ||||||
|         await this.determineCoursePrefetchIcon(); |         await this.determineCoursePrefetchIcon(); | ||||||
| 
 | 
 | ||||||
|         if (this.prefetchCourseData.icon != 'spinner') { |         if (this.prefetchCourseData.icon != CoreConstants.ICON_LOADING) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -359,7 +359,7 @@ export class CoreCourseHelperProvider { | |||||||
|         const siteId = CoreSites.instance.getCurrentSiteId(); |         const siteId = CoreSites.instance.getCurrentSiteId(); | ||||||
| 
 | 
 | ||||||
|         data.downloadSucceeded = false; |         data.downloadSucceeded = false; | ||||||
|         data.icon = 'spinner'; |         data.icon = CoreConstants.ICON_DOWNLOADING; | ||||||
|         data.statusTranslatable = 'core.downloading'; |         data.statusTranslatable = 'core.downloading'; | ||||||
| 
 | 
 | ||||||
|         // Get the sections first if needed.
 |         // Get the sections first if needed.
 | ||||||
| @ -563,7 +563,7 @@ export class CoreCourseHelperProvider { | |||||||
|         done?: () => void, |         done?: () => void, | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|         const initialIcon = instance.prefetchStatusIcon; |         const initialIcon = instance.prefetchStatusIcon; | ||||||
|         instance.prefetchStatusIcon = 'spinner'; // Show spinner since this operation might take a while.
 |         instance.prefetchStatusIcon = CoreConstants.ICON_DOWNLOADING; // Show spinner since this operation might take a while.
 | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             // We need to call getDownloadSize, the package might have been updated.
 |             // We need to call getDownloadSize, the package might have been updated.
 | ||||||
| @ -1122,7 +1122,7 @@ export class CoreCourseHelperProvider { | |||||||
| 
 | 
 | ||||||
|         if (prefetch.loading) { |         if (prefetch.loading) { | ||||||
|             // It seems all courses are being downloaded, show a download button instead.
 |             // It seems all courses are being downloaded, show a download button instead.
 | ||||||
|             prefetch.icon = CoreConstants.NOT_DOWNLOADED_ICON; |             prefetch.icon = CoreConstants.ICON_NOT_DOWNLOADED; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return prefetch; |         return prefetch; | ||||||
| @ -1188,14 +1188,14 @@ export class CoreCourseHelperProvider { | |||||||
|         prefetch: CorePrefetchStatusInfo, |         prefetch: CorePrefetchStatusInfo, | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|         prefetch.loading = true; |         prefetch.loading = true; | ||||||
|         prefetch.icon = CoreConstants.DOWNLOADING_ICON; |         prefetch.icon = CoreConstants.ICON_DOWNLOADING; | ||||||
|         prefetch.badge = ''; |         prefetch.badge = ''; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             await this.confirmAndPrefetchCourses(courses, (progress) => { |             await this.confirmAndPrefetchCourses(courses, (progress) => { | ||||||
|                 prefetch.badge = progress.count + ' / ' + progress.total; |                 prefetch.badge = progress.count + ' / ' + progress.total; | ||||||
|             }); |             }); | ||||||
|             prefetch.icon = CoreConstants.OUTDATED_ICON; |             prefetch.icon = CoreConstants.ICON_OUTDATED; | ||||||
|         } finally { |         } finally { | ||||||
|             prefetch.loading = false; |             prefetch.loading = false; | ||||||
|             prefetch.badge = ''; |             prefetch.badge = ''; | ||||||
| @ -1264,19 +1264,19 @@ export class CoreCourseHelperProvider { | |||||||
|      */ |      */ | ||||||
|     getPrefetchStatusIcon(status: string, trustDownload: boolean = false): string { |     getPrefetchStatusIcon(status: string, trustDownload: boolean = false): string { | ||||||
|         if (status == CoreConstants.NOT_DOWNLOADED) { |         if (status == CoreConstants.NOT_DOWNLOADED) { | ||||||
|             return CoreConstants.NOT_DOWNLOADED_ICON; |             return CoreConstants.ICON_NOT_DOWNLOADED; | ||||||
|         } |         } | ||||||
|         if (status == CoreConstants.OUTDATED || (status == CoreConstants.DOWNLOADED && !trustDownload)) { |         if (status == CoreConstants.OUTDATED || (status == CoreConstants.DOWNLOADED && !trustDownload)) { | ||||||
|             return CoreConstants.OUTDATED_ICON; |             return CoreConstants.ICON_OUTDATED; | ||||||
|         } |         } | ||||||
|         if (status == CoreConstants.DOWNLOADED && trustDownload) { |         if (status == CoreConstants.DOWNLOADED && trustDownload) { | ||||||
|             return CoreConstants.DOWNLOADED_ICON; |             return CoreConstants.ICON_DOWNLOADED; | ||||||
|         } |         } | ||||||
|         if (status == CoreConstants.DOWNLOADING) { |         if (status == CoreConstants.DOWNLOADING) { | ||||||
|             return CoreConstants.DOWNLOADING_ICON; |             return CoreConstants.ICON_DOWNLOADING; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return CoreConstants.DOWNLOADING_ICON; |         return CoreConstants.ICON_DOWNLOADING; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1335,17 +1335,17 @@ export class CoreCourseHelperProvider { | |||||||
|         moduleInfo.status = results[1]; |         moduleInfo.status = results[1]; | ||||||
|         switch (results[1]) { |         switch (results[1]) { | ||||||
|             case CoreConstants.NOT_DOWNLOADED: |             case CoreConstants.NOT_DOWNLOADED: | ||||||
|                 moduleInfo.statusIcon = 'fas-cloud-download-alt'; |                 moduleInfo.statusIcon = CoreConstants.ICON_NOT_DOWNLOADED; | ||||||
|                 break; |                 break; | ||||||
|             case CoreConstants.DOWNLOADING: |             case CoreConstants.DOWNLOADING: | ||||||
|                 moduleInfo.statusIcon = 'spinner'; |                 moduleInfo.statusIcon = CoreConstants.ICON_DOWNLOADING; | ||||||
|                 break; |                 break; | ||||||
|             case CoreConstants.OUTDATED: |             case CoreConstants.OUTDATED: | ||||||
|                 moduleInfo.statusIcon = 'fas-redo'; |                 moduleInfo.statusIcon = CoreConstants.ICON_OUTDATED; | ||||||
|                 break; |                 break; | ||||||
|             case CoreConstants.DOWNLOADED: |             case CoreConstants.DOWNLOADED: | ||||||
|                 if (!CoreCourseModulePrefetchDelegate.instance.canCheckUpdates()) { |                 if (!CoreCourseModulePrefetchDelegate.instance.canCheckUpdates()) { | ||||||
|                     moduleInfo.statusIcon = 'fas-redo'; |                     moduleInfo.statusIcon = CoreConstants.ICON_OUTDATED; | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             default: |             default: | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy { | |||||||
|     filter = ''; |     filter = ''; | ||||||
|     showFilter = false; |     showFilter = false; | ||||||
|     coursesLoaded = false; |     coursesLoaded = false; | ||||||
|     downloadAllCoursesIcon = CoreConstants.NOT_DOWNLOADED_ICON; |     downloadAllCoursesIcon = CoreConstants.ICON_NOT_DOWNLOADED; | ||||||
|     downloadAllCoursesLoading = false; |     downloadAllCoursesLoading = false; | ||||||
|     downloadAllCoursesBadge = ''; |     downloadAllCoursesBadge = ''; | ||||||
|     downloadAllCoursesEnabled = false; |     downloadAllCoursesEnabled = false; | ||||||
|  | |||||||
| @ -64,9 +64,9 @@ export class CoreH5PFramework { | |||||||
|         const db = await CoreSites.instance.getSiteDb(siteId); |         const db = await CoreSites.instance.getSiteDb(siteId); | ||||||
| 
 | 
 | ||||||
|         const whereAndParams = db.getInOrEqual(libraryIds); |         const whereAndParams = db.getInOrEqual(libraryIds); | ||||||
|         whereAndParams[0] = 'mainlibraryid ' + whereAndParams[0]; |         whereAndParams.sql = 'mainlibraryid ' + whereAndParams.sql; | ||||||
| 
 | 
 | ||||||
|         await db.updateRecordsWhere(CONTENT_TABLE_NAME, { filtered: null }, whereAndParams[0], whereAndParams[1]); |         await db.updateRecordsWhere(CONTENT_TABLE_NAME, { filtered: null }, whereAndParams.sql, whereAndParams.params); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -919,4 +919,3 @@ type LibraryDependency = { | |||||||
| type LibraryAddonDBData = Omit<CoreH5PLibraryAddonData, 'addTo'> & { | type LibraryAddonDBData = Omit<CoreH5PLibraryAddonData, 'addTo'> & { | ||||||
|     addTo: string; |     addTo: string; | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper'; | |||||||
| import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../services/mainmenu-delegate'; | import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../services/mainmenu-delegate'; | ||||||
| import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu'; | import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu'; | ||||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||||
|  | import { CoreNavigator } from '@services/navigator'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the main menu of the app. |  * Page that displays the main menu of the app. | ||||||
| @ -131,10 +132,12 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { | |||||||
|      * Open a handler. |      * Open a handler. | ||||||
|      * |      * | ||||||
|      * @param handler Handler to open. |      * @param handler Handler to open. | ||||||
|  |      * @todo: use subPage? | ||||||
|      */ |      */ | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 |  | ||||||
|     openHandler(handler: CoreMainMenuHandlerData): void { |     openHandler(handler: CoreMainMenuHandlerData): void { | ||||||
|         // @todo
 |         const params = handler.pageParams; | ||||||
|  | 
 | ||||||
|  |         CoreNavigator.instance.navigateToSitePath(handler.page, { params }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -142,9 +145,11 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { | |||||||
|      * |      * | ||||||
|      * @param item Item to open. |      * @param item Item to open. | ||||||
|      */ |      */ | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 |  | ||||||
|     openItem(item: CoreMainMenuCustomItem): void { |     openItem(item: CoreMainMenuCustomItem): void { | ||||||
|         // @todo
 |         // @todo CoreNavigator.instance.navigateToSitePath('CoreViewerIframePage', {title: item.label, url: item.url});
 | ||||||
|  | 
 | ||||||
|  |         // eslint-disable-next-line no-console
 | ||||||
|  |         console.error('openItem not implemented', item); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -153,6 +158,9 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { | |||||||
|     async scanQR(): Promise<void> { |     async scanQR(): Promise<void> { | ||||||
|         // Scan for a QR code.
 |         // Scan for a QR code.
 | ||||||
|         // @todo
 |         // @todo
 | ||||||
|  |         // eslint-disable-next-line no-console
 | ||||||
|  |         console.error('scanQR not implemented'); | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -12,6 +12,8 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
|  | import { CoreConstants } from '@/core/constants'; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Settings section. |  * Settings section. | ||||||
|  */ |  */ | ||||||
| @ -40,7 +42,7 @@ export class CoreSettingsConstants { | |||||||
|         { |         { | ||||||
|             name: 'synchronization', |             name: 'synchronization', | ||||||
|             path: 'sync', |             path: 'sync', | ||||||
|             icon: 'fas-sync-alt', |             icon: CoreConstants.ICON_SYNC, | ||||||
|         }, |         }, | ||||||
|         // @TODO sharedfiles
 |         // @TODO sharedfiles
 | ||||||
|         { |         { | ||||||
|  | |||||||
| @ -19,10 +19,12 @@ import { TranslateModule } from '@ngx-translate/core'; | |||||||
| 
 | 
 | ||||||
| import { CoreTagFeedComponent } from './feed/feed'; | import { CoreTagFeedComponent } from './feed/feed'; | ||||||
| import { CoreSharedModule } from '@/core/shared.module'; | import { CoreSharedModule } from '@/core/shared.module'; | ||||||
|  | import { CoreTagListComponent } from './list/list'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     declarations: [ |     declarations: [ | ||||||
|         CoreTagFeedComponent, |         CoreTagFeedComponent, | ||||||
|  |         CoreTagListComponent, | ||||||
|     ], |     ], | ||||||
|     imports: [ |     imports: [ | ||||||
|         CommonModule, |         CommonModule, | ||||||
| @ -34,6 +36,7 @@ import { CoreSharedModule } from '@/core/shared.module'; | |||||||
|     ], |     ], | ||||||
|     exports: [ |     exports: [ | ||||||
|         CoreTagFeedComponent, |         CoreTagFeedComponent, | ||||||
|  |         CoreTagListComponent, | ||||||
|     ], |     ], | ||||||
| }) | }) | ||||||
| export class CoreTagComponentsModule {} | export class CoreTagComponentsModule {} | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								src/core/features/tag/components/list/core-tag-list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/core/features/tag/components/list/core-tag-list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | <ng-container *ngFor="let tag of tags"> | ||||||
|  |     <ion-badge (click)="openTag(tag)" class="core-tag-list-tag">{{ tag.rawname }}</ion-badge> | ||||||
|  | </ng-container> | ||||||
							
								
								
									
										7
									
								
								src/core/features/tag/components/list/list.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/core/features/tag/components/list/list.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | :host { | ||||||
|  |     line-height: 1.6; | ||||||
|  | 
 | ||||||
|  |     ion-badge { | ||||||
|  |         cursor: pointer; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								src/core/features/tag/components/list/list.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/core/features/tag/components/list/list.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | // (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 { CoreTagItem } from '@features/tag/services/tag'; | ||||||
|  | import { Params } from '@angular/router'; | ||||||
|  | import { CoreNavigator } from '@services/navigator'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Component that displays the list of tags of an item. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'core-tag-list', | ||||||
|  |     templateUrl: 'core-tag-list.html', | ||||||
|  |     styleUrls: ['list.scss'], | ||||||
|  | }) | ||||||
|  | export class CoreTagListComponent { | ||||||
|  | 
 | ||||||
|  |     @Input() tags: CoreTagItem[] = []; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Go to tag index page. | ||||||
|  |      */ | ||||||
|  |     openTag(tag: CoreTagItem): void { | ||||||
|  |         const params: Params = { | ||||||
|  |             tagId: tag.id, | ||||||
|  |             tagName: tag.rawname, | ||||||
|  |             collectionId: tag.tagcollid, | ||||||
|  |             fromContextId: tag.taginstancecontextid, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // @todo: Check split view to navigate on the outlet if any.
 | ||||||
|  |         CoreNavigator.instance.navigateToSitePath('/tag/index', { params, preferCurrentTab: false }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -21,6 +21,7 @@ import { CoreTagMainMenuHandler, CoreTagMainMenuHandlerService } from './service | |||||||
| import { CoreTagIndexLinkHandler } from './services/handlers/index-link'; | import { CoreTagIndexLinkHandler } from './services/handlers/index-link'; | ||||||
| import { CoreTagSearchLinkHandler } from './services/handlers/search-link'; | import { CoreTagSearchLinkHandler } from './services/handlers/search-link'; | ||||||
| import { CoreTagComponentsModule } from './components/components.module'; | import { CoreTagComponentsModule } from './components/components.module'; | ||||||
|  | import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; | ||||||
| 
 | 
 | ||||||
| const routes: Routes = [ | const routes: Routes = [ | ||||||
|     { |     { | ||||||
| @ -31,6 +32,7 @@ const routes: Routes = [ | |||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     imports: [ |     imports: [ | ||||||
|  |         CoreMainMenuTabRoutingModule.forChild(routes), | ||||||
|         CoreMainMenuRoutingModule.forChild({ children: routes }), |         CoreMainMenuRoutingModule.forChild({ children: routes }), | ||||||
|         CoreTagComponentsModule, |         CoreTagComponentsModule, | ||||||
|     ], |     ], | ||||||
|  | |||||||
| @ -2196,15 +2196,16 @@ export class CoreFilepoolProvider { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const fileIds = items.map((item) => item.fileId); |         const fileIds = items.map((item) => item.fileId); | ||||||
|  | 
 | ||||||
|         const whereAndParams = db.getInOrEqual(fileIds); |         const whereAndParams = db.getInOrEqual(fileIds); | ||||||
| 
 | 
 | ||||||
|         whereAndParams[0] = 'fileId ' + whereAndParams[0]; |         whereAndParams.sql = 'fileId ' + whereAndParams.sql; | ||||||
| 
 | 
 | ||||||
|         if (onlyUnknown) { |         if (onlyUnknown) { | ||||||
|             whereAndParams[0] += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE + ')'; |             whereAndParams.sql += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE + ')'; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         await db.updateRecordsWhere(FILES_TABLE_NAME, { stale: 1 }, whereAndParams[0], whereAndParams[1]); |         await db.updateRecordsWhere(FILES_TABLE_NAME, { stale: 1 }, whereAndParams.sql, whereAndParams.params); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -48,6 +48,7 @@ export type CoreNavigationOptions = { | |||||||
|     animated?: boolean; |     animated?: boolean; | ||||||
|     params?: Params; |     params?: Params; | ||||||
|     reset?: boolean; |     reset?: boolean; | ||||||
|  |     preferCurrentTab?: boolean; // Default true.
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -363,6 +364,10 @@ export class CoreNavigatorService { | |||||||
|             false, |             false, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|  |         if (options.preferCurrentTab === false && isMainMenuTab) { | ||||||
|  |             return this.navigate(`/main/${path}`, options); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // Open the path within the current main tab.
 |         // Open the path within the current main tab.
 | ||||||
|         if (currentMainMenuTab && (!isMainMenuTab || pathRoot !== currentMainMenuTab)) { |         if (currentMainMenuTab && (!isMainMenuTab || pathRoot !== currentMainMenuTab)) { | ||||||
|             return this.navigate(`/main/${currentMainMenuTab}/${path}`, options); |             return this.navigate(`/main/${currentMainMenuTab}/${path}`, options); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user