commit
						9258bc0a1e
					
				| @ -24,9 +24,7 @@ import { makeSingleton, Translate } from '@singletons'; | |||||||
| /** | /** | ||||||
|  * Service that provides some helper functions regarding private and site files. |  * Service that provides some helper functions regarding private and site files. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class AddonPrivateFilesHelperProvider { | export class AddonPrivateFilesHelperProvider { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -25,9 +25,7 @@ const ROOT_CACHE_KEY = 'mmaFiles:'; | |||||||
| /** | /** | ||||||
|  * Service to handle my files and site files. |  * Service to handle my files and site files. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class AddonPrivateFilesProvider { | export class AddonPrivateFilesProvider { | ||||||
| 
 | 
 | ||||||
|     // Keep old names for backwards compatibility.
 |     // Keep old names for backwards compatibility.
 | ||||||
|  | |||||||
| @ -37,12 +37,14 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|     hideMenu = true; // It will be unhidden when items are added.
 |     hideMenu = true; // It will be unhidden when items are added.
 | ||||||
|     expanded = false; |     expanded = false; | ||||||
|  |     uniqueId: string; | ||||||
|  | 
 | ||||||
|     protected items: CoreContextMenuItemComponent[] = []; |     protected items: CoreContextMenuItemComponent[] = []; | ||||||
|     protected itemsMovedToParent: CoreContextMenuItemComponent[] = []; |     protected itemsMovedToParent: CoreContextMenuItemComponent[] = []; | ||||||
|     protected itemsChangedStream: Subject<void>; // Stream to update the hideMenu boolean when items change.
 |     protected itemsChangedStream: Subject<void>; // Stream to update the hideMenu boolean when items change.
 | ||||||
|     protected instanceId: string; |     protected instanceId: string; | ||||||
|     protected parentContextMenu?: CoreContextMenuComponent; |     protected parentContextMenu?: CoreContextMenuComponent; | ||||||
|     protected uniqueId: string; | 
 | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         protected popoverCtrl: PopoverController, |         protected popoverCtrl: PopoverController, | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ | |||||||
|     </div> |     </div> | ||||||
| </ng-container> | </ng-container> | ||||||
| 
 | 
 | ||||||
| <ion-infinite-scroll [disabled]="!enabled || error || loadingMore" (ionInfinite)="loadMore($event)" [position]="position"> | <ion-infinite-scroll [disabled]="!enabled || error || loadingMore" (ionInfinite)="loadMore()" [position]="position"> | ||||||
|     <ion-infinite-scroll-content></ion-infinite-scroll-content> |     <ion-infinite-scroll-content></ion-infinite-scroll-content> | ||||||
| </ion-infinite-scroll> | </ion-infinite-scroll> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ import { CoreUtils, CoreUtilsProvider } from '@services/utils/utils'; | |||||||
| import { Platform } from '@singletons'; | import { Platform } from '@singletons'; | ||||||
| 
 | 
 | ||||||
| import { mock, mockSingleton, RenderConfig, renderWrapperComponent } from '@/testing/utils'; | import { mock, mockSingleton, RenderConfig, renderWrapperComponent } from '@/testing/utils'; | ||||||
|  | import { CoreFilter } from '@features/filter/services/filter'; | ||||||
| 
 | 
 | ||||||
| describe('CoreFormatTextDirective', () => { | describe('CoreFormatTextDirective', () => { | ||||||
| 
 | 
 | ||||||
| @ -54,6 +55,7 @@ describe('CoreFormatTextDirective', () => { | |||||||
|         const sentence = Faker.lorem.sentence(); |         const sentence = Faker.lorem.sentence(); | ||||||
| 
 | 
 | ||||||
|         mockSingleton(CoreSites, { getSite: () => Promise.reject() }); |         mockSingleton(CoreSites, { getSite: () => Promise.reject() }); | ||||||
|  |         mockSingleton(CoreFilter, { formatText: (text) => Promise.resolve(text) }); | ||||||
| 
 | 
 | ||||||
|         // Act
 |         // Act
 | ||||||
|         const fixture = await renderWrapperComponent( |         const fixture = await renderWrapperComponent( | ||||||
| @ -85,6 +87,7 @@ describe('CoreFormatTextDirective', () => { | |||||||
|             getSite: jest.fn(() => Promise.resolve(site)), |             getSite: jest.fn(() => Promise.resolve(site)), | ||||||
|             getCurrentSite: () => Promise.resolve(site), |             getCurrentSite: () => Promise.resolve(site), | ||||||
|         }); |         }); | ||||||
|  |         mockSingleton(CoreFilter, { formatText: (text) => Promise.resolve(text) }); | ||||||
| 
 | 
 | ||||||
|         // Act
 |         // Act
 | ||||||
|         const fixture = await renderWrapperComponent( |         const fixture = await renderWrapperComponent( | ||||||
|  | |||||||
| @ -27,9 +27,7 @@ import { Params } from '@angular/router'; | |||||||
| /** | /** | ||||||
|  * Service that provides some features regarding content links. |  * Service that provides some features regarding content links. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreContentLinksHelperProvider { | export class CoreContentLinksHelperProvider { | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|  | |||||||
| @ -107,9 +107,7 @@ export type CorePrefetchStatusInfo = { | |||||||
| /** | /** | ||||||
|  * Helper to gather some common course functions. |  * Helper to gather some common course functions. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreCourseHelperProvider { | export class CoreCourseHelperProvider { | ||||||
| 
 | 
 | ||||||
|     protected courseDwnPromises: { [s: string]: { [id: number]: Promise<void> } } = {}; |     protected courseDwnPromises: { [s: string]: { [id: number]: Promise<void> } } = {}; | ||||||
|  | |||||||
| @ -21,9 +21,7 @@ import { CoreStatusWithWarningsWSResponse } from '@services/ws'; | |||||||
| /** | /** | ||||||
|  * Service to handle offline data for courses. |  * Service to handle offline data for courses. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreCourseOfflineProvider { | export class CoreCourseOfflineProvider { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -41,9 +41,7 @@ const ROOT_CACHE_KEY = 'mmCourse:'; | |||||||
| /** | /** | ||||||
|  * Service that provides some features regarding a course. |  * Service that provides some features regarding a course. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreCourseProvider { | export class CoreCourseProvider { | ||||||
| 
 | 
 | ||||||
|     static readonly ALL_SECTIONS_ID = -2; |     static readonly ALL_SECTIONS_ID = -2; | ||||||
|  | |||||||
| @ -98,10 +98,10 @@ export class CoreCoursesCourseListItemComponent implements OnInit { | |||||||
|         /* if (this.isEnrolled) { |         /* if (this.isEnrolled) { | ||||||
|             CoreCourseHelper.instance.openCourse(this.course); |             CoreCourseHelper.instance.openCourse(this.course); | ||||||
|         } else { |         } else { | ||||||
|             this.navCtrl.navigateForward('/courses/preview', { queryParams: { course: this.course } }); |             this.navCtrl.navigateForward('/main/home/courses/preview', { queryParams: { course: this.course } }); | ||||||
|         } */ |         } */ | ||||||
|         // @todo while opencourse function is not completed, open preview page.
 |         // @todo while opencourse function is not completed, open preview page.
 | ||||||
|         this.navCtrl.navigateForward('/courses/preview', { queryParams: { course: this.course } }); |         this.navCtrl.navigateForward('/main/home/courses/preview', { queryParams: { course: this.course } }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ | |||||||
|                 </ion-label> |                 </ion-label> | ||||||
|             </ion-item-divider> |             </ion-item-divider> | ||||||
|             <section *ngFor="let category of categories"> |             <section *ngFor="let category of categories"> | ||||||
|                 <ion-item class="ion-text-wrap" router-direction="forward" [routerLink]="['/courses/categories', category.id]" |                 <ion-item class="ion-text-wrap" router-direction="forward" [routerLink]="['/main/home/courses/categories', category.id]" | ||||||
|                 [title]="category.name" detail> |                 [title]="category.name" detail> | ||||||
|                     <ion-icon name="fas-folder" slot="start"></ion-icon> |                     <ion-icon name="fas-folder" slot="start"></ion-icon> | ||||||
|                     <ion-label> |                     <ion-label> | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ | |||||||
|                 <img [src]="courseImageUrl" core-external-content alt=""/> |                 <img [src]="courseImageUrl" core-external-content alt=""/> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|         <div class="core-course-thumb-parallax-content"> |         <div class="core-course-thumb-parallax-content" *ngIf="course"> | ||||||
|             <ion-item class="ion-text-wrap" (click)="openCourse()" [title]="course.fullname" [attr.details]="!avoidOpenCourse && canAccessCourse"> |             <ion-item class="ion-text-wrap" (click)="openCourse()" [title]="course.fullname" [attr.details]="!avoidOpenCourse && canAccessCourse"> | ||||||
|                 <ion-icon name="fas-graduation-cap" fixed-width slot="start"></ion-icon> |                 <ion-icon name="fas-graduation-cap" fixed-width slot="start"></ion-icon> | ||||||
|                 <ion-label> |                 <ion-label> | ||||||
| @ -43,7 +43,7 @@ | |||||||
|                         <h2>{{ 'core.teachers' | translate }}</h2> |                         <h2>{{ 'core.teachers' | translate }}</h2> | ||||||
|                     </ion-label> |                     </ion-label> | ||||||
|                 </ion-item-divider> |                 </ion-item-divider> | ||||||
|                 <ion-item class="ion-text-wrap" *ngFor="let contact of course.contacts" core-user-link |                 <!-- @todo <ion-item class="ion-text-wrap" *ngFor="let contact of course.contacts" core-user-link | ||||||
|                     [userId]="contact.id" |                     [userId]="contact.id" | ||||||
|                     [courseId]="isEnrolled ? course.id : null" |                     [courseId]="isEnrolled ? course.id : null" | ||||||
|                     [attr.aria-label]="'core.viewprofile' | translate"> |                     [attr.aria-label]="'core.viewprofile' | translate"> | ||||||
| @ -55,7 +55,7 @@ | |||||||
|                     <ion-label> |                     <ion-label> | ||||||
|                         <h2>{{contact.fullname}}</h2> |                         <h2>{{contact.fullname}}</h2> | ||||||
|                     </ion-label> |                     </ion-label> | ||||||
|                 </ion-item> |                 </ion-item>--> | ||||||
|                 <ion-item-divider></ion-item-divider> |                 <ion-item-divider></ion-item-divider> | ||||||
|             </ng-container> |             </ng-container> | ||||||
| 
 | 
 | ||||||
| @ -102,10 +102,10 @@ | |||||||
|             </ion-item> |             </ion-item> | ||||||
|             <ion-item *ngIf="canAccessCourse && downloadCourseEnabled" (click)="prefetchCourse()" detail="false" |             <ion-item *ngIf="canAccessCourse && downloadCourseEnabled" (click)="prefetchCourse()" detail="false" | ||||||
|                     [attr.aria-label]="prefetchCourseData.statusTranslatable | translate"> |                     [attr.aria-label]="prefetchCourseData.statusTranslatable | translate"> | ||||||
|                 <ion-icon *ngIf="!prefetchCourseData.status != statusDownloaded && !prefetchCourseData.loading" |                 <ion-icon *ngIf="(prefetchCourseData.status != statusDownloaded) && !prefetchCourseData.loading" | ||||||
|                     [name]="prefetchCourseData.icon" slot="start"> |                     [name]="prefetchCourseData.icon" slot="start"> | ||||||
|                 </ion-icon> |                 </ion-icon> | ||||||
|                 <ion-icon *ngIf="prefetchCourseData.status == statusDownloaded && !prefetchCourseData.loading" |                 <ion-icon *ngIf="(prefetchCourseData.status == statusDownloaded) && !prefetchCourseData.loading" | ||||||
|                     slot="start" [name]="prefetchCourseData.icon" color="success" |                     slot="start" [name]="prefetchCourseData.icon" color="success" | ||||||
|                     [attr.aria-label]="prefetchCourseData.statusTranslatable | translate" role="status"> |                     [attr.aria-label]="prefetchCourseData.statusTranslatable | translate" role="status"> | ||||||
|                 </ion-icon> |                 </ion-icon> | ||||||
|  | |||||||
| @ -64,6 +64,7 @@ export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy { | |||||||
|     downloadCourseEnabled: boolean; |     downloadCourseEnabled: boolean; | ||||||
|     courseUrl = ''; |     courseUrl = ''; | ||||||
|     courseImageUrl?: string; |     courseImageUrl?: string; | ||||||
|  |     isMobile: boolean; | ||||||
| 
 | 
 | ||||||
|     protected isGuestEnabled = false; |     protected isGuestEnabled = false; | ||||||
|     protected guestInstanceId?: number; |     protected guestInstanceId?: number; | ||||||
| @ -71,7 +72,6 @@ export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy { | |||||||
|     protected waitStart = 0; |     protected waitStart = 0; | ||||||
|     protected enrolUrl = ''; |     protected enrolUrl = ''; | ||||||
|     protected paypalReturnUrl = ''; |     protected paypalReturnUrl = ''; | ||||||
|     protected isMobile: boolean; |  | ||||||
|     protected pageDestroyed = false; |     protected pageDestroyed = false; | ||||||
|     protected courseStatusObserver?: CoreEventObserver; |     protected courseStatusObserver?: CoreEventObserver; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -83,7 +83,7 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { | |||||||
|      * Open page to manage courses storage. |      * Open page to manage courses storage. | ||||||
|      */ |      */ | ||||||
|     manageCoursesStorage(): void { |     manageCoursesStorage(): void { | ||||||
|         // @todo this.navCtrl.navigateForward(['/courses/storage']);
 |         // @todo this.navCtrl.navigateForward(['/main/home/courses/storage']);
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ | |||||||
|     </ion-toolbar> |     </ion-toolbar> | ||||||
| </ion-header> | </ion-header> | ||||||
| <ion-content> | <ion-content> | ||||||
|     <core-search-box (onSubmit)="search($event)" (onClear)="clearSearch($event)" |     <core-search-box (onSubmit)="search($event)" (onClear)="clearSearch()" | ||||||
|     [placeholder]="'core.courses.search' | translate" [searchLabel]="'core.courses.search' | translate" autoFocus="true" |     [placeholder]="'core.courses.search' | translate" [searchLabel]="'core.courses.search' | translate" autoFocus="true" | ||||||
|     searchArea="CoreCoursesSearch"></core-search-box> |     searchArea="CoreCoursesSearch"></core-search-box> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -25,9 +25,7 @@ import { CoreWSExternalFile } from '@services/ws'; | |||||||
| /** | /** | ||||||
|  * Helper to gather some common courses functions. |  * Helper to gather some common courses functions. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreCoursesHelperProvider { | export class CoreCoursesHelperProvider { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -26,9 +26,7 @@ const ROOT_CACHE_KEY = 'mmCourses:'; | |||||||
| /** | /** | ||||||
|  * Service that provides some features regarding lists of courses and categories. |  * Service that provides some features regarding lists of courses and categories. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreCoursesProvider { | export class CoreCoursesProvider { | ||||||
| 
 | 
 | ||||||
|     static readonly SEARCH_PER_PAGE = 20; |     static readonly SEARCH_PER_PAGE = 20; | ||||||
|  | |||||||
| @ -23,9 +23,7 @@ import { CaptureMediaComponentInputs, CoreEmulatorCaptureMediaComponent } from ' | |||||||
| /** | /** | ||||||
|  * Helper service with some features to capture media (image, audio, video). |  * Helper service with some features to capture media (image, audio, video). | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreEmulatorCaptureHelperProvider { | export class CoreEmulatorCaptureHelperProvider { | ||||||
| 
 | 
 | ||||||
|     protected possibleAudioMimeTypes = { |     protected possibleAudioMimeTypes = { | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ import { CoreLoginModule } from './login/login.module'; | |||||||
| import { CoreMainMenuModule } from './mainmenu/mainmenu.module'; | import { CoreMainMenuModule } from './mainmenu/mainmenu.module'; | ||||||
| import { CoreSettingsModule } from './settings/settings.module'; | import { CoreSettingsModule } from './settings/settings.module'; | ||||||
| import { CoreSiteHomeModule } from './sitehome/sitehome.module'; | import { CoreSiteHomeModule } from './sitehome/sitehome.module'; | ||||||
|  | import { CoreTagModule } from './tag/tag.module'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     imports: [ |     imports: [ | ||||||
| @ -33,6 +34,7 @@ import { CoreSiteHomeModule } from './sitehome/sitehome.module'; | |||||||
|         CoreMainMenuModule, |         CoreMainMenuModule, | ||||||
|         CoreSettingsModule, |         CoreSettingsModule, | ||||||
|         CoreSiteHomeModule, |         CoreSiteHomeModule, | ||||||
|  |         CoreTagModule, | ||||||
|     ], |     ], | ||||||
| }) | }) | ||||||
| export class CoreFeaturesModule {} | export class CoreFeaturesModule {} | ||||||
|  | |||||||
| @ -139,9 +139,7 @@ export interface CoreFileUploaderHandlerDataToReturn extends CoreFileUploaderHan | |||||||
| /** | /** | ||||||
|  * Delegate to register handlers to be shown in the file picker. |  * Delegate to register handlers to be shown in the file picker. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreFileUploaderDelegateService extends CoreDelegate<CoreFileUploaderHandler> { | export class CoreFileUploaderDelegateService extends CoreDelegate<CoreFileUploaderHandler> { | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|  | |||||||
| @ -38,9 +38,7 @@ import { CoreWSUploadFileResult } from '@services/ws'; | |||||||
| /** | /** | ||||||
|  * Helper service to upload files. |  * Helper service to upload files. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreFileUploaderHelperProvider { | export class CoreFileUploaderHelperProvider { | ||||||
| 
 | 
 | ||||||
|     protected logger: CoreLogger; |     protected logger: CoreLogger; | ||||||
|  | |||||||
| @ -45,9 +45,7 @@ export interface CoreFileUploaderOptions extends CoreWSFileUploadOptions { | |||||||
| /** | /** | ||||||
|  * Service to upload files. |  * Service to upload files. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreFileUploaderProvider { | export class CoreFileUploaderProvider { | ||||||
| 
 | 
 | ||||||
|     static readonly LIMITED_SIZE_WARNING = 1048576; // 1 MB.
 |     static readonly LIMITED_SIZE_WARNING = 1048576; // 1 MB.
 | ||||||
|  | |||||||
| @ -92,4 +92,4 @@ export class CoreFileUploaderAudioHandlerService implements CoreFileUploaderHand | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class CoreFileUploaderAudioHandler extends makeSingleton(CoreFileUploaderAudioHandlerService) {} | export class CoreFileUploaderAudioHandler extends makeSingleton(CoreFileUploaderAudioHandlerService) { } | ||||||
|  | |||||||
| @ -40,9 +40,7 @@ import { CoreObject } from '@singletons/object'; | |||||||
| /** | /** | ||||||
|  * Helper provider that provides some common features regarding authentication. |  * Helper provider that provides some common features regarding authentication. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreLoginHelperProvider { | export class CoreLoginHelperProvider { | ||||||
| 
 | 
 | ||||||
|     static readonly OPEN_COURSE = 'open_course'; |     static readonly OPEN_COURSE = 'open_course'; | ||||||
|  | |||||||
| @ -83,9 +83,7 @@ export interface CoreMainMenuHomeHandlerToDisplay extends CoreDelegateToDisplay, | |||||||
|  * Service to interact with plugins to be shown in the main menu. Provides functions to register a plugin |  * Service to interact with plugins to be shown in the main menu. Provides functions to register a plugin | ||||||
|  * and notify an update in the data. |  * and notify an update in the data. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreMainMenuHomeDelegateService extends CoreSortedDelegate<CoreMainMenuHomeHandlerToDisplay, CoreMainMenuHomeHandler> { | export class CoreMainMenuHomeDelegateService extends CoreSortedDelegate<CoreMainMenuHomeHandlerToDisplay, CoreMainMenuHomeHandler> { | ||||||
| 
 | 
 | ||||||
|     protected featurePrefix = 'CoreMainMenuHomeDelegate_'; |     protected featurePrefix = 'CoreMainMenuHomeDelegate_'; | ||||||
|  | |||||||
| @ -95,9 +95,7 @@ export interface CoreMainMenuHandlerToDisplay extends CoreDelegateToDisplay, Cor | |||||||
|  * Service to interact with plugins to be shown in the main menu. Provides functions to register a plugin |  * Service to interact with plugins to be shown in the main menu. Provides functions to register a plugin | ||||||
|  * and notify an update in the data. |  * and notify an update in the data. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreMainMenuDelegateService extends CoreSortedDelegate<CoreMainMenuHandlerToDisplay, CoreMainMenuHandler> { | export class CoreMainMenuDelegateService extends CoreSortedDelegate<CoreMainMenuHandlerToDisplay, CoreMainMenuHandler> { | ||||||
| 
 | 
 | ||||||
|     protected featurePrefix = 'CoreMainMenuDelegate_'; |     protected featurePrefix = 'CoreMainMenuDelegate_'; | ||||||
|  | |||||||
| @ -25,9 +25,7 @@ import { makeSingleton } from '@singletons'; | |||||||
| /** | /** | ||||||
|  * Service that provides some features regarding Main Menu. |  * Service that provides some features regarding Main Menu. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreMainMenuProvider { | export class CoreMainMenuProvider { | ||||||
| 
 | 
 | ||||||
|     static readonly NUM_MAIN_HANDLERS = 4; |     static readonly NUM_MAIN_HANDLERS = 4; | ||||||
|  | |||||||
| @ -24,4 +24,8 @@ | |||||||
|             cursor: pointer; |             cursor: pointer; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     ion-label { | ||||||
|  |         margin: 0; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -22,9 +22,7 @@ import { makeSingleton } from '@singletons'; | |||||||
| /** | /** | ||||||
|  * Service that enables adding a history to a search box. |  * Service that enables adding a history to a search box. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreSearchHistoryProvider { | export class CoreSearchHistoryProvider { | ||||||
| 
 | 
 | ||||||
|     protected static readonly HISTORY_LIMIT = 10; |     protected static readonly HISTORY_LIMIT = 10; | ||||||
|  | |||||||
| @ -63,9 +63,7 @@ export type CoreSettingsHandlerToDisplay = CoreDelegateToDisplay & CoreSettingsH | |||||||
|  * Service to interact with addons to be shown in app settings. Provides functions to register a plugin |  * Service to interact with addons to be shown in app settings. Provides functions to register a plugin | ||||||
|  * and notify an update in the data. |  * and notify an update in the data. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreSettingsDelegateService extends CoreSortedDelegate<CoreSettingsHandlerToDisplay, CoreSettingsHandler> { | export class CoreSettingsDelegateService extends CoreSortedDelegate<CoreSettingsHandlerToDisplay, CoreSettingsHandler> { | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|  | |||||||
| @ -48,9 +48,7 @@ export const enum CoreColorScheme { | |||||||
| /** | /** | ||||||
|  * Settings helper service. |  * Settings helper service. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreSettingsHelperProvider { | export class CoreSettingsHelperProvider { | ||||||
| 
 | 
 | ||||||
|     protected syncPromises: { [s: string]: Promise<void> } = {}; |     protected syncPromises: { [s: string]: Promise<void> } = {}; | ||||||
|  | |||||||
| @ -60,7 +60,7 @@ | |||||||
| </ion-content> | </ion-content> | ||||||
| 
 | 
 | ||||||
| <ng-template #allCourseList> | <ng-template #allCourseList> | ||||||
|     <ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/courses/all" detail> |     <ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/main/home/courses/all" detail> | ||||||
|         <ion-icon name="fas-graduation-cap" fixed-width slot="start"></ion-icon> |         <ion-icon name="fas-graduation-cap" fixed-width slot="start"></ion-icon> | ||||||
|         <ion-label> |         <ion-label> | ||||||
|             <h2>{{ 'core.courses.availablecourses' | translate}}</h2> |             <h2>{{ 'core.courses.availablecourses' | translate}}</h2> | ||||||
| @ -76,7 +76,7 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #categories> | <ng-template #categories> | ||||||
|     <ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/courses/categories" detail> |     <ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/main/home/courses/categories" detail> | ||||||
|         <ion-icon name="folder" slot="start"></ion-icon> |         <ion-icon name="folder" slot="start"></ion-icon> | ||||||
|         <ion-label> |         <ion-label> | ||||||
|             <h2>{{ 'core.courses.categories' | translate}}</h2> |             <h2>{{ 'core.courses.categories' | translate}}</h2> | ||||||
| @ -85,7 +85,7 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #enrolledCourseList> | <ng-template #enrolledCourseList> | ||||||
|     <ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/courses/my" detail> |     <ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/main/home/courses/my" detail> | ||||||
|         <ion-icon name="fas-graduation-cap" fixed-width slot="start"> |         <ion-icon name="fas-graduation-cap" fixed-width slot="start"> | ||||||
|         </ion-icon> |         </ion-icon> | ||||||
|         <ion-label><h2>{{ 'core.courses.mycourses' | translate}}</h2></ion-label> |         <ion-label><h2>{{ 'core.courses.mycourses' | translate}}</h2></ion-label> | ||||||
| @ -93,7 +93,7 @@ | |||||||
| </ng-template> | </ng-template> | ||||||
| 
 | 
 | ||||||
| <ng-template #courseSearch> | <ng-template #courseSearch> | ||||||
|     <ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/courses/search" detail> |     <ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/main/home/courses/search" detail> | ||||||
|         <ion-icon name="fas-search" slot="start"></ion-icon> |         <ion-icon name="fas-search" slot="start"></ion-icon> | ||||||
|         <ion-label><h2>{{ 'core.courses.searchcourses' | translate}}</h2></ion-label> |         <ion-label><h2>{{ 'core.courses.searchcourses' | translate}}</h2></ion-label> | ||||||
|     </ion-item> |     </ion-item> | ||||||
|  | |||||||
| @ -194,7 +194,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { | |||||||
|      * Open page to manage courses storage. |      * Open page to manage courses storage. | ||||||
|      */ |      */ | ||||||
|     manageCoursesStorage(): void { |     manageCoursesStorage(): void { | ||||||
|         // @todo this.navCtrl.navigateForward(['/courses/storage']);
 |         // @todo this.navCtrl.navigateForward(['/main/home/courses/storage']);
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -35,9 +35,7 @@ export enum FrontPageItemNames { | |||||||
| /** | /** | ||||||
|  * Service that provides some features regarding site home. |  * Service that provides some features regarding site home. | ||||||
|  */ |  */ | ||||||
| @Injectable({ | @Injectable({ providedIn: 'root' }) | ||||||
|     providedIn: 'root', |  | ||||||
| }) |  | ||||||
| export class CoreSiteHomeProvider { | export class CoreSiteHomeProvider { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								src/core/features/tag/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/core/features/tag/lang/en.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | { | ||||||
|  |     "defautltagcoll": "Default collection", | ||||||
|  |     "errorareanotsupported": "This tag area is not supported by the app.", | ||||||
|  |     "inalltagcoll": "Everywhere", | ||||||
|  |     "itemstaggedwith": "{{$a.tagarea}} tagged with \"{{$a.tag}}\"", | ||||||
|  |     "notagsfound": "No tags matching \"{{$a}}\" found", | ||||||
|  |     "noresultsfor": "No results for \"{{$a}}\"", | ||||||
|  |     "searchtags": "Search tags", | ||||||
|  |     "showingfirsttags": "Showing {{$a}} most popular tags", | ||||||
|  |     "tag": "Tag", | ||||||
|  |     "tagarea_course": "Courses", | ||||||
|  |     "tagarea_course_modules": "Activities and resources", | ||||||
|  |     "tagarea_post": "Blog posts", | ||||||
|  |     "tagarea_user": "User interests", | ||||||
|  |     "tags": "Tags", | ||||||
|  |     "warningareasnotsupported": "Some of the tag areas are not displayed because they are not supported by the app." | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								src/core/features/tag/pages/index-area/index-area.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/core/features/tag/pages/index-area/index-area.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | <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.tag.itemstaggedwith' | translate: { $a: {tagarea: areaNameKey | translate, tag: tagName} } }} | ||||||
|  |         </ion-title> | ||||||
|  |     </ion-toolbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)"> | ||||||
|  |         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |     </ion-refresher> | ||||||
|  |     <core-loading [hideUntil]="loaded"> | ||||||
|  |         <!-- @todo <ng-container *ngIf="loaded"> | ||||||
|  |             <core-dynamic-component [component]="areaComponent" [data]="{items: items}"></core-dynamic-component> | ||||||
|  |         </ng-container>--> | ||||||
|  |         <core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError"> | ||||||
|  |         </core-infinite-loading> | ||||||
|  |     </core-loading> | ||||||
|  | </ion-content> | ||||||
| @ -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 { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | import { RouterModule, Routes } from '@angular/router'; | ||||||
|  | import { CommonModule } from '@angular/common'; | ||||||
|  | 
 | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | 
 | ||||||
|  | import { CoreTagIndexAreaPage } from './index-area.page'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         component: CoreTagIndexAreaPage, | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     declarations: [ | ||||||
|  |         CoreTagIndexAreaPage, | ||||||
|  |     ], | ||||||
|  |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |     ], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class CoreTagIndexAreaPageModule {} | ||||||
							
								
								
									
										175
									
								
								src/core/features/tag/pages/index-area/index-area.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								src/core/features/tag/pages/index-area/index-area.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,175 @@ | |||||||
|  | // (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 } from '@angular/core'; | ||||||
|  | import { IonInfiniteScroll, IonRefresher } from '@ionic/angular'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { CoreTag } from '@features/tag/services/tag'; | ||||||
|  | import { CoreTagFeedElement } from '../../services/tag-helper'; | ||||||
|  | import { ActivatedRoute } from '@angular/router'; | ||||||
|  | import { CoreTagAreaDelegate } from '../../services/tag-area-delegate'; | ||||||
|  | import { Translate } from '@singletons'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Page that displays the tag index area. | ||||||
|  |  * | ||||||
|  |  * @todo testing. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'page-core-tag-index-area', | ||||||
|  |     templateUrl: 'index-area.html', | ||||||
|  | }) | ||||||
|  | export class CoreTagIndexAreaPage implements OnInit { | ||||||
|  | 
 | ||||||
|  |     tagId = 0; | ||||||
|  |     tagName = ''; | ||||||
|  |     collectionId = 0; | ||||||
|  |     areaId = 0; | ||||||
|  |     fromContextId = 0; | ||||||
|  |     contextId = 0; | ||||||
|  |     recursive = true; | ||||||
|  | 
 | ||||||
|  |     areaNameKey = ''; | ||||||
|  |     loaded = false; | ||||||
|  |     componentName?: string; | ||||||
|  |     itemType?: string; | ||||||
|  |     items: CoreTagFeedElement[] = []; | ||||||
|  |     nextPage = 0; | ||||||
|  |     canLoadMore = false; | ||||||
|  |     areaComponent: any; // @todo
 | ||||||
|  |     loadMoreError = false; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         protected route: ActivatedRoute, | ||||||
|  |     ) { } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * View loaded. | ||||||
|  |      */ | ||||||
|  |     async ngOnInit(): Promise<void> { | ||||||
|  | 
 | ||||||
|  |         const navParams = this.route.snapshot.queryParamMap; | ||||||
|  | 
 | ||||||
|  |         this.tagId = navParams['tagId'] ? parseInt(navParams['tagId'], 10) : this.tagId; | ||||||
|  |         this.tagName = navParams['tagName'] || this.tagName; | ||||||
|  |         this.collectionId = navParams['collectionId'] ? parseInt(navParams['collectionId'], 10) : this.collectionId; | ||||||
|  |         this.areaId = navParams['areaId'] ? parseInt(navParams['areaId']!, 10) : this.areaId; | ||||||
|  |         this.fromContextId = parseInt(navParams['fromContextId'], 10) || this.fromContextId; | ||||||
|  |         this.contextId = navParams['contextId'] ? parseInt(navParams['contextId'], 10) : this.contextId; | ||||||
|  |         this.recursive = typeof navParams['recursive'] == 'undefined'? true : navParams['recursive']; | ||||||
|  | 
 | ||||||
|  |         this.areaNameKey = navParams['areaNameKey']; | ||||||
|  |         // Pass the the following parameters to avoid fetching the first page.
 | ||||||
|  |         this.componentName = navParams['componentName']; | ||||||
|  |         this.itemType = navParams['itemType']; | ||||||
|  |         this.items = []; // @todo navParams['items'] || [];
 | ||||||
|  |         this.nextPage = navParams.has('nextPage') ? parseInt(navParams['nextPage']!, 10) : 0; | ||||||
|  |         this.canLoadMore = !!navParams['canLoadMore']; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             if (!this.componentName || !this.itemType || !this.items.length || this.nextPage == 0) { | ||||||
|  |                 await this.fetchData(true); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             this.areaComponent = await CoreTagAreaDelegate.instance.getComponent(this.componentName!, this.itemType!); | ||||||
|  |         } finally { | ||||||
|  |             this.loaded = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch next page of the tag index area. | ||||||
|  |      * | ||||||
|  |      * @param refresh Whether to refresh the data or fetch a new page. | ||||||
|  |      * @return Resolved when done. | ||||||
|  |      */ | ||||||
|  |     async fetchData(refresh: boolean = false): Promise<void> { | ||||||
|  |         this.loadMoreError = false; | ||||||
|  |         const page = refresh ? 0 : this.nextPage; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             const areas = await CoreTag.instance.getTagIndexPerArea( | ||||||
|  |                 this.tagId, | ||||||
|  |                 this.tagName, | ||||||
|  |                 this.collectionId, | ||||||
|  |                 this.areaId, | ||||||
|  |                 this.fromContextId, | ||||||
|  |                 this.contextId, | ||||||
|  |                 this.recursive, | ||||||
|  |                 page, | ||||||
|  |             ); | ||||||
|  |             const area = areas[0]; | ||||||
|  | 
 | ||||||
|  |             const items = await CoreTagAreaDelegate.instance.parseContent(area.component, area.itemtype, area.content); | ||||||
|  |             if (!items || !items.length) { | ||||||
|  |                 // Tag area not supported.
 | ||||||
|  |                 throw Translate.instance.instant('core.tag.errorareanotsupported'); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (page == 0) { | ||||||
|  |                 this.items = items; | ||||||
|  |             } else { | ||||||
|  |                 this.items.push(...items); | ||||||
|  |             } | ||||||
|  |             this.componentName = area.component; | ||||||
|  |             this.itemType = area.itemtype; | ||||||
|  |             this.areaNameKey = CoreTagAreaDelegate.instance.getDisplayNameKey(area.component, area.itemtype); | ||||||
|  |             this.canLoadMore = !!area.nextpageurl; | ||||||
|  |             this.nextPage = page + 1; | ||||||
|  |         } catch (error) { | ||||||
|  |             this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
 | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading tag index'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Load more items. | ||||||
|  |      * | ||||||
|  |      * @param infiniteComplete Infinite scroll complete function. | ||||||
|  |      * @return Resolved when done. | ||||||
|  |      */ | ||||||
|  |     async loadMore(infiniteComplete?: CustomEvent<IonInfiniteScroll>): Promise<void> { | ||||||
|  |         try { | ||||||
|  |             await this.fetchData(); | ||||||
|  |         } finally { | ||||||
|  |             infiniteComplete?.detail.complete(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Refresh data. | ||||||
|  |      * | ||||||
|  |      * @param refresher Refresher. | ||||||
|  |      */ | ||||||
|  |     async refreshData(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||||
|  |         try { | ||||||
|  |             await CoreTag.instance.invalidateTagIndexPerArea( | ||||||
|  |                 this.tagId, | ||||||
|  |                 this.tagName, | ||||||
|  |                 this.collectionId, | ||||||
|  |                 this.areaId, | ||||||
|  |                 this.fromContextId, | ||||||
|  |                 this.contextId, | ||||||
|  |                 this.recursive, | ||||||
|  |             ); | ||||||
|  |         } finally { | ||||||
|  |             try { | ||||||
|  |                 await this.fetchData(true); | ||||||
|  |             } finally { | ||||||
|  |                 refresher?.detail.complete(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								src/core/features/tag/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/core/features/tag/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | <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.tag.tag' | translate }}: {{ tagName }}</ion-title> | ||||||
|  |     </ion-toolbar> | ||||||
|  | </ion-header> | ||||||
|  | <!--@todo <core-split-view>--> | ||||||
|  | <ion-content> | ||||||
|  |     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)"> | ||||||
|  |         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |     </ion-refresher> | ||||||
|  |     <core-loading [hideUntil]="loaded"> | ||||||
|  |         <ion-list *ngIf="hasUnsupportedAreas || areas.length"> | ||||||
|  |             <ion-item *ngIf="hasUnsupportedAreas" class="core-warning-item"> | ||||||
|  |                 <ion-icon slot="start" name="fas-exclamation-triangle" color="warning"></ion-icon> | ||||||
|  |                 <ion-label class="ion-text-wrap">{{ 'core.tag.warningareasnotsupported' | translate }}</ion-label> | ||||||
|  |             </ion-item> | ||||||
|  |             <ion-item class="ion-text-wrap" *ngFor="let area of areas" [title]="area.nameKey | translate" | ||||||
|  |                 (click)="openArea(area)" [class.core-split-item-selected]="area!.id == selectedAreaId"> | ||||||
|  |                 <ion-label> | ||||||
|  |                     <h2>{{ area!.nameKey | translate }}</h2> | ||||||
|  |                 </ion-label> | ||||||
|  |                 <ion-badge slot="end" *ngIf="area!.badge">{{ area!.badge }}</ion-badge> | ||||||
|  |             </ion-item> | ||||||
|  |         </ion-list> | ||||||
|  |         <core-empty-box icon="fa-tag" *ngIf="!hasUnsupportedAreas && (!areas || !areas.length)" | ||||||
|  |             [message]="'core.tag.noresultsfor' | translate: { $a: tagName }"></core-empty-box> | ||||||
|  |     </core-loading> | ||||||
|  | </ion-content> | ||||||
							
								
								
									
										47
									
								
								src/core/features/tag/pages/index/index.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/core/features/tag/pages/index/index.page.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 { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | import { CommonModule } from '@angular/common'; | ||||||
|  | import { RouterModule, Routes } from '@angular/router'; | ||||||
|  | 
 | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | 
 | ||||||
|  | import { CoreTagIndexPage } from './index.page'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         component: CoreTagIndexPage, | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     declarations: [ | ||||||
|  |         CoreTagIndexPage, | ||||||
|  |     ], | ||||||
|  |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |     ], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class CoreTagIndexPageModule {} | ||||||
							
								
								
									
										186
									
								
								src/core/features/tag/pages/index/index.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/core/features/tag/pages/index/index.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,186 @@ | |||||||
|  | // (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 } from '@angular/core'; | ||||||
|  | import { IonRefresher } from '@ionic/angular'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | // import { CoreSplitViewComponent } from '@components/split-view/split-view';
 | ||||||
|  | import { CoreTag } from '@features/tag/services/tag'; | ||||||
|  | import { CoreTagAreaDelegate } from '@/core/features/tag/services/tag-area-delegate'; | ||||||
|  | import { ActivatedRoute, Router } from '@angular/router'; | ||||||
|  | import { CoreTagFeedElement } from '../../services/tag-helper'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Page that displays the tag index. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'page-core-tag-index', | ||||||
|  |     templateUrl: 'index.html', | ||||||
|  | }) | ||||||
|  | export class CoreTagIndexPage implements OnInit { | ||||||
|  | 
 | ||||||
|  |     // @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
 | ||||||
|  | 
 | ||||||
|  |     tagId = 0; | ||||||
|  |     tagName = ''; | ||||||
|  |     collectionId = 0; | ||||||
|  |     areaId = 0; | ||||||
|  |     fromContextId = 0; | ||||||
|  |     contextId = 0; | ||||||
|  |     recursive = true; | ||||||
|  |     loaded = false; | ||||||
|  |     selectedAreaId?: number; | ||||||
|  |     hasUnsupportedAreas = false; | ||||||
|  | 
 | ||||||
|  |     areas: (CoreTagAreaDisplay | null)[] = []; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         protected route: ActivatedRoute, | ||||||
|  |         protected router: Router, | ||||||
|  |     ) { } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * View loaded. | ||||||
|  |      */ | ||||||
|  |     async ngOnInit(): Promise<void> { | ||||||
|  |         const navParams = this.route.snapshot.queryParams; | ||||||
|  | 
 | ||||||
|  |         this.tagId = navParams['tagId'] ? parseInt(navParams['tagId'], 10) : this.tagId; | ||||||
|  |         this.tagName = navParams['tagName'] || this.tagName; | ||||||
|  |         this.collectionId = navParams['collectionId'] ? parseInt(navParams['collectionId'], 10) : this.collectionId; | ||||||
|  |         this.areaId = navParams['areaId'] ? parseInt(navParams['areaId']!, 10) : this.areaId; | ||||||
|  |         this.fromContextId = parseInt(navParams['fromContextId'], 10) || this.fromContextId; | ||||||
|  |         this.contextId = navParams['contextId'] ? parseInt(navParams['contextId'], 10) : this.contextId; | ||||||
|  |         this.recursive = typeof navParams['recursive'] == 'undefined'? true : navParams['recursive']; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             await this.fetchData(); | ||||||
|  |             /* if (this.splitviewCtrl.isOn() && this.areas && this.areas.length > 0) { | ||||||
|  |                 const area = this.areas.find((area) => area.id == this.areaId); | ||||||
|  |                 this.openArea(area || this.areas[0]); | ||||||
|  |             }*/ | ||||||
|  |         } finally { | ||||||
|  |             this.loaded = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch first page of tag index per area. | ||||||
|  |      * | ||||||
|  |      * @return Resolved when done. | ||||||
|  |      */ | ||||||
|  |     async fetchData(): Promise<void> { | ||||||
|  |         try { | ||||||
|  |             const areas = await CoreTag.instance.getTagIndexPerArea( | ||||||
|  |                 this.tagId, | ||||||
|  |                 this.tagName, | ||||||
|  |                 this.collectionId, | ||||||
|  |                 this.areaId, | ||||||
|  |                 this.fromContextId, | ||||||
|  |                 this.contextId, | ||||||
|  |                 this.recursive, | ||||||
|  |                 0, | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             this.areas = []; | ||||||
|  |             this.hasUnsupportedAreas = false; | ||||||
|  | 
 | ||||||
|  |             const areasDisplay: (CoreTagAreaDisplay | null)[] = await Promise.all(areas.map(async (area) => { | ||||||
|  |                 const items = await CoreTagAreaDelegate.instance.parseContent(area.component, area.itemtype, area.content); | ||||||
|  | 
 | ||||||
|  |                 if (!items || !items.length) { | ||||||
|  |                     // Tag area not supported, skip.
 | ||||||
|  |                     this.hasUnsupportedAreas = true; | ||||||
|  | 
 | ||||||
|  |                     return null; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return { | ||||||
|  |                     id: area.ta, | ||||||
|  |                     componentName: area.component, | ||||||
|  |                     itemType: area.itemtype, | ||||||
|  |                     nameKey: CoreTagAreaDelegate.instance.getDisplayNameKey(area.component, area.itemtype), | ||||||
|  |                     items, | ||||||
|  |                     canLoadMore: !!area.nextpageurl, | ||||||
|  |                     badge: items && items.length ? items.length + (area.nextpageurl ? '+' : '') : '', | ||||||
|  |                 }; | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |             this.areas = areasDisplay.filter((area) => area != null); | ||||||
|  | 
 | ||||||
|  |         } catch (error) { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading tag index'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Refresh data. | ||||||
|  |      * | ||||||
|  |      * @param refresher Refresher. | ||||||
|  |      */ | ||||||
|  |     refreshData(refresher?: CustomEvent<IonRefresher>): void { | ||||||
|  |         CoreTag.instance.invalidateTagIndexPerArea( | ||||||
|  |             this.tagId, | ||||||
|  |             this.tagName, | ||||||
|  |             this.collectionId, | ||||||
|  |             this.areaId, | ||||||
|  |             this.fromContextId, | ||||||
|  |             this.contextId, | ||||||
|  |             this.recursive, | ||||||
|  |         ).finally(() => { | ||||||
|  |             this.fetchData().finally(() => { | ||||||
|  |                 refresher?.detail.complete(); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Navigate to an index area. | ||||||
|  |      * | ||||||
|  |      * @param area Area. | ||||||
|  |      */ | ||||||
|  |     openArea(area: CoreTagAreaDisplay): void { | ||||||
|  |         this.selectedAreaId = area.id; | ||||||
|  | 
 | ||||||
|  |         const params = { | ||||||
|  |             tagId: this.tagId, | ||||||
|  |             tagName: this.tagName, | ||||||
|  |             collectionId: this.collectionId, | ||||||
|  |             areaId: area.id, | ||||||
|  |             fromContextId: this.fromContextId, | ||||||
|  |             contextId: this.contextId, | ||||||
|  |             recursive: this.recursive, | ||||||
|  |             areaNameKey: area.nameKey, | ||||||
|  |             componentName: area.componentName, | ||||||
|  |             itemType: area.itemType, | ||||||
|  |             items: area.items.slice(), | ||||||
|  |             canLoadMore: area.canLoadMore, | ||||||
|  |             nextPage: 1, | ||||||
|  |         }; | ||||||
|  |         // this.splitviewCtrl.push('core-tag-index-area', params);
 | ||||||
|  |         this.router.navigate(['core-tag-index-area'], { queryParams: params }); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export type CoreTagAreaDisplay = { | ||||||
|  |     id: number; | ||||||
|  |     componentName: string; | ||||||
|  |     itemType: string; | ||||||
|  |     nameKey: string; | ||||||
|  |     items: CoreTagFeedElement[]; | ||||||
|  |     canLoadMore: boolean; | ||||||
|  |     badge: string; | ||||||
|  | }; | ||||||
							
								
								
									
										45
									
								
								src/core/features/tag/pages/search/search.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/core/features/tag/pages/search/search.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | <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.tag.searchtags' | translate }}</ion-title> | ||||||
|  |     </ion-toolbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)"> | ||||||
|  |         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|  |     </ion-refresher> | ||||||
|  |     <ion-grid class="safe-area-page"> | ||||||
|  |         <ion-row> | ||||||
|  |             <ion-col size="12" [attr.col-sm-6]="collections && collections.length > 1 ? '' : null"> | ||||||
|  |                 <core-search-box (onSubmit)="searchTags($event)" (onClear)="searchTags('')" [initialSearch]="query" | ||||||
|  |                     [disabled]="searching" autocorrect="off" [spellcheck]="false" [autoFocus]="false" [lengthCheck]="0" | ||||||
|  |                     searchArea="CoreTag"></core-search-box> | ||||||
|  |             </ion-col> | ||||||
|  |             <ion-col size="12" size-sm="6" *ngIf="collections && collections.length > 1"> | ||||||
|  |                 <ion-select class="ion-text-start" [(ngModel)]="collectionId" (ngModelChange)="searchTags(query)" | ||||||
|  |                     [disabled]="searching" interface="popover" class="core-button-select"> | ||||||
|  |                     <ion-select-option [value]="0">{{ 'core.tag.inalltagcoll' | translate }}</ion-select-option> | ||||||
|  |                     <ion-select-option *ngFor="let collection of collections" [value]="collection.id"> | ||||||
|  |                         {{ collection.name }}</ion-select-option> | ||||||
|  |                 </ion-select> | ||||||
|  |             </ion-col> | ||||||
|  |         </ion-row> | ||||||
|  |     </ion-grid> | ||||||
|  |     <core-loading [hideUntil]="loaded && !searching" class="safe-area-page"> | ||||||
|  |         <core-empty-box *ngIf="!cloud || !cloud!.tags || !cloud!.tags.length" icon="fas-tags" | ||||||
|  |             [message]="'core.tag.notagsfound' | translate: {$a: query}"></core-empty-box> | ||||||
|  | 
 | ||||||
|  |         <ng-container *ngIf="cloud && cloud!.tags && cloud!.tags.length > 0"> | ||||||
|  |             <div class="ion-text-center core-tag-cloud"> | ||||||
|  |                 <ion-badge *ngFor="let tag of cloud!.tags" (click)="openTag(tag)" class="ion-text-wrap"> | ||||||
|  |                     <span [class]="'size' + tag.size">{{ tag.name }}</span> | ||||||
|  |                 </ion-badge> | ||||||
|  |             </div> | ||||||
|  |             <p *ngIf="cloud!.tags.length < cloud!.totalcount" class="ion-text-center"> | ||||||
|  |                 {{ 'core.tag.showingfirsttags' | translate: {$a: cloud!.tags.length} }} | ||||||
|  |             </p> | ||||||
|  |         </ng-container> | ||||||
|  |     </core-loading> | ||||||
|  | </ion-content> | ||||||
							
								
								
									
										50
									
								
								src/core/features/tag/pages/search/search.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/core/features/tag/pages/search/search.page.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | // (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 { IonicModule } from '@ionic/angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | import { CommonModule } from '@angular/common'; | ||||||
|  | import { RouterModule, Routes } from '@angular/router'; | ||||||
|  | 
 | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | import { CoreSearchComponentsModule } from '@features/search/components/components.module'; | ||||||
|  | 
 | ||||||
|  | import { CoreTagSearchPage } from './search.page'; | ||||||
|  | import { FormsModule } from '@angular/forms'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         component: CoreTagSearchPage, | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | @NgModule({ | ||||||
|  |     declarations: [ | ||||||
|  |         CoreTagSearchPage, | ||||||
|  |     ], | ||||||
|  |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|  |         CommonModule, | ||||||
|  |         IonicModule, | ||||||
|  |         FormsModule, | ||||||
|  |         TranslateModule.forChild(), | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |         CoreSearchComponentsModule, | ||||||
|  |     ], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class CoreTagSearchPageModule {} | ||||||
							
								
								
									
										142
									
								
								src/core/features/tag/pages/search/search.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/core/features/tag/pages/search/search.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,142 @@ | |||||||
|  | // (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 } from '@angular/core'; | ||||||
|  | import { IonRefresher, NavController } from '@ionic/angular'; | ||||||
|  | import { ActivatedRoute } from '@angular/router'; | ||||||
|  | 
 | ||||||
|  | import { CoreApp } from '@services/app'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreTextUtils } from '@services/utils/text'; | ||||||
|  | import { CoreTagCloud, CoreTagCollection, CoreTagCloudTag, CoreTag } from '@features/tag/services/tag'; | ||||||
|  | import { Translate } from '@singletons'; | ||||||
|  | import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Page that displays most used tags and allows searching. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'page-core-tag-search', | ||||||
|  |     templateUrl: 'search.html', | ||||||
|  |     styleUrls: ['search.scss'], | ||||||
|  | }) | ||||||
|  | export class CoreTagSearchPage implements OnInit { | ||||||
|  | 
 | ||||||
|  |     collectionId!: number; | ||||||
|  |     query!: string; | ||||||
|  |     collections: CoreTagCollection[] = []; | ||||||
|  |     cloud?: CoreTagCloud; | ||||||
|  |     loaded = false; | ||||||
|  |     searching = false; | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         protected navCtrl: NavController, | ||||||
|  |         protected route: ActivatedRoute, | ||||||
|  |     ) { | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * View loaded. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  |         // @todo: Check params work.
 | ||||||
|  |         this.collectionId = this.route.snapshot.queryParamMap.has('collectionId') ? | ||||||
|  |             parseInt(this.route.snapshot.queryParamMap.get('collectionId')!, 10) : 0; | ||||||
|  |         this.query = this.route.snapshot.queryParamMap.get('query') || ''; | ||||||
|  | 
 | ||||||
|  |         this.fetchData().finally(() => { | ||||||
|  |             this.loaded = true; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fetchData(): Promise<void> { | ||||||
|  |         try { | ||||||
|  |             await Promise.all([ | ||||||
|  |                 this.fetchCollections(), | ||||||
|  |                 this.fetchTags(), | ||||||
|  |             ]); | ||||||
|  |         } catch (error) { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading tags.'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch tag collections. | ||||||
|  |      * | ||||||
|  |      * @return Resolved when done. | ||||||
|  |      */ | ||||||
|  |     async fetchCollections(): Promise<void> { | ||||||
|  |         const collections = await CoreTag.instance.getTagCollections(); | ||||||
|  | 
 | ||||||
|  |         collections.forEach((collection) => { | ||||||
|  |             if (!collection.name && collection.isdefault) { | ||||||
|  |                 collection.name = Translate.instance.instant('core.tag.defautltagcoll'); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.collections = collections; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch tags. | ||||||
|  |      * | ||||||
|  |      * @return Resolved when done. | ||||||
|  |      */ | ||||||
|  |     async fetchTags(): Promise<void> { | ||||||
|  |         this.cloud = await CoreTag.instance.getTagCloud(this.collectionId, undefined, undefined, this.query); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Go to tag index page. | ||||||
|  |      */ | ||||||
|  |     openTag(tag: CoreTagCloudTag): void { | ||||||
|  |         const url = CoreTextUtils.instance.decodeURI(tag.viewurl); | ||||||
|  |         CoreContentLinksHelper.instance.handleLink(url); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Refresh data. | ||||||
|  |      * | ||||||
|  |      * @param refresher Refresher event. | ||||||
|  |      */ | ||||||
|  |     refreshData(refresher?: CustomEvent<IonRefresher>): void { | ||||||
|  |         CoreUtils.instance.allPromises([ | ||||||
|  |             CoreTag.instance.invalidateTagCollections(), | ||||||
|  |             CoreTag.instance.invalidateTagCloud(this.collectionId, undefined, undefined, this.query), | ||||||
|  |         ]).finally(() => this.fetchData().finally(() => { | ||||||
|  |             refresher?.detail.complete(); | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Search tags. | ||||||
|  |      * | ||||||
|  |      * @param query Search query. | ||||||
|  |      * @return Resolved when done. | ||||||
|  |      */ | ||||||
|  |     searchTags(query: string): Promise<void> { | ||||||
|  |         this.searching = true; | ||||||
|  |         this.query = query; | ||||||
|  |         CoreApp.instance.closeKeyboard(); | ||||||
|  | 
 | ||||||
|  |         return this.fetchTags().catch((error) => { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading tags.'); | ||||||
|  |         }).finally(() => { | ||||||
|  |             this.searching = false; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										95
									
								
								src/core/features/tag/pages/search/search.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/core/features/tag/pages/search/search.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | |||||||
|  | :host { | ||||||
|  |     core-search-box ion-card { | ||||||
|  |         width: 100% !important; | ||||||
|  |         margin: 0 !important; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .core-tag-cloud ion-badge { | ||||||
|  |         margin: 8px; | ||||||
|  |         cursor: pointer; | ||||||
|  | 
 | ||||||
|  |         .size20 { | ||||||
|  |             font-size: 3.4rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size19 { | ||||||
|  |             font-size: 3.3rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size18 { | ||||||
|  |             font-size: 3.2rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size17 { | ||||||
|  |             font-size: 3.1rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size16 { | ||||||
|  |             font-size: 3rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size15 { | ||||||
|  |             font-size: 2.9rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size14 { | ||||||
|  |             font-size: 2.8rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size13 { | ||||||
|  |             font-size: 2.7rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size12 { | ||||||
|  |             font-size: 2.6rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size11 { | ||||||
|  |             font-size: 2.5rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size10 { | ||||||
|  |             font-size: 2.4rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size9 { | ||||||
|  |             font-size: 2.3rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size8 { | ||||||
|  |             font-size: 2.2rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size7 { | ||||||
|  |             font-size: 2.1rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size6 { | ||||||
|  |             font-size: 2rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size5 { | ||||||
|  |             font-size: 1.9rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size4 { | ||||||
|  |             font-size: 1.8rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size3 { | ||||||
|  |             font-size: 1.7rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size2 { | ||||||
|  |             font-size: 1.6rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size1 { | ||||||
|  |             font-size: 1.5rem; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .size0 { | ||||||
|  |             font-size: 1.4rem; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								src/core/features/tag/services/handlers/index.link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/core/features/tag/services/handlers/index.link.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 { Params } from '@angular/router'; | ||||||
|  | import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||||
|  | import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||||
|  | import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreTag } from '../tag'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Handler to treat links to tag index. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class CoreTagIndexLinkHandlerService extends CoreContentLinksHandlerBase { | ||||||
|  | 
 | ||||||
|  |     name = 'CoreTagIndexLinkHandler'; | ||||||
|  |     pattern = /\/tag\/index\.php/; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the list of actions for a link (url). | ||||||
|  |      * | ||||||
|  |      * @param siteIds List of sites the URL belongs to. | ||||||
|  |      * @param url The URL to treat. | ||||||
|  |      * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} | ||||||
|  |      * @param courseId Course ID related to the URL. Optional but recommended. | ||||||
|  |      * @param data Extra data to handle the URL. | ||||||
|  |      * @return List of (or promise resolved with list of) actions. | ||||||
|  |      */ | ||||||
|  |     getActions( | ||||||
|  |         siteIds: string[], | ||||||
|  |         url: string, | ||||||
|  |         params: Params, | ||||||
|  |     ): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { | ||||||
|  |         return [{ | ||||||
|  |             action: (siteId): void => { | ||||||
|  |                 const pageParams = { | ||||||
|  |                     tagId: parseInt(params.id, 10) || 0, | ||||||
|  |                     tagName: params.tag || '', | ||||||
|  |                     collectionId: parseInt(params.tc, 10) || 0, | ||||||
|  |                     areaId: parseInt(params.ta, 10) || 0, | ||||||
|  |                     fromContextId: parseInt(params.from, 10) || 0, | ||||||
|  |                     contextId: parseInt(params.ctx, 10) || 0, | ||||||
|  |                     recursive: parseInt(params.rec, 10) || 1, | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 if (!pageParams.tagId && (!pageParams.tagName || !pageParams.collectionId)) { | ||||||
|  |                     CoreContentLinksHelper.instance.goInSite('/main/tag/search', {}, siteId); | ||||||
|  |                 } else if (pageParams.areaId) { | ||||||
|  |                     CoreContentLinksHelper.instance.goInSite('/main/tag/index-area', pageParams, siteId); | ||||||
|  |                 } else { | ||||||
|  |                     CoreContentLinksHelper.instance.goInSite('/main/tag/index', pageParams, siteId); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |         }]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if the handler is enabled for a certain site (site + user) and a URL. | ||||||
|  |      * If not defined, defaults to true. | ||||||
|  |      * | ||||||
|  |      * @param siteId The site ID. | ||||||
|  |      * @param url The URL to treat. | ||||||
|  |      * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} | ||||||
|  |      * @param courseId Course ID related to the URL. Optional but recommended. | ||||||
|  |      * @return Whether the handler is enabled for the URL and site. | ||||||
|  |      */ | ||||||
|  |     isEnabled(siteId: string): boolean | Promise<boolean> { | ||||||
|  |         return CoreTag.instance.areTagsAvailable(siteId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class CoreTagIndexLinkHandler extends makeSingleton(CoreTagIndexLinkHandlerService) {} | ||||||
							
								
								
									
										68
									
								
								src/core/features/tag/services/handlers/search.link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/core/features/tag/services/handlers/search.link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||||
|  | //
 | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||||
|  | // you may not use this file except in compliance with the License.
 | ||||||
|  | // You may obtain a copy of the License at
 | ||||||
|  | //
 | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | //
 | ||||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||||
|  | // See the License for the specific language governing permissions and
 | ||||||
|  | // limitations under the License.
 | ||||||
|  | 
 | ||||||
|  | import { Injectable } from '@angular/core'; | ||||||
|  | import { Params } from '@angular/router'; | ||||||
|  | import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||||
|  | import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||||
|  | import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreTag } from '../tag'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Handler to treat links to tag search. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class CoreTagSearchLinkHandlerService extends CoreContentLinksHandlerBase { | ||||||
|  | 
 | ||||||
|  |     name = 'CoreTagSearchLinkHandler'; | ||||||
|  |     pattern = /\/tag\/search\.php/; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the list of actions for a link (url). | ||||||
|  |      * | ||||||
|  |      * @param siteIds List of sites the URL belongs to. | ||||||
|  |      * @param url The URL to treat. | ||||||
|  |      * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} | ||||||
|  |      * @param courseId Course ID related to the URL. Optional but recommended. | ||||||
|  |      * @param data Extra data to handle the URL. | ||||||
|  |      * @return List of (or promise resolved with list of) actions. | ||||||
|  |      */ | ||||||
|  |     getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { | ||||||
|  |         return [{ | ||||||
|  |             action: (siteId): void => { | ||||||
|  |                 const pageParams = { | ||||||
|  |                     collectionId: parseInt(params.tc, 10) || 0, | ||||||
|  |                     query: params.query || '', | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 CoreContentLinksHelper.instance.goInSite('/main/tag/search', pageParams, siteId); | ||||||
|  |             }, | ||||||
|  |         }]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if the handler is enabled for a certain site (site + user) and a URL. | ||||||
|  |      * If not defined, defaults to true. | ||||||
|  |      * | ||||||
|  |      * @param siteId The site ID. | ||||||
|  |      * @return Whether the handler is enabled for the URL and site. | ||||||
|  |      */ | ||||||
|  |     isEnabled(siteId: string): boolean | Promise<boolean> { | ||||||
|  |         return CoreTag.instance.areTagsAvailable(siteId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class CoreTagSearchLinkHandler extends makeSingleton(CoreTagSearchLinkHandlerService) {} | ||||||
							
								
								
									
										63
									
								
								src/core/features/tag/services/handlers/tag.mainmenu.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/core/features/tag/services/handlers/tag.mainmenu.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | // (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 { CoreTag } from '../tag'; | ||||||
|  | import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Handler to inject an option into main menu. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class CoreTagMainMenuHandlerService implements CoreMainMenuHandler { | ||||||
|  | 
 | ||||||
|  |     static readonly PAGE_NAME = 'tag'; | ||||||
|  | 
 | ||||||
|  |     name = 'CoreTag'; | ||||||
|  |     priority = 300; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if the handler is enabled on a site level. | ||||||
|  |      * | ||||||
|  |      * @return Whether or not the handler is enabled on a site level. | ||||||
|  |      */ | ||||||
|  |     async isEnabled(): Promise<boolean> { | ||||||
|  |         const available = await CoreTag.instance.areTagsAvailable(); | ||||||
|  |         if (!available) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // The only way to check whether tags are enabled on web is to perform a WS call.
 | ||||||
|  |         return CoreUtils.instance.promiseWorks(CoreTag.instance.getTagCollections()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns the data needed to render the handler. | ||||||
|  |      * | ||||||
|  |      * @return Data needed to render the handler. | ||||||
|  |      */ | ||||||
|  |     getDisplayData(): CoreMainMenuHandlerData { | ||||||
|  |         return { | ||||||
|  |             icon: 'fas-tags', | ||||||
|  |             title: 'core.tag.tags', | ||||||
|  |             page: CoreTagMainMenuHandlerService.PAGE_NAME, | ||||||
|  |             class: 'core-tag-search-handler', | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class CoreTagMainMenuHandler extends makeSingleton(CoreTagMainMenuHandlerService) {} | ||||||
							
								
								
									
										99
									
								
								src/core/features/tag/services/tag-area-delegate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/core/features/tag/services/tag-area-delegate.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | |||||||
|  | // (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 { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; | ||||||
|  | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreTagFeedElement } from './tag-helper'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Interface that all tag area handlers must implement. | ||||||
|  |  */ | ||||||
|  | export interface CoreTagAreaHandler extends CoreDelegateHandler { | ||||||
|  |     /** | ||||||
|  |      * Component and item type separated by a slash. E.g. 'core/course_modules'. | ||||||
|  |      */ | ||||||
|  |     type: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Parses the rendered content of a tag index and returns the items. | ||||||
|  |      * | ||||||
|  |      * @param content Rendered content. | ||||||
|  |      * @return Area items (or promise resolved with the items). | ||||||
|  |      */ | ||||||
|  |     parseContent(content: string): CoreTagFeedElement[] | Promise<CoreTagFeedElement[]>; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the component to use to display items. | ||||||
|  |      * | ||||||
|  |      * @return The component (or promise resolved with component) to use, undefined if not found. | ||||||
|  |      * @todo, check return types. | ||||||
|  |      */ | ||||||
|  |     getComponent(): Type<unknown> | Promise<Type<unknown>>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Delegate to register tag area handlers. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class CoreTagAreaDelegateService extends CoreDelegate<CoreTagAreaHandler> { | ||||||
|  | 
 | ||||||
|  |     protected handlerNameProperty = 'type'; | ||||||
|  | 
 | ||||||
|  |     constructor() { | ||||||
|  |         super('CoreTagAreaDelegate'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns the display name string for this area. | ||||||
|  |      * | ||||||
|  |      * @param component Component name. | ||||||
|  |      * @param itemType Item type. | ||||||
|  |      * @return String key. | ||||||
|  |      */ | ||||||
|  |     getDisplayNameKey(component: string, itemType: string): string { | ||||||
|  |         return (component == 'core' ? 'core.tag' : 'addon.' + component) + '.tagarea_' + itemType; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Parses the rendered content of a tag index and returns the items. | ||||||
|  |      * | ||||||
|  |      * @param component Component name. | ||||||
|  |      * @param itemType Item type. | ||||||
|  |      * @param content Rendered content. | ||||||
|  |      * @return Promise resolved with the area items, or undefined if not found. | ||||||
|  |      */ | ||||||
|  |     async parseContent(component: string, itemType: string, content: string): Promise<CoreTagFeedElement[] | undefined> { | ||||||
|  |         const type = component + '/' + itemType; | ||||||
|  | 
 | ||||||
|  |         return await this.executeFunctionOnEnabled(type, 'parseContent', [content]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the component to use to display an area item. | ||||||
|  |      * | ||||||
|  |      * @param component Component name. | ||||||
|  |      * @param itemType Item type. | ||||||
|  |      * @return The component (or promise resolved with component) to use, undefined if not found. | ||||||
|  |      * @todo, check return types. | ||||||
|  |      */ | ||||||
|  |     async getComponent(component: string, itemType: string): Promise<Type<unknown> | undefined> { | ||||||
|  |         const type = component + '/' + itemType; | ||||||
|  | 
 | ||||||
|  |         return await this.executeFunctionOnEnabled(type, 'getComponent'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class CoreTagAreaDelegate extends makeSingleton(CoreTagAreaDelegateService) {} | ||||||
							
								
								
									
										94
									
								
								src/core/features/tag/services/tag-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/core/features/tag/services/tag-helper.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 { makeSingleton } from '@singletons'; | ||||||
|  | import { Injectable } from '@angular/core'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Service with helper functions for tags. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class CoreTagHelperProvider { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Parses the rendered content of the "core_tag/tagfeed" web template and returns the items. | ||||||
|  |      * | ||||||
|  |      * @param content Rendered content. | ||||||
|  |      * @return Area items. | ||||||
|  |      */ | ||||||
|  |     parseFeedContent(content: string): CoreTagFeedElement[] { | ||||||
|  |         const items: CoreTagFeedElement[] = []; | ||||||
|  |         const element = CoreDomUtils.instance.convertToElement(content); | ||||||
|  | 
 | ||||||
|  |         Array.from(element.querySelectorAll('ul.tag_feed > li.media')).forEach((itemElement) => { | ||||||
|  |             const item: CoreTagFeedElement = { details: [] }; | ||||||
|  | 
 | ||||||
|  |             Array.from(itemElement.querySelectorAll('div.media-body > div')).forEach((div: HTMLElement) => { | ||||||
|  |                 if (div.classList.contains('media-heading')) { | ||||||
|  |                     item.heading = div.innerText.trim(); | ||||||
|  |                     const link = div.querySelector('a'); | ||||||
|  |                     if (link) { | ||||||
|  |                         item.url = link.getAttribute('href'); | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     // Separate details by lines.
 | ||||||
|  |                     const lines = ['']; | ||||||
|  |                     Array.from(div.childNodes).forEach((childNode: Node) => { | ||||||
|  |                         if (childNode.nodeType == Node.TEXT_NODE) { | ||||||
|  |                             lines[lines.length - 1] += childNode.textContent; | ||||||
|  |                         } else if (childNode.nodeType == Node.ELEMENT_NODE) { | ||||||
|  |                             const childElement = childNode as HTMLElement; | ||||||
|  |                             if (childElement.tagName == 'BR') { | ||||||
|  |                                 lines.push(''); | ||||||
|  |                             } else { | ||||||
|  |                                 lines[lines.length - 1] += childElement.innerText; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                     item.details.push(...lines.map((line) => line.trim()).filter((line) => line != '')); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             const image = itemElement.querySelector('div.itemimage img'); | ||||||
|  |             if (image) { | ||||||
|  |                 if (image.classList.contains('userpicture')) { | ||||||
|  |                     item.avatarUrl = image.getAttribute('src'); | ||||||
|  |                 } else { | ||||||
|  |                     item.iconUrl = image.getAttribute('src'); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (item.heading && item.url) { | ||||||
|  |                 items.push(item); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return items; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class CoreTagHelper extends makeSingleton(CoreTagHelperProvider) {} | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Feed area element type. | ||||||
|  |  */ | ||||||
|  | export type CoreTagFeedElement = { | ||||||
|  |     details: string[]; | ||||||
|  |     heading?: string; | ||||||
|  |     iconUrl?: string | null; | ||||||
|  |     avatarUrl?: string | null; | ||||||
|  |     url?: string | null; | ||||||
|  | }; | ||||||
							
								
								
									
										440
									
								
								src/core/features/tag/services/tag.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										440
									
								
								src/core/features/tag/services/tag.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,440 @@ | |||||||
|  | // (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 } from '@services/sites'; | ||||||
|  | import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||||
|  | import { CoreWSExternalWarning } from '@services/ws'; | ||||||
|  | import { makeSingleton, Translate } from '@singletons'; | ||||||
|  | 
 | ||||||
|  | const ROOT_CACHE_KEY = 'CoreTag:'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Service to handle tags. | ||||||
|  |  */ | ||||||
|  | @Injectable({ providedIn: 'root' }) | ||||||
|  | export class CoreTagProvider { | ||||||
|  | 
 | ||||||
|  |     static readonly SEARCH_LIMIT = 150; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check whether tags are available in a certain site. | ||||||
|  |      * | ||||||
|  |      * @param siteId Site Id. If not defined, use current site. | ||||||
|  |      * @return Promise resolved with true if available, resolved with false otherwise. | ||||||
|  |      * @since 3.7 | ||||||
|  |      */ | ||||||
|  |     async areTagsAvailable(siteId?: string): Promise<boolean> { | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  | 
 | ||||||
|  |         return this.areTagsAvailableInSite(site); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check whether tags are available in a certain site. | ||||||
|  |      * | ||||||
|  |      * @param site Site. If not defined, use current site. | ||||||
|  |      * @return True if available. | ||||||
|  |      */ | ||||||
|  |     areTagsAvailableInSite(site?: CoreSite): boolean { | ||||||
|  |         site = site || CoreSites.instance.getCurrentSite(); | ||||||
|  | 
 | ||||||
|  |         return !!site && site.wsAvailable('core_tag_get_tagindex_per_area') && | ||||||
|  |             site.wsAvailable('core_tag_get_tag_cloud') && | ||||||
|  |             site.wsAvailable('core_tag_get_tag_collections') && | ||||||
|  |             !site.isFeatureDisabled('NoDelegate_CoreTag'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch the tag cloud. | ||||||
|  |      * | ||||||
|  |      * @param collectionId Tag collection ID. | ||||||
|  |      * @param isStandard Whether to return only standard tags. | ||||||
|  |      * @param sort Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). | ||||||
|  |      * @param search Search string. | ||||||
|  |      * @param fromContextId Context ID where this tag cloud is displayed. | ||||||
|  |      * @param contextId Only retrieve tag instances in this context. | ||||||
|  |      * @param recursive Retrieve tag instances in the context and its children. | ||||||
|  |      * @param limit Maximum number of tags to retrieve. Defaults to SEARCH_LIMIT. | ||||||
|  |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved with the tag cloud. | ||||||
|  |      * @since 3.7 | ||||||
|  |      */ | ||||||
|  |     async getTagCloud( | ||||||
|  |         collectionId: number = 0, | ||||||
|  |         isStandard: boolean = false, | ||||||
|  |         sort: string = 'name', | ||||||
|  |         search: string = '', | ||||||
|  |         fromContextId: number = 0, | ||||||
|  |         contextId: number = 0, | ||||||
|  |         recursive: boolean = true, | ||||||
|  |         limit?: number, | ||||||
|  |         siteId?: string, | ||||||
|  |     ): Promise<CoreTagCloud> { | ||||||
|  |         limit = limit || CoreTagProvider.SEARCH_LIMIT; | ||||||
|  | 
 | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  |         const params: CoreTagGetTagCloudWSParams = { | ||||||
|  |             tagcollid: collectionId, | ||||||
|  |             isstandard: isStandard, | ||||||
|  |             limit, | ||||||
|  |             sort, | ||||||
|  |             search, | ||||||
|  |             fromctx: fromContextId, | ||||||
|  |             ctx: contextId, | ||||||
|  |             rec: recursive, | ||||||
|  |         }; | ||||||
|  |         const preSets: CoreSiteWSPreSets = { | ||||||
|  |             updateFrequency: CoreSite.FREQUENCY_SOMETIMES, | ||||||
|  |             cacheKey: this.getTagCloudKey(collectionId, isStandard, sort, search, fromContextId, contextId, recursive), | ||||||
|  |             getFromCache: search != '', // Try to get updated data when searching.
 | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         return site.read('core_tag_get_tag_cloud', params, preSets); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch the tag collections. | ||||||
|  |      * | ||||||
|  |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved with the tag collections. | ||||||
|  |      * @since 3.7 | ||||||
|  |      */ | ||||||
|  |     async getTagCollections(siteId?: string): Promise<CoreTagCollection[]> { | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  |         const preSets: CoreSiteWSPreSets = { | ||||||
|  |             updateFrequency: CoreSite.FREQUENCY_RARELY, | ||||||
|  |             cacheKey: this.getTagCollectionsKey(), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         const response: CoreTagCollections = await site.read('core_tag_get_tag_collections', null, preSets); | ||||||
|  | 
 | ||||||
|  |         if (!response || !response.collections) { | ||||||
|  |             throw null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return response.collections; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch the tag index. | ||||||
|  |      * | ||||||
|  |      * @param id Tag ID. | ||||||
|  |      * @param name Tag name. | ||||||
|  |      * @param collectionId Tag collection ID. | ||||||
|  |      * @param areaId Tag area ID. | ||||||
|  |      * @param fromContextId Context ID where the link was displayed. | ||||||
|  |      * @param contextId Context ID where to search for items. | ||||||
|  |      * @param recursive Search in the context and its children. | ||||||
|  |      * @param page Page number. | ||||||
|  |      * @param siteId Site ID. If not defined, current site. | ||||||
|  |      * @return Promise resolved with the tag index per area. | ||||||
|  |      * @since 3.7 | ||||||
|  |      */ | ||||||
|  |     async getTagIndexPerArea( | ||||||
|  |         id: number, | ||||||
|  |         name: string = '', | ||||||
|  |         collectionId: number = 0, | ||||||
|  |         areaId: number = 0, | ||||||
|  |         fromContextId: number = 0, | ||||||
|  |         contextId: number = 0, | ||||||
|  |         recursive: boolean = true, | ||||||
|  |         page: number = 0, | ||||||
|  |         siteId?: string, | ||||||
|  |     ): Promise<CoreTagIndex[]> { | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  |         const params: CoreTagGetTagindexPerAreaWSParams = { | ||||||
|  |             tagindex: { | ||||||
|  |                 id, | ||||||
|  |                 tag: name, | ||||||
|  |                 tc: collectionId, | ||||||
|  |                 ta: areaId, | ||||||
|  |                 excl: true, | ||||||
|  |                 from: fromContextId, | ||||||
|  |                 ctx: contextId, | ||||||
|  |                 rec: recursive, | ||||||
|  |                 page, | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  |         const preSets: CoreSiteWSPreSets = { | ||||||
|  |             updateFrequency: CoreSite.FREQUENCY_OFTEN, | ||||||
|  |             cacheKey: this.getTagIndexPerAreaKey(id, name, collectionId, areaId, fromContextId, contextId, recursive), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         let response: CoreTagIndex[]; | ||||||
|  |         try { | ||||||
|  |             response = await site.read('core_tag_get_tagindex_per_area', params, preSets); | ||||||
|  |         } catch (error) { | ||||||
|  |             // Workaround for WS not passing parameter to error string.
 | ||||||
|  |             if (error && error.errorcode == 'notagsfound') { | ||||||
|  |                 error.message = Translate.instance.instant('core.tag.notagsfound', { $a: name || id || '' }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             throw error; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!response) { | ||||||
|  |             throw null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return response; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Invalidate tag cloud. | ||||||
|  |      * | ||||||
|  |      * @param collectionId Tag collection ID. | ||||||
|  |      * @param isStandard Whether to return only standard tags. | ||||||
|  |      * @param sort Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). | ||||||
|  |      * @param search Search string. | ||||||
|  |      * @param fromContextId Context ID where this tag cloud is displayed. | ||||||
|  |      * @param contextId Only retrieve tag instances in this context. | ||||||
|  |      * @param recursive Retrieve tag instances in the context and its children. | ||||||
|  |      * @return Promise resolved when the data is invalidated. | ||||||
|  |      */ | ||||||
|  |     async invalidateTagCloud( | ||||||
|  |         collectionId: number = 0, | ||||||
|  |         isStandard: boolean = false, | ||||||
|  |         sort: string = 'name', | ||||||
|  |         search: string = '', | ||||||
|  |         fromContextId: number = 0, | ||||||
|  |         contextId: number = 0, | ||||||
|  |         recursive: boolean = true, | ||||||
|  |         siteId?: string, | ||||||
|  |     ): Promise<void> { | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  |         const key = this.getTagCloudKey(collectionId, isStandard, sort, search, fromContextId, contextId, recursive); | ||||||
|  | 
 | ||||||
|  |         return site.invalidateWsCacheForKey(key); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Invalidate tag collections. | ||||||
|  |      * | ||||||
|  |      * @return Promise resolved when the data is invalidated. | ||||||
|  |      */ | ||||||
|  |     async invalidateTagCollections(siteId?: string): Promise<void> { | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  |         const key = this.getTagCollectionsKey(); | ||||||
|  | 
 | ||||||
|  |         return site.invalidateWsCacheForKey(key); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Invalidate tag index. | ||||||
|  |      * | ||||||
|  |      * @param id Tag ID. | ||||||
|  |      * @param name Tag name. | ||||||
|  |      * @param collectionId Tag collection ID. | ||||||
|  |      * @param areaId Tag area ID. | ||||||
|  |      * @param fromContextId Context ID where the link was displayed. | ||||||
|  |      * @param contextId Context ID where to search for items. | ||||||
|  |      * @param recursive Search in the context and its children. | ||||||
|  |      * @return Promise resolved when the data is invalidated. | ||||||
|  |      */ | ||||||
|  |     async invalidateTagIndexPerArea( | ||||||
|  |         id: number, | ||||||
|  |         name: string = '', | ||||||
|  |         collectionId: number = 0, | ||||||
|  |         areaId: number = 0, | ||||||
|  |         fromContextId: number = 0, | ||||||
|  |         contextId: number = 0, | ||||||
|  |         recursive: boolean = true, | ||||||
|  |         siteId?: string, | ||||||
|  |     ): Promise<void> { | ||||||
|  |         const site = await CoreSites.instance.getSite(siteId); | ||||||
|  |         const key = this.getTagIndexPerAreaKey(id, name, collectionId, areaId, fromContextId, contextId, recursive); | ||||||
|  | 
 | ||||||
|  |         return site.invalidateWsCacheForKey(key); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get cache key for tag cloud. | ||||||
|  |      * | ||||||
|  |      * @param collectionId Tag collection ID. | ||||||
|  |      * @param isStandard Whether to return only standard tags. | ||||||
|  |      * @param sort Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). | ||||||
|  |      * @param search Search string. | ||||||
|  |      * @param fromContextId Context ID where this tag cloud is displayed. | ||||||
|  |      * @param contextId Only retrieve tag instances in this context. | ||||||
|  |      * @param recursive Retrieve tag instances in the context and it's children. | ||||||
|  |      * @return Cache key. | ||||||
|  |      */ | ||||||
|  |     protected getTagCloudKey( | ||||||
|  |         collectionId: number, | ||||||
|  |         isStandard: boolean, | ||||||
|  |         sort: string, | ||||||
|  |         search: string, | ||||||
|  |         fromContextId: number, | ||||||
|  |         contextId: number, | ||||||
|  |         recursive: boolean, | ||||||
|  |     ): string { | ||||||
|  |         return ROOT_CACHE_KEY + | ||||||
|  |             'cloud:' + | ||||||
|  |             collectionId + ':' + | ||||||
|  |             (isStandard ? 1 : 0) + ':' + | ||||||
|  |             sort + ':' + search + ':' + | ||||||
|  |             fromContextId + ':' + | ||||||
|  |             contextId + ':' + | ||||||
|  |             (recursive ? 1 : 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get cache key for tag collections. | ||||||
|  |      * | ||||||
|  |      * @return Cache key. | ||||||
|  |      */ | ||||||
|  |     protected getTagCollectionsKey(): string { | ||||||
|  |         return ROOT_CACHE_KEY + 'collections'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get cache key for tag index. | ||||||
|  |      * | ||||||
|  |      * @param id Tag ID. | ||||||
|  |      * @param name Tag name. | ||||||
|  |      * @param collectionId Tag collection ID. | ||||||
|  |      * @param areaId Tag area ID. | ||||||
|  |      * @param fromContextId Context ID where the link was displayed. | ||||||
|  |      * @param contextId Context ID where to search for items. | ||||||
|  |      * @param recursive Search in the context and its children. | ||||||
|  |      * @return Cache key. | ||||||
|  |      */ | ||||||
|  |     protected getTagIndexPerAreaKey( | ||||||
|  |         id: number, | ||||||
|  |         name: string, | ||||||
|  |         collectionId: number, | ||||||
|  |         areaId: number, | ||||||
|  |         fromContextId: number, | ||||||
|  |         contextId: number, | ||||||
|  |         recursive: boolean, | ||||||
|  |     ): string { | ||||||
|  |         return ROOT_CACHE_KEY + | ||||||
|  |             'index:' + id + ':' + | ||||||
|  |             name + ':' + collectionId + ':' + | ||||||
|  |             areaId + ':' + fromContextId + ':' + | ||||||
|  |             contextId + ':' + | ||||||
|  |             (recursive ? 1 : 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class CoreTag extends makeSingleton(CoreTagProvider) {} | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Params of core_tag_get_tag_cloud WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagGetTagCloudWSParams = { | ||||||
|  |     tagcollid?: number; // Tag collection id.
 | ||||||
|  |     isstandard?: boolean; // Whether to return only standard tags.
 | ||||||
|  |     limit?: number; // Maximum number of tags to retrieve.
 | ||||||
|  |     sort?: string; // Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid).
 | ||||||
|  |     search?: string; // Search string.
 | ||||||
|  |     fromctx?: number; // Context id where this tag cloud is displayed.
 | ||||||
|  |     ctx?: number; // Only retrieve tag instances in this context.
 | ||||||
|  |     rec?: boolean; // Retrieve tag instances in the $ctx context and it's children.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag cloud returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagCloud = { | ||||||
|  |     tags: CoreTagCloudTag[]; | ||||||
|  |     tagscount: number; | ||||||
|  |     totalcount: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag cloud tag returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagCloudTag = { | ||||||
|  |     name: string; | ||||||
|  |     viewurl: string; | ||||||
|  |     flag: boolean; | ||||||
|  |     isstandard: boolean; | ||||||
|  |     count: number; | ||||||
|  |     size: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag collection returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagCollection = { | ||||||
|  |     id: number; // Collection id.
 | ||||||
|  |     name: string; // Collection name.
 | ||||||
|  |     isdefault: boolean; // Whether is the default collection.
 | ||||||
|  |     component: string; // Component the collection is related to.
 | ||||||
|  |     sortorder: number; // Collection ordering in the list.
 | ||||||
|  |     searchable: boolean; // Whether the tag collection is searchable.
 | ||||||
|  |     customurl: string; // Custom URL for the tag page instead of /tag/index.php.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of tag collections returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagCollections = { | ||||||
|  |     collections: CoreTagCollection[]; | ||||||
|  |     warnings?: CoreWSExternalWarning[]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Params of core_tag_get_tagindex_per_area WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagGetTagindexPerAreaWSParams = { | ||||||
|  |     tagindex: { | ||||||
|  |         id?: number; // Tag id.
 | ||||||
|  |         tag?: string; // Tag name.
 | ||||||
|  |         tc?: number; // Tag collection id.
 | ||||||
|  |         ta?: number; // Tag area id.
 | ||||||
|  |         excl?: boolean; // Exlusive mode for this tag area.
 | ||||||
|  |         from?: number; // Context id where the link was displayed.
 | ||||||
|  |         ctx?: number; // Context id where to search for items.
 | ||||||
|  |         rec?: boolean; // Search in the context recursive.
 | ||||||
|  |         page?: number; // Page number (0-based).
 | ||||||
|  |     }; // Parameters.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag index returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagIndex = { | ||||||
|  |     tagid: number; | ||||||
|  |     ta: number; | ||||||
|  |     component: string; | ||||||
|  |     itemtype: string; | ||||||
|  |     nextpageurl: string; | ||||||
|  |     prevpageurl: string; | ||||||
|  |     exclusiveurl: string; | ||||||
|  |     exclusivetext: string; | ||||||
|  |     title: string; | ||||||
|  |     content: string; | ||||||
|  |     hascontent: number; | ||||||
|  |     anchor: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of a tag item returned by WS. | ||||||
|  |  */ | ||||||
|  | export type CoreTagItem = { | ||||||
|  |     id: number; | ||||||
|  |     name: string; | ||||||
|  |     rawname: string; | ||||||
|  |     isstandard: boolean; | ||||||
|  |     tagcollid: number; | ||||||
|  |     taginstanceid: number; | ||||||
|  |     taginstancecontextid: number; | ||||||
|  |     itemid: number; | ||||||
|  |     ordering: number; | ||||||
|  |     flag: number; | ||||||
|  | }; | ||||||
							
								
								
									
										42
									
								
								src/core/features/tag/tag-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/core/features/tag/tag-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||||
|  | //
 | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||||
|  | // you may not use this file except in compliance with the License.
 | ||||||
|  | // You may obtain a copy of the License at
 | ||||||
|  | //
 | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | //
 | ||||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||||
|  | // See the License for the specific language governing permissions and
 | ||||||
|  | // limitations under the License.
 | ||||||
|  | 
 | ||||||
|  | import {  NgModule } from '@angular/core'; | ||||||
|  | import { RouterModule, Routes } from '@angular/router'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: 'index', | ||||||
|  |         loadChildren: () => import('@features/tag/pages/index/index.page.module').then(m => m.CoreTagIndexPageModule), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: 'search', | ||||||
|  |         loadChildren: () => import('@features/tag//pages/search/search.page.module').then(m => m.CoreTagSearchPageModule), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: 'index-area', | ||||||
|  |         loadChildren: () => import('@features/tag/pages/index-area/index-area.page.module').then(m => m.CoreTagIndexAreaPageModule), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         redirectTo: 'search', | ||||||
|  |         pathMatch: 'full', | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [RouterModule.forChild(routes)], | ||||||
|  |     exports: [RouterModule], | ||||||
|  | }) | ||||||
|  | export class CoreTagLazyModule { } | ||||||
							
								
								
									
										48
									
								
								src/core/features/tag/tag.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/core/features/tag/tag.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | // (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 { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate'; | ||||||
|  | import { CoreMainMenuRoutingModule } from '../mainmenu/mainmenu-routing.module'; | ||||||
|  | import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; | ||||||
|  | import { CoreTagMainMenuHandler, CoreTagMainMenuHandlerService } from './services/handlers/tag.mainmenu'; | ||||||
|  | import { CoreTagIndexLinkHandler } from './services/handlers/index.link'; | ||||||
|  | import { CoreTagSearchLinkHandler } from './services/handlers/search.link'; | ||||||
|  | 
 | ||||||
|  | const routes: Routes = [ | ||||||
|  |     { | ||||||
|  |         path: CoreTagMainMenuHandlerService.PAGE_NAME, | ||||||
|  |         loadChildren: () =>  import('./tag-lazy.module').then(m => m.CoreTagLazyModule), | ||||||
|  |     }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     imports: [ | ||||||
|  |         CoreMainMenuRoutingModule.forChild({ children: routes }), | ||||||
|  |     ], | ||||||
|  |     exports: [CoreMainMenuRoutingModule], | ||||||
|  |     providers: [ | ||||||
|  |         { | ||||||
|  |             provide: APP_INITIALIZER, | ||||||
|  |             multi: true, | ||||||
|  |             useValue: () => { | ||||||
|  |                 CoreMainMenuDelegate.instance.registerHandler(CoreTagMainMenuHandler.instance); | ||||||
|  |                 CoreContentLinksDelegate.instance.registerHandler(CoreTagIndexLinkHandler.instance); | ||||||
|  |                 CoreContentLinksDelegate.instance.registerHandler(CoreTagSearchLinkHandler.instance); | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |     ], | ||||||
|  | }) | ||||||
|  | export class CoreTagModule {} | ||||||
| @ -104,6 +104,9 @@ | |||||||
|     ion-toolbar { |     ion-toolbar { | ||||||
|         --color: var(--custom-toolbar-color, var(--ion-color-primary-contrast)); |         --color: var(--custom-toolbar-color, var(--ion-color-primary-contrast)); | ||||||
|         --background: var(--ion-statusbar-background); |         --background: var(--ion-statusbar-background); | ||||||
|  |         ion-button { | ||||||
|  |             --ion-toolbar-color: transparent; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         ion-spinner { |         ion-spinner { | ||||||
|             --color: var(--custom-toolbar-color, var(--ion-color-primary-contrast)); |             --color: var(--custom-toolbar-color, var(--ion-color-primary-contrast)); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user