forked from CIT/Vmeda.Online
		
	MOBILE-3109 addon: Add return types to all addons except mod
This commit is contained in:
		
							parent
							
								
									0c6c1b6383
								
							
						
					
					
						commit
						b2497a1dd0
					
				| @ -1,6 +1,6 @@ | ||||
| <ion-header> | ||||
|     <ion-navbar core-back-button> | ||||
|         <ion-title>{{badge.name}}</ion-title> | ||||
|         <ion-title>{{badge && badge.name}}</ion-title> | ||||
|     </ion-navbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
| @ -9,7 +9,7 @@ | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="badgeLoaded"> | ||||
| 
 | ||||
|         <ion-item-group> | ||||
|         <ion-item-group *ngIf="badge"> | ||||
|             <ion-item text-wrap class="item-avatar-center"> | ||||
|                 <img *ngIf="badge.badgeurl" class="avatar" [src]="badge.badgeurl" core-external-content [alt]="badge.name"> | ||||
|                 <ion-badge color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire"> | ||||
| @ -30,7 +30,7 @@ | ||||
|             </ion-item> | ||||
|         </ion-item-group> | ||||
| 
 | ||||
|         <ion-item-group> | ||||
|         <ion-item-group *ngIf="badge"> | ||||
|             <ion-item-divider> | ||||
|                 <h2>{{ 'addon.badges.issuerdetails' | translate}}</h2> | ||||
|             </ion-item-divider> | ||||
| @ -48,7 +48,7 @@ | ||||
|             </ion-item> | ||||
|         </ion-item-group> | ||||
| 
 | ||||
|         <ion-item-group> | ||||
|         <ion-item-group *ngIf="badge"> | ||||
|             <ion-item-divider> | ||||
|                 <h2>{{ 'addon.badges.badgedetails' | translate}}</h2> | ||||
|             </ion-item-divider> | ||||
| @ -99,7 +99,7 @@ | ||||
|             <!-- Criteria (not yet avalaible) --> | ||||
|         </ion-item-group> | ||||
| 
 | ||||
|         <ion-item-group> | ||||
|         <ion-item-group *ngIf="badge"> | ||||
|             <ion-item-divider> | ||||
|                 <h2>{{ 'addon.badges.issuancedetails' | translate}}</h2> | ||||
|             </ion-item-divider> | ||||
| @ -120,7 +120,7 @@ | ||||
|         </ion-item-group> | ||||
| 
 | ||||
|         <!-- Endorsement --> | ||||
|         <ion-item-group *ngIf="badge.endorsement"> | ||||
|         <ion-item-group *ngIf="badge && badge.endorsement"> | ||||
|             <ion-item-divider> | ||||
|                 <h2>{{ 'addon.badges.bendorsement' | translate}}</h2> | ||||
|             </ion-item-divider> | ||||
| @ -159,7 +159,7 @@ | ||||
|         </ion-item-group> | ||||
| 
 | ||||
|         <!-- Related badges --> | ||||
|         <ion-item-group *ngIf="badge.relatedbadges"> | ||||
|         <ion-item-group *ngIf="badge && badge.relatedbadges"> | ||||
|             <ion-item-divider> | ||||
|                 <h2>{{ 'addon.badges.relatedbages' | translate}}</h2> | ||||
|             </ion-item-divider> | ||||
| @ -172,7 +172,7 @@ | ||||
|         </ion-item-group> | ||||
| 
 | ||||
|         <!-- Competencies alignment --> | ||||
|         <ion-item-group *ngIf="badge.competencies"> | ||||
|         <ion-item-group *ngIf="badge && badge.competencies"> | ||||
|             <ion-item-divider> | ||||
|                 <h2>{{ 'addon.badges.alignment' | translate}}</h2> | ||||
|             </ion-item-divider> | ||||
|  | ||||
| @ -19,7 +19,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
| import { AddonBadgesProvider } from '../../providers/badges'; | ||||
| import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of calendar events. | ||||
| @ -38,7 +38,7 @@ export class AddonBadgesIssuedBadgePage { | ||||
| 
 | ||||
|     user: any = {}; | ||||
|     course: any = {}; | ||||
|     badge: any = {}; | ||||
|     badge: AddonBadgesUserBadge; | ||||
| 
 | ||||
|     badgeLoaded = false; | ||||
|     currentTime = 0; | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| 
 | ||||
| import { Component, ViewChild } from '@angular/core'; | ||||
| import { IonicPage, Content, NavParams } from 'ionic-angular'; | ||||
| import { AddonBadgesProvider } from '../../providers/badges'; | ||||
| import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges'; | ||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| @ -36,7 +36,7 @@ export class AddonBadgesUserBadgesPage { | ||||
|     userId: number; | ||||
| 
 | ||||
|     badgesLoaded = false; | ||||
|     badges = []; | ||||
|     badges: AddonBadgesUserBadge[] = []; | ||||
|     currentTime = 0; | ||||
|     badgeHash: string; | ||||
| 
 | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreWSExternalWarning } from '@providers/ws'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| 
 | ||||
| /** | ||||
| @ -70,7 +71,7 @@ export class AddonBadgesProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise to be resolved when the badges are retrieved. | ||||
|      */ | ||||
|     getUserBadges(courseId: number, userId: number, siteId?: string): Promise<any> { | ||||
|     getUserBadges(courseId: number, userId: number, siteId?: string): Promise<AddonBadgesUserBadge[]> { | ||||
| 
 | ||||
|         this.logger.debug('Get badges for course ' + courseId); | ||||
| 
 | ||||
| @ -110,3 +111,76 @@ export class AddonBadgesProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_badges_get_user_badges. | ||||
|  */ | ||||
| export type AddonBadgesGetUserBadgesResult = { | ||||
|     badges: AddonBadgesUserBadge[]; // List of badges.
 | ||||
|     warnings?: CoreWSExternalWarning[]; // List of warnings.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Badge data returned by WS core_badges_get_user_badges. | ||||
|  */ | ||||
| export type AddonBadgesUserBadge = { | ||||
|     id?: number; // Badge id.
 | ||||
|     name: string; // Badge name.
 | ||||
|     description: string; // Badge description.
 | ||||
|     timecreated?: number; // Time created.
 | ||||
|     timemodified?: number; // Time modified.
 | ||||
|     usercreated?: number; // User created.
 | ||||
|     usermodified?: number; // User modified.
 | ||||
|     issuername: string; // Issuer name.
 | ||||
|     issuerurl: string; // Issuer URL.
 | ||||
|     issuercontact: string; // Issuer contact.
 | ||||
|     expiredate?: number; // Expire date.
 | ||||
|     expireperiod?: number; // Expire period.
 | ||||
|     type?: number; // Type.
 | ||||
|     courseid?: number; // Course id.
 | ||||
|     message?: string; // Message.
 | ||||
|     messagesubject?: string; // Message subject.
 | ||||
|     attachment?: number; // Attachment.
 | ||||
|     notification?: number; // @since 3.6. Whether to notify when badge is awarded.
 | ||||
|     nextcron?: number; // @since 3.6. Next cron.
 | ||||
|     status?: number; // Status.
 | ||||
|     issuedid?: number; // Issued id.
 | ||||
|     uniquehash: string; // Unique hash.
 | ||||
|     dateissued: number; // Date issued.
 | ||||
|     dateexpire: number; // Date expire.
 | ||||
|     visible?: number; // Visible.
 | ||||
|     email?: string; // @since 3.6. User email.
 | ||||
|     version?: string; // @since 3.6. Version.
 | ||||
|     language?: string; // @since 3.6. Language.
 | ||||
|     imageauthorname?: string; // @since 3.6. Name of the image author.
 | ||||
|     imageauthoremail?: string; // @since 3.6. Email of the image author.
 | ||||
|     imageauthorurl?: string; // @since 3.6. URL of the image author.
 | ||||
|     imagecaption?: string; // @since 3.6. Caption of the image.
 | ||||
|     badgeurl: string; // Badge URL.
 | ||||
|     endorsement?: { // @since 3.6.
 | ||||
|         id: number; // Endorsement id.
 | ||||
|         badgeid: number; // Badge id.
 | ||||
|         issuername: string; // Endorsement issuer name.
 | ||||
|         issuerurl: string; // Endorsement issuer URL.
 | ||||
|         issueremail: string; // Endorsement issuer email.
 | ||||
|         claimid: string; // Claim URL.
 | ||||
|         claimcomment: string; // Claim comment.
 | ||||
|         dateissued: number; // Date issued.
 | ||||
|     }; | ||||
|     alignment: { // @since 3.6. Badge alignments.
 | ||||
|         id?: number; // Alignment id.
 | ||||
|         badgeid?: number; // Badge id.
 | ||||
|         targetName?: string; // Target name.
 | ||||
|         targetUrl?: string; // Target URL.
 | ||||
|         targetDescription?: string; // Target description.
 | ||||
|         targetFramework?: string; // Target framework.
 | ||||
|         targetCode?: string; // Target code.
 | ||||
|     }[]; | ||||
|     relatedbadges: { // @since 3.6. Related badges.
 | ||||
|         id: number; // Badge id.
 | ||||
|         name: string; // Badge name.
 | ||||
|         version?: string; // Version.
 | ||||
|         language?: string; // Language.
 | ||||
|         type?: number; // Type.
 | ||||
|     }[]; | ||||
| }; | ||||
|  | ||||
| @ -16,7 +16,9 @@ import { Component, OnInit, Injector, Optional } from '@angular/core'; | ||||
| import { NavController } from 'ionic-angular'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; | ||||
| import { AddonBlockRecentlyAccessedItemsProvider } from '../../providers/recentlyaccesseditems'; | ||||
| import { | ||||
|     AddonBlockRecentlyAccessedItemsProvider, AddonBlockRecentlyAccessedItemsItem | ||||
| } from '../../providers/recentlyaccesseditems'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; | ||||
| 
 | ||||
| @ -28,7 +30,7 @@ import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/hel | ||||
|     templateUrl: 'addon-block-recentlyaccesseditems.html' | ||||
| }) | ||||
| export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseComponent implements OnInit { | ||||
|     items = []; | ||||
|     items: AddonBlockRecentlyAccessedItemsItem[] = []; | ||||
| 
 | ||||
|     protected fetchContentDefaultError = 'Error getting recently accessed items data.'; | ||||
| 
 | ||||
|  | ||||
| @ -42,14 +42,16 @@ export class AddonBlockRecentlyAccessedItemsProvider { | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved when the info is retrieved. | ||||
|      */ | ||||
|     getRecentItems(siteId?: string): Promise<any[]> { | ||||
|     getRecentItems(siteId?: string): Promise<AddonBlockRecentlyAccessedItemsItem[]> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const preSets = { | ||||
|                     cacheKey: this.getRecentItemsCacheKey() | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets).then((items) => { | ||||
|             return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets) | ||||
|                     .then((items: AddonBlockRecentlyAccessedItemsItem[]) => { | ||||
| 
 | ||||
|                 return items.map((item) => { | ||||
|                     const modicon = item.icon && this.domUtils.getHTMLElementAttribute(item.icon, 'src'); | ||||
|                     item.iconUrl = this.courseProvider.getModuleIconSrc(item.modname, modicon); | ||||
| @ -72,3 +74,27 @@ export class AddonBlockRecentlyAccessedItemsProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS block_recentlyaccesseditems_get_recent_items. | ||||
|  */ | ||||
| export type AddonBlockRecentlyAccessedItemsItem = { | ||||
|     id: number; // Id.
 | ||||
|     courseid: number; // Courseid.
 | ||||
|     cmid: number; // Cmid.
 | ||||
|     userid: number; // Userid.
 | ||||
|     modname: string; // Modname.
 | ||||
|     name: string; // Name.
 | ||||
|     coursename: string; // Coursename.
 | ||||
|     timeaccess: number; // Timeaccess.
 | ||||
|     viewurl: string; // Viewurl.
 | ||||
|     courseviewurl: string; // Courseviewurl.
 | ||||
|     icon: string; // Icon.
 | ||||
| } & AddonBlockRecentlyAccessedItemsItemCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for recently accessed item. | ||||
|  */ | ||||
| export type AddonBlockRecentlyAccessedItemsItemCalculatedData = { | ||||
|     iconUrl: string; // Icon URL. Calculated by the app.
 | ||||
| }; | ||||
|  | ||||
| @ -21,6 +21,7 @@ import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; | ||||
| import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; | ||||
| import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; | ||||
| import { AddonBlockTimelineProvider } from '../../providers/timeline'; | ||||
| import { AddonCalendarEvent } from '@addon/calendar/providers/calendar'; | ||||
| 
 | ||||
| /** | ||||
|  * Component to render a timeline block. | ||||
| @ -34,9 +35,9 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen | ||||
|     filter = 'next30days'; | ||||
|     currentSite: any; | ||||
|     timeline = { | ||||
|         events: [], | ||||
|         events: <AddonCalendarEvent[]> [], | ||||
|         loaded: false, | ||||
|         canLoadMore: undefined | ||||
|         canLoadMore: <number> undefined | ||||
|     }; | ||||
|     timelineCourses = { | ||||
|         courses: [], | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreCoursesDashboardProvider } from '@core/courses/providers/dashboard'; | ||||
| import { AddonCalendarEvents, AddonCalendarEventsGroupedByCourse, AddonCalendarEvent } from '@addon/calendar/providers/calendar'; | ||||
| import * as moment from 'moment'; | ||||
| 
 | ||||
| /** | ||||
| @ -38,7 +39,7 @@ export class AddonBlockTimelineProvider { | ||||
|      * @return Promise resolved when the info is retrieved. | ||||
|      */ | ||||
|     getActionEventsByCourse(courseId: number, afterEventId?: number, siteId?: string): | ||||
|             Promise<{ events: any[], canLoadMore: number }> { | ||||
|             Promise<{ events: AddonCalendarEvent[], canLoadMore: number }> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 | ||||
| @ -55,7 +56,9 @@ export class AddonBlockTimelineProvider { | ||||
|                 data.aftereventid = afterEventId; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_calendar_get_action_events_by_course', data, preSets).then((courseEvents): any => { | ||||
|             return site.read('core_calendar_get_action_events_by_course', data, preSets) | ||||
|                     .then((courseEvents: AddonCalendarEvents): any => { | ||||
| 
 | ||||
|                 if (courseEvents && courseEvents.events) { | ||||
|                     return this.treatCourseEvents(courseEvents, time); | ||||
|                 } | ||||
| @ -82,8 +85,9 @@ export class AddonBlockTimelineProvider { | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved when the info is retrieved. | ||||
|      */ | ||||
|     getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [s: string]: | ||||
|             { events: any[], canLoadMore: number } }> { | ||||
|     getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [courseId: string]: | ||||
|             { events: AddonCalendarEvent[], canLoadMore: number } }> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 | ||||
|                 data = { | ||||
| @ -95,7 +99,9 @@ export class AddonBlockTimelineProvider { | ||||
|                     cacheKey: this.getActionEventsByCoursesCacheKey() | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_calendar_get_action_events_by_courses', data, preSets).then((events): any => { | ||||
|             return site.read('core_calendar_get_action_events_by_courses', data, preSets) | ||||
|                     .then((events: AddonCalendarEventsGroupedByCourse): any => { | ||||
| 
 | ||||
|                 if (events && events.groupedbycourse) { | ||||
|                     const courseEvents = {}; | ||||
| 
 | ||||
| @ -127,7 +133,9 @@ export class AddonBlockTimelineProvider { | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved when the info is retrieved. | ||||
|      */ | ||||
|     getActionEventsByTimesort(afterEventId: number, siteId?: string): Promise<{ events: any[], canLoadMore: number }> { | ||||
|     getActionEventsByTimesort(afterEventId: number, siteId?: string): | ||||
|             Promise<{ events: AddonCalendarEvent[], canLoadMore: number }> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const time = moment().subtract(14, 'days').unix(), // Check two weeks ago.
 | ||||
|                 data: any = { | ||||
| @ -144,12 +152,14 @@ export class AddonBlockTimelineProvider { | ||||
|                 data.aftereventid = afterEventId; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_calendar_get_action_events_by_timesort', data, preSets).then((events): any => { | ||||
|                 if (events && events.events) { | ||||
|                     const canLoadMore = events.events.length >= data.limitnum ? events.lastid : undefined; | ||||
|             return site.read('core_calendar_get_action_events_by_timesort', data, preSets) | ||||
|                     .then((result: AddonCalendarEvents): any => { | ||||
| 
 | ||||
|                 if (result && result.events) { | ||||
|                     const canLoadMore = result.events.length >= data.limitnum ? result.lastid : undefined; | ||||
| 
 | ||||
|                     // Filter events by time in case it uses cache.
 | ||||
|                     events = events.events.filter((element) => { | ||||
|                     const events = result.events.filter((element) => { | ||||
|                         return element.timesort >= time; | ||||
|                     }); | ||||
| 
 | ||||
| @ -236,7 +246,9 @@ export class AddonBlockTimelineProvider { | ||||
|      * @param timeFrom Current time to filter events from. | ||||
|      * @return Object with course events and last loaded event id if more can be loaded. | ||||
|      */ | ||||
|     protected treatCourseEvents(course: any, timeFrom: number): { events: any[], canLoadMore: number } { | ||||
|     protected treatCourseEvents(course: AddonCalendarEvents, timeFrom: number): | ||||
|             { events: AddonCalendarEvent[], canLoadMore: number } { | ||||
| 
 | ||||
|         const canLoadMore: number = | ||||
|             course.events.length >= AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE ? course.lastid : undefined; | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { AddonBlogProvider } from '../../providers/blog'; | ||||
| import { AddonBlogProvider, AddonBlogPost } from '../../providers/blog'; | ||||
| import { CoreCommentsProvider } from '@core/comments/providers/comments'; | ||||
| import { CoreTagProvider } from '@core/tag/providers/tag'; | ||||
| 
 | ||||
| @ -48,7 +48,7 @@ export class AddonBlogEntriesComponent implements OnInit { | ||||
|     loaded = false; | ||||
|     canLoadMore = false; | ||||
|     loadMoreError = false; | ||||
|     entries = []; | ||||
|     entries: AddonBlogPostFormatted[] = []; | ||||
|     currentUserId: number; | ||||
|     showMyEntriesToggle = false; | ||||
|     onlyMyEntries = false; | ||||
| @ -118,7 +118,7 @@ export class AddonBlogEntriesComponent implements OnInit { | ||||
|         const loadPage = this.onlyMyEntries ? this.userPageLoaded : this.pageLoaded; | ||||
| 
 | ||||
|         return this.blogProvider.getEntries(this.filter, loadPage).then((result) => { | ||||
|             const promises = result.entries.map((entry) => { | ||||
|             const promises = result.entries.map((entry: AddonBlogPostFormatted) => { | ||||
|                 switch (entry.publishstate) { | ||||
|                     case 'draft': | ||||
|                         entry.publishTranslated = 'publishtonoone'; | ||||
| @ -237,5 +237,12 @@ export class AddonBlogEntriesComponent implements OnInit { | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Blog post with some calculated data. | ||||
|  */ | ||||
| type AddonBlogPostFormatted = AddonBlogPost & { | ||||
|     publishTranslated?: string; // Calculated in the app. Key of the string to translate the publish state of the post.
 | ||||
|     user?: any; // Calculated in the app. Data of the user that wrote the post.
 | ||||
| }; | ||||
|  | ||||
| @ -18,6 +18,8 @@ import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; | ||||
| import { CoreTagItem } from '@core/tag/providers/tag'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle blog entries. | ||||
| @ -68,7 +70,7 @@ export class AddonBlogProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise to be resolved when the entries are retrieved. | ||||
|      */ | ||||
|     getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise<any> { | ||||
|     getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise<AddonBlogGetEntriesResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const data = { | ||||
|                 filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value'), | ||||
| @ -105,7 +107,7 @@ export class AddonBlogProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise to be resolved when done. | ||||
|      */ | ||||
|     logView(filter: any = {}, siteId?: string): Promise<any> { | ||||
|     logView(filter: any = {}, siteId?: string): Promise<AddonBlogViewEntriesResult> { | ||||
|         this.pushNotificationsProvider.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId); | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| @ -117,3 +119,48 @@ export class AddonBlogProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by blog's post_exporter. | ||||
|  */ | ||||
| export type AddonBlogPost = { | ||||
|     id: number; // Post/entry id.
 | ||||
|     module: string; // Where it was published the post (blog, blog_external...).
 | ||||
|     userid: number; // Post author.
 | ||||
|     courseid: number; // Course where the post was created.
 | ||||
|     groupid: number; // Group post was created for.
 | ||||
|     moduleid: number; // Module id where the post was created (not used anymore).
 | ||||
|     coursemoduleid: number; // Course module id where the post was created.
 | ||||
|     subject: string; // Post subject.
 | ||||
|     summary: string; // Post summary.
 | ||||
|     summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     content: string; // Post content.
 | ||||
|     uniquehash: string; // Post unique hash.
 | ||||
|     rating: number; // Post rating.
 | ||||
|     format: number; // Post content format.
 | ||||
|     attachment: string; // Post atachment.
 | ||||
|     publishstate: string; // Post publish state.
 | ||||
|     lastmodified: number; // When it was last modified.
 | ||||
|     created: number; // When it was created.
 | ||||
|     usermodified: number; // User that updated the post.
 | ||||
|     summaryfiles: CoreWSExternalFile[]; // Summaryfiles.
 | ||||
|     attachmentfiles?: CoreWSExternalFile[]; // Attachmentfiles.
 | ||||
|     tags?: CoreTagItem[]; // @since 3.7. Tags.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_blog_get_entries. | ||||
|  */ | ||||
| export type AddonBlogGetEntriesResult = { | ||||
|     entries: AddonBlogPost[]; | ||||
|     totalentries: number; // The total number of entries found.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_blog_view_entries. | ||||
|  */ | ||||
| export type AddonBlogViewEntriesResult = { | ||||
|     status: boolean; // Status: true if success.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
|  | ||||
| @ -21,6 +21,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; | ||||
| import { CoreCourseHelperProvider } from '@core/course/providers/helper'; | ||||
| import { AddonBlogEntriesComponent } from '../components/entries/entries'; | ||||
| import { AddonBlogProvider } from './blog'; | ||||
| import { CoreWSExternalFile } from '@providers/ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Course nav handler. | ||||
| @ -100,7 +101,7 @@ export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler { | ||||
| 
 | ||||
|         return this.blogProvider.getEntries({courseid: course.id}).then((result) => { | ||||
|             return result.entries.map((entry) => { | ||||
|                 let files = []; | ||||
|                 let files: CoreWSExternalFile[] = []; | ||||
| 
 | ||||
|                 if (entry.attachmentfiles && entry.attachmentfiles.length) { | ||||
|                     files = entry.attachmentfiles; | ||||
|  | ||||
| @ -19,7 +19,7 @@ import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | ||||
| import { AddonCalendarProvider, AddonCalendarWeek } from '../../providers/calendar'; | ||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
| @ -44,7 +44,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | ||||
| 
 | ||||
|     periodName: string; | ||||
|     weekDays: any[]; | ||||
|     weeks: any[]; | ||||
|     weeks: AddonCalendarWeek[]; | ||||
|     loaded = false; | ||||
|     timeFormat: string; | ||||
|     isCurrentMonth: boolean; | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
|         <ng-container *ngFor="let event of filteredEvents"> | ||||
|             <a ion-item text-wrap [title]="event.name" (click)="eventClicked(event)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> | ||||
|                 <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> | ||||
|                 <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> | ||||
|                 <core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||
|                 <h2><core-format-text [text]="event.name"></core-format-text></h2> | ||||
|                 <p><core-format-text [text]="event.formattedtime"></core-format-text></p> | ||||
|                 <ion-note *ngIf="event.offline && !event.deleted" item-end> | ||||
|  | ||||
| @ -17,7 +17,7 @@ import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | ||||
| import { AddonCalendarProvider, AddonCalendarCalendarEvent } from '../../providers/calendar'; | ||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
| @ -43,8 +43,8 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, | ||||
|     protected categoriesRetrieved = false; | ||||
|     protected categories = {}; | ||||
|     protected currentSiteId: string; | ||||
|     protected events = []; // Events (both online and offline).
 | ||||
|     protected onlineEvents = []; | ||||
|     protected events: AddonCalendarCalendarEvent[] = []; // Events (both online and offline).
 | ||||
|     protected onlineEvents: AddonCalendarCalendarEvent[] = []; | ||||
|     protected offlineEvents = []; // Offline events.
 | ||||
|     protected deletedEvents = []; // Events deleted in offline.
 | ||||
|     protected lookAhead: number; | ||||
|  | ||||
| @ -50,7 +50,7 @@ | ||||
|             <ng-container *ngFor="let event of filteredEvents"> | ||||
|                 <ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.item-dimmed]="event.ispast" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> | ||||
|                     <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> | ||||
|                     <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> | ||||
|                     <core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||
|                     <h2><core-format-text [text]="event.name"></core-format-text></h2> | ||||
|                     <p><core-format-text [text]="event.formattedtime"></core-format-text></p> | ||||
|                     <ion-note *ngIf="event.offline && !event.deleted" item-end> | ||||
|  | ||||
| @ -20,7 +20,7 @@ import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | ||||
| import { AddonCalendarProvider, AddonCalendarCalendarEvent } from '../../providers/calendar'; | ||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||
| import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | ||||
| @ -45,7 +45,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|     protected day: number; | ||||
|     protected categories = {}; | ||||
|     protected events = []; // Events (both online and offline).
 | ||||
|     protected onlineEvents = []; | ||||
|     protected onlineEvents: AddonCalendarCalendarEvent[] = []; | ||||
|     protected offlineEvents = {}; // Offline events.
 | ||||
|     protected offlineEditedEventsIds = []; // IDs of events edited in offline.
 | ||||
|     protected deletedEvents = []; // Events deleted in offline.
 | ||||
| @ -287,7 +287,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|         return this.calendarProvider.getDayEvents(this.year, this.month, this.day).catch((error) => { | ||||
|             if (!this.appProvider.isOnline()) { | ||||
|                 // Allow navigating to non-cached days in offline (behave as if using emergency cache).
 | ||||
|                 return Promise.resolve({ events: [] }); | ||||
|                 return Promise.resolve({ events: <AddonCalendarCalendarEvent[]> [] }); | ||||
|             } else { | ||||
|                 return Promise.reject(error); | ||||
|             } | ||||
|  | ||||
| @ -134,7 +134,7 @@ | ||||
|                 <div *ngIf="event && event.repeatid" text-wrap radio-group [formControlName]="'repeateditall'" class="addon-calendar-radio-container"> | ||||
|                     <ion-item class="addon-calendar-radio-title"><h2>{{ 'addon.calendar.repeatedevents' | translate }}</h2></ion-item> | ||||
|                     <ion-item> | ||||
|                         <ion-label>{{ 'addon.calendar.repeateditall' | translate:{$a: event.othereventscount} }}</ion-label> | ||||
|                         <ion-label>{{ 'addon.calendar.repeateditall' | translate:{$a: otherEventsCount} }}</ion-label> | ||||
|                         <ion-radio [value]="1"></ion-radio> | ||||
|                     </ion-item> | ||||
|                     <ion-item> | ||||
|  | ||||
| @ -27,7 +27,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts'; | ||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | ||||
| import { AddonCalendarProvider, AddonCalendarGetAccessInfoResult, AddonCalendarEvent } from '../../providers/calendar'; | ||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||
| import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | ||||
| @ -58,7 +58,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | ||||
|     courseGroupSet = false; | ||||
|     advanced = false; | ||||
|     errors: any; | ||||
|     event: any; // The event object (when editing an event).
 | ||||
|     event: AddonCalendarEvent; // The event object (when editing an event).
 | ||||
|     otherEventsCount: number; | ||||
| 
 | ||||
|     // Form variables.
 | ||||
|     eventForm: FormGroup; | ||||
| @ -70,7 +71,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | ||||
|     protected courseId: number; | ||||
|     protected originalData: any; | ||||
|     protected currentSite: CoreSite; | ||||
|     protected types: any; // Object with the supported types.
 | ||||
|     protected types: {[name: string]: boolean}; // Object with the supported types.
 | ||||
|     protected showAll: boolean; | ||||
|     protected isDestroyed = false; | ||||
|     protected error = false; | ||||
| @ -152,7 +153,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected fetchData(refresh?: boolean): Promise<any> { | ||||
|         let accessInfo; | ||||
|         let accessInfo: AddonCalendarGetAccessInfoResult; | ||||
| 
 | ||||
|         this.error = false; | ||||
| 
 | ||||
| @ -197,7 +198,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | ||||
|                         promises.push(this.calendarProvider.getEventById(this.eventId).then((event) => { | ||||
|                             this.event = event; | ||||
|                             if (event && event.repeatid) { | ||||
|                                 event.othereventscount = event.eventcount ? event.eventcount - 1 : ''; | ||||
|                                 this.otherEventsCount = event.eventcount ? event.eventcount - 1 : 0; | ||||
|                             } | ||||
| 
 | ||||
|                             return event; | ||||
| @ -489,7 +490,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | ||||
| 
 | ||||
|         // Send the data.
 | ||||
|         const modal = this.domUtils.showModalLoading('core.sending', true); | ||||
|         let event; | ||||
|         let event: AddonCalendarEvent; | ||||
| 
 | ||||
|         this.calendarProvider.submitEvent(this.eventId, data).then((result) => { | ||||
|             event = result.event; | ||||
| @ -497,7 +498,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { | ||||
|             if (result.sent) { | ||||
|                 // Event created or edited, invalidate right days & months.
 | ||||
|                 const numberOfRepetitions = formData.repeat ? formData.repeats : | ||||
|                     (data.repeateditall && this.event.othereventscount ? this.event.othereventscount + 1 : 1); | ||||
|                     (data.repeateditall && this.otherEventsCount ? this.otherEventsCount + 1 : 1); | ||||
| 
 | ||||
|                 return this.calendarHelper.refreshAfterChangeEvent(result.event, numberOfRepetitions).catch(() => { | ||||
|                     // Ignore errors.
 | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|     <ion-navbar core-back-button> | ||||
|         <ion-title> | ||||
|             <img *ngIf="event && event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon"> | ||||
|             <core-icon *ngIf="event && event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> | ||||
|             <core-icon *ngIf="event && event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||
|             <core-format-text *ngIf="event" [text]="event.name"></core-format-text> | ||||
|         </ion-title> | ||||
|         <ion-buttons end> | ||||
| @ -32,7 +32,7 @@ | ||||
|             <ion-card-content *ngIf="event"> | ||||
|                 <ion-item text-wrap *ngIf="isSplitViewOn"> | ||||
|                     <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start alt="" role="presentation" class="core-module-icon"> | ||||
|                     <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> | ||||
|                     <core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||
|                     <h2>{{ 'addon.calendar.eventname' | translate }}</h2> | ||||
|                     <p><core-format-text [text]="event.name"></core-format-text></p> | ||||
|                     <ion-note item-end *ngIf="event.deleted"> | ||||
|  | ||||
| @ -34,7 +34,7 @@ | ||||
|                     </ion-item-divider> | ||||
|                     <a ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> | ||||
|                         <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> | ||||
|                         <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> | ||||
|                         <core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon> | ||||
|                         <h2><core-format-text [text]="event.name"></core-format-text></h2> | ||||
|                         <p> | ||||
|                             {{ event.timestart * 1000 | coreFormatDate: "strftimetime" }} | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| 
 | ||||
| import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core'; | ||||
| import { IonicPage, Content, NavParams, NavController } from 'ionic-angular'; | ||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | ||||
| import { AddonCalendarProvider, AddonCalendarGetEventsEvent } from '../../providers/calendar'; | ||||
| import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; | ||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||
| import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; | ||||
| @ -62,7 +62,7 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|     protected manualSyncObserver: any; | ||||
|     protected onlineObserver: any; | ||||
|     protected currentSiteId: string; | ||||
|     protected onlineEvents = []; | ||||
|     protected onlineEvents: AddonCalendarGetEventsEvent[] = []; | ||||
|     protected offlineEvents = []; | ||||
|     protected deletedEvents = []; | ||||
| 
 | ||||
| @ -70,7 +70,7 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|     eventsLoaded = false; | ||||
|     events = []; // Events (both online and offline).
 | ||||
|     notificationsEnabled = false; | ||||
|     filteredEvents = []; | ||||
|     filteredEvents: AddonCalendarGetEventsEvent[] = []; | ||||
|     canLoadMore = false; | ||||
|     loadMoreError = false; | ||||
|     courseId: number; | ||||
| @ -402,7 +402,7 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|      * | ||||
|      * @return Filtered events. | ||||
|      */ | ||||
|     protected getFilteredEvents(): any[] { | ||||
|     protected getFilteredEvents(): AddonCalendarGetEventsEvent[] { | ||||
|         if (!this.courseId) { | ||||
|             // No filter, display everything.
 | ||||
|             return this.events; | ||||
| @ -581,7 +581,7 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|      * @param event Event info. | ||||
|      * @return If date has changed and should be shown. | ||||
|      */ | ||||
|     protected endsSameDay(event: any): boolean { | ||||
|     protected endsSameDay(event: AddonCalendarGetEventsEvent): boolean { | ||||
|         if (!event.timeduration) { | ||||
|             // No duration.
 | ||||
|             return true; | ||||
|  | ||||
| @ -31,6 +31,7 @@ import { SQLiteDB } from '@classes/sqlitedb'; | ||||
| import { AddonCalendarOfflineProvider } from './calendar-offline'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreWSExternalWarning, CoreWSDate } from '@providers/ws'; | ||||
| import * as moment from 'moment'; | ||||
| 
 | ||||
| /** | ||||
| @ -489,7 +490,7 @@ export class AddonCalendarProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     deleteEventOnline(eventId: number, deleteAll?: boolean, siteId?: string): Promise<any> { | ||||
|     deleteEventOnline(eventId: number, deleteAll?: boolean, siteId?: string): Promise<null> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
|             const params = { | ||||
| @ -535,22 +536,6 @@ export class AddonCalendarProvider { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if event ends the same day or not. | ||||
|      * | ||||
|      * @param event Event info. | ||||
|      * @return If the . | ||||
|      */ | ||||
|     endsSameDay(event: any): boolean { | ||||
|         if (!event.timeduration) { | ||||
|             // No duration.
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         // Check if day has changed.
 | ||||
|         return moment(event.timestart * 1000).isSame((event.timestart + event.timeduration) * 1000, 'day'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Format event time. Similar to calendar_format_event_time. | ||||
|      * | ||||
| @ -562,8 +547,8 @@ export class AddonCalendarProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the formatted event time. | ||||
|      */ | ||||
|     formatEventTime(event: any, format: string, useCommonWords: boolean = true, seenDay?: number, showTime: number = 0, | ||||
|             siteId?: string): Promise<string> { | ||||
|     formatEventTime(event: AddonCalendarAnyEvent, format: string, useCommonWords: boolean = true, seenDay?: number, | ||||
|             showTime: number = 0, siteId?: string): Promise<string> { | ||||
| 
 | ||||
|         const start = event.timestart * 1000, | ||||
|             end = (event.timestart + event.timeduration) * 1000; | ||||
| @ -635,7 +620,7 @@ export class AddonCalendarProvider { | ||||
|      * @return Promise resolved with object with access information. | ||||
|      * @since 3.7 | ||||
|      */ | ||||
|     getAccessInformation(courseId?: number, siteId?: string): Promise<any> { | ||||
|     getAccessInformation(courseId?: number, siteId?: string): Promise<AddonCalendarGetAccessInfoResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params: any = {}, | ||||
|                 preSets = { | ||||
| @ -680,7 +665,7 @@ export class AddonCalendarProvider { | ||||
|      * @return Promise resolved with an object indicating the types. | ||||
|      * @since 3.7 | ||||
|      */ | ||||
|     getAllowedEventTypes(courseId?: number, siteId?: string): Promise<any> { | ||||
|     getAllowedEventTypes(courseId?: number, siteId?: string): Promise<{[name: string]: boolean}> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params: any = {}, | ||||
|                 preSets = { | ||||
| @ -691,7 +676,8 @@ export class AddonCalendarProvider { | ||||
|                 params.courseid = courseId; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_calendar_get_allowed_event_types', params, preSets).then((response) => { | ||||
|             return site.read('core_calendar_get_allowed_event_types', params, preSets) | ||||
|                     .then((response: AddonCalendarGetAllowedEventTypesResult) => { | ||||
|                 // Convert the array to an object.
 | ||||
|                 const result = {}; | ||||
| 
 | ||||
| @ -812,11 +798,10 @@ export class AddonCalendarProvider { | ||||
|      * Get a calendar event. If the server request fails and data is not cached, try to get it from local DB. | ||||
|      * | ||||
|      * @param id Event ID. | ||||
|      * @param refresh True when we should update the event data. | ||||
|      * @param siteId ID of the site. If not defined, use current site. | ||||
|      * @return Promise resolved when the event data is retrieved. | ||||
|      */ | ||||
|     getEvent(id: number, siteId?: string): Promise<any> { | ||||
|     getEvent(id: number, siteId?: string): Promise<AddonCalendarGetEventsEvent> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const preSets = { | ||||
|                     cacheKey: this.getEventCacheKey(id), | ||||
| @ -834,7 +819,8 @@ export class AddonCalendarProvider { | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => { | ||||
|             return site.read('core_calendar_get_calendar_events', data, preSets) | ||||
|                     .then((response: AddonCalendarGetEventsResult) => { | ||||
|                 // The WebService returns all category events. Check the response to search for the event we want.
 | ||||
|                 const event = response.events.find((e) => { return e.id == id; }); | ||||
| 
 | ||||
| @ -849,12 +835,11 @@ export class AddonCalendarProvider { | ||||
|      * Get a calendar event by ID. This function returns more data than getEvent, but it isn't available in all Moodles. | ||||
|      * | ||||
|      * @param id Event ID. | ||||
|      * @param refresh True when we should update the event data. | ||||
|      * @param siteId ID of the site. If not defined, use current site. | ||||
|      * @return Promise resolved when the event data is retrieved. | ||||
|      * @since 3.4 | ||||
|      */ | ||||
|     getEventById(id: number, siteId?: string): Promise<any> { | ||||
|     getEventById(id: number, siteId?: string): Promise<AddonCalendarEvent> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const preSets = { | ||||
|                     cacheKey: this.getEventCacheKey(id), | ||||
| @ -864,7 +849,8 @@ export class AddonCalendarProvider { | ||||
|                     eventid: id | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_calendar_get_calendar_event_by_id', data, preSets).then((response) => { | ||||
|             return site.read('core_calendar_get_calendar_event_by_id', data, preSets) | ||||
|                     .then((response: AddonCalendarGetEventByIdResult) => { | ||||
|                 return response.event; | ||||
|             }).catch((error) => { | ||||
|                 return this.getEventFromLocalDb(id).catch(() => { | ||||
| @ -918,7 +904,7 @@ export class AddonCalendarProvider { | ||||
|      * @param siteId ID of the site the event belongs to. If not defined, use current site. | ||||
|      * @return Promise resolved when the notification is updated. | ||||
|      */ | ||||
|     addEventReminder(event: any, time: number, siteId?: string): Promise<any> { | ||||
|     addEventReminder(event: AddonCalendarAnyEvent, time: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const reminder = { | ||||
|                 eventid: event.id, | ||||
| @ -976,7 +962,7 @@ export class AddonCalendarProvider { | ||||
|      * @return Promise resolved with the response. | ||||
|      */ | ||||
|     getDayEvents(year: number, month: number, day: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, | ||||
|             siteId?: string): Promise<any> { | ||||
|             siteId?: string): Promise<AddonCalendarCalendarDay> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
| @ -1003,7 +989,7 @@ export class AddonCalendarProvider { | ||||
|                 preSets.emergencyCache = false; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_calendar_get_calendar_day_view', data, preSets).then((response) => { | ||||
|             return site.read('core_calendar_get_calendar_day_view', data, preSets).then((response: AddonCalendarCalendarDay) => { | ||||
|                 this.storeEventsInLocalDB(response.events, siteId); | ||||
| 
 | ||||
|                 return response; | ||||
| @ -1071,10 +1057,10 @@ export class AddonCalendarProvider { | ||||
|      * @param daysToStart Number of days from now to start getting events. | ||||
|      * @param daysInterval Number of days between timestart and timeend. | ||||
|      * @param siteId Site to get the events from. If not defined, use current site. | ||||
|      * @return Promise to be resolved when the participants are retrieved. | ||||
|      * @return Promise to be resolved when the events are retrieved. | ||||
|      */ | ||||
|     getEventsList(initialTime?: number, daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL, | ||||
|             siteId?: string): Promise<any[]> { | ||||
|             siteId?: string): Promise<AddonCalendarGetEventsEvent[]> { | ||||
| 
 | ||||
|         initialTime = initialTime || this.timeUtils.timestamp(); | ||||
| 
 | ||||
| @ -1122,7 +1108,9 @@ export class AddonCalendarProvider { | ||||
|                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES | ||||
|                 }; | ||||
| 
 | ||||
|                 return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => { | ||||
|                 return site.read('core_calendar_get_calendar_events', data, preSets) | ||||
|                         .then((response: AddonCalendarGetEventsResult) => { | ||||
| 
 | ||||
|                     if (!this.canViewMonthInSite(site)) { | ||||
|                         // Store events only in 3.1-3.3. In 3.4+ we'll use the new WS that return more info.
 | ||||
|                         this.storeEventsInLocalDB(response.events, siteId); | ||||
| @ -1178,7 +1166,7 @@ export class AddonCalendarProvider { | ||||
|      * @return Promise resolved with the response. | ||||
|      */ | ||||
|     getMonthlyEvents(year: number, month: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string) | ||||
|             : Promise<any> { | ||||
|             : Promise<AddonCalendarMonth> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
| @ -1210,7 +1198,9 @@ export class AddonCalendarProvider { | ||||
|                 preSets.emergencyCache = false; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_calendar_get_calendar_monthly_view', data, preSets).then((response) => { | ||||
|             return site.read('core_calendar_get_calendar_monthly_view', data, preSets) | ||||
|                     .then((response: AddonCalendarMonth) => { | ||||
| 
 | ||||
|                 response.weeks.forEach((week) => { | ||||
|                     week.days.forEach((day) => { | ||||
|                         this.storeEventsInLocalDB(day.events, siteId); | ||||
| @ -1270,7 +1260,8 @@ export class AddonCalendarProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the response. | ||||
|      */ | ||||
|     getUpcomingEvents(courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string): Promise<any> { | ||||
|     getUpcomingEvents(courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string) | ||||
|             : Promise<AddonCalendarUpcoming> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
| @ -1293,7 +1284,7 @@ export class AddonCalendarProvider { | ||||
|                 preSets.emergencyCache = false; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_calendar_get_calendar_upcoming_view', data, preSets).then((response) => { | ||||
|             return site.read('core_calendar_get_calendar_upcoming_view', data, preSets).then((response: AddonCalendarUpcoming) => { | ||||
|                 this.storeEventsInLocalDB(response.events, siteId); | ||||
| 
 | ||||
|                 return response; | ||||
| @ -1604,11 +1595,14 @@ export class AddonCalendarProvider { | ||||
|      * If local notification plugin is not enabled, resolve the promise. | ||||
|      * | ||||
|      * @param event Event to schedule. | ||||
|      * @param reminderId The reminder ID. | ||||
|      * @param time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start". | ||||
|      * @param siteId Site ID the event belongs to. If not defined, use current site. | ||||
|      * @return Promise resolved when the notification is scheduled. | ||||
|      */ | ||||
|     protected scheduleEventNotification(event: any, reminderId: number, time: number, siteId?: string): Promise<void> { | ||||
|     protected scheduleEventNotification(event: AddonCalendarAnyEvent, reminderId: number, time: number, siteId?: string) | ||||
|             : Promise<void> { | ||||
| 
 | ||||
|         if (this.localNotificationsProvider.isAvailable()) { | ||||
|             siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
| @ -1672,7 +1666,7 @@ export class AddonCalendarProvider { | ||||
|      * @param siteId ID of the site the events belong to. If not defined, use current site. | ||||
|      * @return Promise resolved when all the notifications have been scheduled. | ||||
|      */ | ||||
|     scheduleEventsNotifications(events: any[], siteId?: string): Promise<any[]> { | ||||
|     scheduleEventsNotifications(events: AddonCalendarAnyEvent[], siteId?: string): Promise<any[]> { | ||||
| 
 | ||||
|         if (this.localNotificationsProvider.isAvailable()) { | ||||
|             siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| @ -1803,11 +1797,10 @@ export class AddonCalendarProvider { | ||||
|      * @param timeCreated The time the event was created. Only if modifying a new offline event. | ||||
|      * @param forceOffline True to always save it in offline. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the event and a boolean indicating if data was | ||||
|      *         sent to server or stored in offline. | ||||
|      * @return Promise resolved with the event and a boolean indicating if data was sent to server or stored in offline. | ||||
|      */ | ||||
|     submitEvent(eventId: number, formData: any, timeCreated?: number, forceOffline?: boolean, siteId?: string): | ||||
|             Promise<{sent: boolean, event: any}> { | ||||
|             Promise<{sent: boolean, event: AddonCalendarEvent}> { | ||||
| 
 | ||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
| @ -1847,7 +1840,7 @@ export class AddonCalendarProvider { | ||||
|      * @param siteId Site ID. If not provided, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     submitEventOnline(eventId: number, formData: any, siteId?: string): Promise<any> { | ||||
|     submitEventOnline(eventId: number, formData: any, siteId?: string): Promise<AddonCalendarEvent> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             // Add data that is "hidden" in web.
 | ||||
|             formData.id = eventId || 0; | ||||
| @ -1865,10 +1858,12 @@ export class AddonCalendarProvider { | ||||
|                 formdata: this.utils.objectToGetParams(formData) | ||||
|             }; | ||||
| 
 | ||||
|             return site.write('core_calendar_submit_create_update_form', params).then((result) => { | ||||
|             return site.write('core_calendar_submit_create_update_form', params) | ||||
|                     .then((result: AddonCalendarSubmitCreateUpdateFormResult): AddonCalendarEvent => { | ||||
| 
 | ||||
|                 if (result.validationerror) { | ||||
|                     // Simulate a WS error.
 | ||||
|                     return Promise.reject({ | ||||
|                     return <any> Promise.reject({ | ||||
|                         message: this.translate.instant('core.invalidformdata'), | ||||
|                         errorcode: 'validationerror' | ||||
|                     }); | ||||
| @ -1879,3 +1874,337 @@ export class AddonCalendarProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's events_exporter. | ||||
|  */ | ||||
| export type AddonCalendarEvents = { | ||||
|     events: AddonCalendarEvent[]; // Events.
 | ||||
|     firstid: number; // Firstid.
 | ||||
|     lastid: number; // Lastid.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's events_grouped_by_course_exporter. | ||||
|  */ | ||||
| export type AddonCalendarEventsGroupedByCourse = { | ||||
|     groupedbycourse: AddonCalendarEventsSameCourse[]; // Groupped by course.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's events_same_course_exporter. | ||||
|  */ | ||||
| export type AddonCalendarEventsSameCourse = AddonCalendarEvents & { | ||||
|     courseid: number; // Courseid.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's event_exporter_base. | ||||
|  */ | ||||
| export type AddonCalendarEventBase = { | ||||
|     id: number; // Id.
 | ||||
|     name: string; // Name.
 | ||||
|     description?: string; // Description.
 | ||||
|     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     location?: string; // Location.
 | ||||
|     categoryid?: number; // Categoryid.
 | ||||
|     groupid?: number; // Groupid.
 | ||||
|     userid?: number; // Userid.
 | ||||
|     repeatid?: number; // Repeatid.
 | ||||
|     eventcount?: number; // Eventcount.
 | ||||
|     modulename?: string; // Modulename.
 | ||||
|     instance?: number; // Instance.
 | ||||
|     eventtype: string; // Eventtype.
 | ||||
|     timestart: number; // Timestart.
 | ||||
|     timeduration: number; // Timeduration.
 | ||||
|     timesort: number; // Timesort.
 | ||||
|     visible: number; // Visible.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     icon: { | ||||
|         key: string; // Key.
 | ||||
|         component: string; // Component.
 | ||||
|         alttext: string; // Alttext.
 | ||||
|     }; | ||||
|     category?: { | ||||
|         id: number; // Id.
 | ||||
|         name: string; // Name.
 | ||||
|         idnumber: string; // Idnumber.
 | ||||
|         description?: string; // Description.
 | ||||
|         parent: number; // Parent.
 | ||||
|         coursecount: number; // Coursecount.
 | ||||
|         visible: number; // Visible.
 | ||||
|         timemodified: number; // Timemodified.
 | ||||
|         depth: number; // Depth.
 | ||||
|         nestedname: string; // Nestedname.
 | ||||
|         url: string; // Url.
 | ||||
|     }; | ||||
|     course?: { | ||||
|         id: number; // Id.
 | ||||
|         fullname: string; // Fullname.
 | ||||
|         shortname: string; // Shortname.
 | ||||
|         idnumber: string; // Idnumber.
 | ||||
|         summary: string; // Summary.
 | ||||
|         summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|         startdate: number; // Startdate.
 | ||||
|         enddate: number; // Enddate.
 | ||||
|         visible: boolean; // Visible.
 | ||||
|         fullnamedisplay: string; // Fullnamedisplay.
 | ||||
|         viewurl: string; // Viewurl.
 | ||||
|         courseimage: string; // Courseimage.
 | ||||
|         progress?: number; // Progress.
 | ||||
|         hasprogress: boolean; // Hasprogress.
 | ||||
|         isfavourite: boolean; // Isfavourite.
 | ||||
|         hidden: boolean; // Hidden.
 | ||||
|         timeaccess?: number; // Timeaccess.
 | ||||
|         showshortname: boolean; // Showshortname.
 | ||||
|         coursecategory: string; // Coursecategory.
 | ||||
|     }; | ||||
|     subscription?: { | ||||
|         displayeventsource: boolean; // Displayeventsource.
 | ||||
|         subscriptionname?: string; // Subscriptionname.
 | ||||
|         subscriptionurl?: string; // Subscriptionurl.
 | ||||
|     }; | ||||
|     canedit: boolean; // Canedit.
 | ||||
|     candelete: boolean; // Candelete.
 | ||||
|     deleteurl: string; // Deleteurl.
 | ||||
|     editurl: string; // Editurl.
 | ||||
|     viewurl: string; // Viewurl.
 | ||||
|     formattedtime: string; // Formattedtime.
 | ||||
|     isactionevent: boolean; // Isactionevent.
 | ||||
|     iscourseevent: boolean; // Iscourseevent.
 | ||||
|     iscategoryevent: boolean; // Iscategoryevent.
 | ||||
|     groupname?: string; // Groupname.
 | ||||
|     normalisedeventtype: string; // Normalisedeventtype.
 | ||||
|     normalisedeventtypetext: string; // Normalisedeventtypetext.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's event_exporter.  Don't confuse it with AddonCalendarCalendarEvent. | ||||
|  */ | ||||
| export type AddonCalendarEvent = AddonCalendarEventBase & { | ||||
|     url: string; // Url.
 | ||||
|     action?: { | ||||
|         name: string; // Name.
 | ||||
|         url: string; // Url.
 | ||||
|         itemcount: number; // Itemcount.
 | ||||
|         actionable: boolean; // Actionable.
 | ||||
|         showitemcount: boolean; // Showitemcount.
 | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's calendar_event_exporter. Don't confuse it with AddonCalendarEvent. | ||||
|  */ | ||||
| export type AddonCalendarCalendarEvent = AddonCalendarEventBase & { | ||||
|     url: string; // Url.
 | ||||
|     islastday: boolean; // Islastday.
 | ||||
|     popupname: string; // Popupname.
 | ||||
|     mindaytimestamp?: number; // Mindaytimestamp.
 | ||||
|     mindayerror?: string; // Mindayerror.
 | ||||
|     maxdaytimestamp?: number; // Maxdaytimestamp.
 | ||||
|     maxdayerror?: string; // Maxdayerror.
 | ||||
|     draggable: boolean; // Draggable.
 | ||||
| } & AddonCalendarCalendarEventCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Any of the possible types of events. | ||||
|  */ | ||||
| export type AddonCalendarAnyEvent = AddonCalendarGetEventsEvent | AddonCalendarEvent | AddonCalendarCalendarEvent; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's calendar_day_exporter. Don't confuse it with AddonCalendarDay. | ||||
|  */ | ||||
| export type AddonCalendarCalendarDay = { | ||||
|     events: AddonCalendarCalendarEvent[]; // Events.
 | ||||
|     defaulteventcontext: number; // Defaulteventcontext.
 | ||||
|     filter_selector: string; // Filter_selector.
 | ||||
|     courseid: number; // Courseid.
 | ||||
|     categoryid?: number; // Categoryid.
 | ||||
|     neweventtimestamp: number; // Neweventtimestamp.
 | ||||
|     date: CoreWSDate; | ||||
|     periodname: string; // Periodname.
 | ||||
|     previousperiod: CoreWSDate; | ||||
|     previousperiodlink: string; // Previousperiodlink.
 | ||||
|     previousperiodname: string; // Previousperiodname.
 | ||||
|     nextperiod: CoreWSDate; | ||||
|     nextperiodname: string; // Nextperiodname.
 | ||||
|     nextperiodlink: string; // Nextperiodlink.
 | ||||
|     larrow: string; // Larrow.
 | ||||
|     rarrow: string; // Rarrow.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's month_exporter. | ||||
|  */ | ||||
| export type AddonCalendarMonth = { | ||||
|     url: string; // Url.
 | ||||
|     courseid: number; // Courseid.
 | ||||
|     categoryid?: number; // Categoryid.
 | ||||
|     filter_selector?: string; // Filter_selector.
 | ||||
|     weeks: AddonCalendarWeek[]; // Weeks.
 | ||||
|     daynames: AddonCalendarDayName[]; // Daynames.
 | ||||
|     view: string; // View.
 | ||||
|     date: CoreWSDate; | ||||
|     periodname: string; // Periodname.
 | ||||
|     includenavigation: boolean; // Includenavigation.
 | ||||
|     initialeventsloaded: boolean; // Initialeventsloaded.
 | ||||
|     previousperiod: CoreWSDate; | ||||
|     previousperiodlink: string; // Previousperiodlink.
 | ||||
|     previousperiodname: string; // Previousperiodname.
 | ||||
|     nextperiod: CoreWSDate; | ||||
|     nextperiodname: string; // Nextperiodname.
 | ||||
|     nextperiodlink: string; // Nextperiodlink.
 | ||||
|     larrow: string; // Larrow.
 | ||||
|     rarrow: string; // Rarrow.
 | ||||
|     defaulteventcontext: number; // Defaulteventcontext.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's week_exporter. | ||||
|  */ | ||||
| export type AddonCalendarWeek = { | ||||
|     prepadding: number[]; // Prepadding.
 | ||||
|     postpadding: number[]; // Postpadding.
 | ||||
|     days: AddonCalendarWeekDay[]; // Days.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's week_day_exporter. | ||||
|  */ | ||||
| export type AddonCalendarWeekDay = AddonCalendarDay & { | ||||
|     istoday: boolean; // Istoday.
 | ||||
|     isweekend: boolean; // Isweekend.
 | ||||
|     popovertitle: string; // Popovertitle.
 | ||||
|     ispast?: boolean; // Calculated in the app. Whether the day is in the past.
 | ||||
|     filteredEvents?: AddonCalendarCalendarEvent[]; // Calculated in the app. Filtered events.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's day_exporter. Don't confuse it with AddonCalendarCalendarDay. | ||||
|  */ | ||||
| export type AddonCalendarDay = { | ||||
|     seconds: number; // Seconds.
 | ||||
|     minutes: number; // Minutes.
 | ||||
|     hours: number; // Hours.
 | ||||
|     mday: number; // Mday.
 | ||||
|     wday: number; // Wday.
 | ||||
|     year: number; // Year.
 | ||||
|     yday: number; // Yday.
 | ||||
|     timestamp: number; // Timestamp.
 | ||||
|     neweventtimestamp: number; // Neweventtimestamp.
 | ||||
|     viewdaylink?: string; // Viewdaylink.
 | ||||
|     events: AddonCalendarCalendarEvent[]; // Events.
 | ||||
|     hasevents: boolean; // Hasevents.
 | ||||
|     calendareventtypes: string[]; // Calendareventtypes.
 | ||||
|     previousperiod: number; // Previousperiod.
 | ||||
|     nextperiod: number; // Nextperiod.
 | ||||
|     navigation: string; // Navigation.
 | ||||
|     haslastdayofevent: boolean; // Haslastdayofevent.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's day_name_exporter. | ||||
|  */ | ||||
| export type AddonCalendarDayName = { | ||||
|     dayno: number; // Dayno.
 | ||||
|     shortname: string; // Shortname.
 | ||||
|     fullname: string; // Fullname.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by calendar's calendar_upcoming_exporter. | ||||
|  */ | ||||
| export type AddonCalendarUpcoming = { | ||||
|     events: AddonCalendarCalendarEvent[]; // Events.
 | ||||
|     defaulteventcontext: number; // Defaulteventcontext.
 | ||||
|     filter_selector: string; // Filter_selector.
 | ||||
|     courseid: number; // Courseid.
 | ||||
|     categoryid?: number; // Categoryid.
 | ||||
|     isloggedin: boolean; // Isloggedin.
 | ||||
|     date: CoreWSDate; // Date.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_calendar_get_calendar_access_information. | ||||
|  */ | ||||
| export type AddonCalendarGetAccessInfoResult = { | ||||
|     canmanageentries: boolean; // Whether the user can manage entries.
 | ||||
|     canmanageownentries: boolean; // Whether the user can manage its own entries.
 | ||||
|     canmanagegroupentries: boolean; // Whether the user can manage group entries.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_calendar_get_allowed_event_types. | ||||
|  */ | ||||
| export type AddonCalendarGetAllowedEventTypesResult = { | ||||
|     allowedeventtypes: string[]; | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_calendar_get_calendar_events. | ||||
|  */ | ||||
| export type AddonCalendarGetEventsResult = { | ||||
|     events: AddonCalendarGetEventsEvent[]; | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Event data returned by WS core_calendar_get_calendar_events. | ||||
|  */ | ||||
| export type AddonCalendarGetEventsEvent = { | ||||
|     id: number; // Event id.
 | ||||
|     name: string; // Event name.
 | ||||
|     description?: string; // Description.
 | ||||
|     format: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     courseid: number; // Course id.
 | ||||
|     categoryid?: number; // @since 3.4. Category id (only for category events).
 | ||||
|     groupid: number; // Group id.
 | ||||
|     userid: number; // User id.
 | ||||
|     repeatid: number; // Repeat id.
 | ||||
|     modulename?: string; // Module name.
 | ||||
|     instance: number; // Instance id.
 | ||||
|     eventtype: string; // Event type.
 | ||||
|     timestart: number; // Timestart.
 | ||||
|     timeduration: number; // Time duration.
 | ||||
|     visible: number; // Visible.
 | ||||
|     uuid?: string; // Unique id of ical events.
 | ||||
|     sequence: number; // Sequence.
 | ||||
|     timemodified: number; // Time modified.
 | ||||
|     subscriptionid?: number; // Subscription id.
 | ||||
|     showDate?: boolean; // Calculated in the app. Whether date should be shown before this event.
 | ||||
|     endsSameDay?: boolean; // Calculated in the app. Whether the event finishes the same day it starts.
 | ||||
|     deleted?: boolean; // Calculated in the app. Whether it has been deleted in offline.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_calendar_get_calendar_event_by_id. | ||||
|  */ | ||||
| export type AddonCalendarGetEventByIdResult = { | ||||
|     event: AddonCalendarEvent; // Event.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_calendar_submit_create_update_form. | ||||
|  */ | ||||
| export type AddonCalendarSubmitCreateUpdateFormResult = { | ||||
|     event?: AddonCalendarEvent; // Event.
 | ||||
|     validationerror: boolean; // Invalid form data.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for AddonCalendarCalendarEvent. | ||||
|  */ | ||||
| export type AddonCalendarCalendarEventCalculatedData = { | ||||
|     eventIcon?: string; // Calculated in the app. Event icon.
 | ||||
|     moduleIcon?: string; // Calculated in the app. Module icon.
 | ||||
|     formattedType?: string; // Calculated in the app. Formatted type.
 | ||||
|     duration?: number; // Calculated in the app. Duration of offline event.
 | ||||
|     format?: number; // Calculated in the app. Format of offline event.
 | ||||
|     timedurationuntil?: number; // Calculated in the app. Time duration until of offline event.
 | ||||
|     timedurationminutes?: number; // Calculated in the app. Time duration in minutes of offline event.
 | ||||
|     deleted?: boolean; // Calculated in the app. Whether it has been deleted in offline.
 | ||||
|     ispast?: boolean; // Calculated in the app. Whether the event is in the past.
 | ||||
| }; | ||||
|  | ||||
| @ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreCourseProvider } from '@core/course/providers/course'; | ||||
| import { AddonCalendarProvider } from './calendar'; | ||||
| import { AddonCalendarProvider, AddonCalendarCalendarEvent } from './calendar'; | ||||
| import { CoreConstants } from '@core/constants'; | ||||
| import { CoreConfigProvider } from '@providers/config'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| @ -130,11 +130,11 @@ export class AddonCalendarHelperProvider { | ||||
|      * | ||||
|      * @param e Event to format. | ||||
|      */ | ||||
|     formatEventData(e: any): void { | ||||
|         e.icon = this.EVENTICONS[e.eventtype] || false; | ||||
|         if (!e.icon) { | ||||
|             e.icon = this.courseProvider.getModuleIconSrc(e.modulename); | ||||
|             e.moduleIcon = e.icon; | ||||
|     formatEventData(e: AddonCalendarCalendarEvent): void { | ||||
|         e.eventIcon = this.EVENTICONS[e.eventtype] || ''; | ||||
|         if (!e.eventIcon) { | ||||
|             e.eventIcon = this.courseProvider.getModuleIconSrc(e.modulename); | ||||
|             e.moduleIcon = e.eventIcon; | ||||
|         } | ||||
| 
 | ||||
|         e.formattedType = this.calendarProvider.getEventType(e); | ||||
| @ -160,7 +160,7 @@ export class AddonCalendarHelperProvider { | ||||
|      * @param eventTypes Result of getAllowedEventTypes. | ||||
|      * @return Options. | ||||
|      */ | ||||
|     getEventTypeOptions(eventTypes: any): {name: string, value: string}[] { | ||||
|     getEventTypeOptions(eventTypes: {[name: string]: boolean}): {name: string, value: string}[] { | ||||
|         const options = []; | ||||
| 
 | ||||
|         if (eventTypes.user) { | ||||
|  | ||||
| @ -16,7 +16,7 @@ import { Component, ViewChild, Input } from '@angular/core'; | ||||
| import { Content, NavController } from 'ionic-angular'; | ||||
| import { CoreAppProvider } from '@providers/app'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | ||||
| import { AddonCompetencyProvider, AddonCompetencyDataForCourseCompetenciesPageResult } from '../../providers/competency'; | ||||
| import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||
| 
 | ||||
| /** | ||||
| @ -33,7 +33,7 @@ export class AddonCompetencyCourseComponent { | ||||
|     @Input() userId: number; | ||||
| 
 | ||||
|     competenciesLoaded = false; | ||||
|     competencies: any; | ||||
|     competencies: AddonCompetencyDataForCourseCompetenciesPageResult; | ||||
|     user: any; | ||||
| 
 | ||||
|     constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, | ||||
|  | ||||
| @ -17,7 +17,10 @@ import { IonicPage, NavParams } from 'ionic-angular'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | ||||
| import { | ||||
|     AddonCompetencyProvider, AddonCompetencyDataForCourseCompetenciesPageResult, AddonCompetencyDataForPlanPageResult, | ||||
|     AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency | ||||
| } from '../../providers/competency'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of competencies of a learning plan. | ||||
| @ -36,7 +39,7 @@ export class AddonCompetencyCompetenciesPage { | ||||
|     protected userId: number; | ||||
| 
 | ||||
|     competenciesLoaded = false; | ||||
|     competencies = []; | ||||
|     competencies: AddonCompetencyDataForPlanPageCompetency[] | AddonCompetencyDataForCourseCompetenciesPageCompetency[] = []; | ||||
|     title: string; | ||||
| 
 | ||||
|     constructor(navParams: NavParams, private translate: TranslateService, private domUtils: CoreDomUtilsProvider, | ||||
| @ -59,7 +62,7 @@ export class AddonCompetencyCompetenciesPage { | ||||
|         this.fetchCompetencies().then(() => { | ||||
|             if (!this.competencyId && this.splitviewCtrl.isOn() && this.competencies.length > 0) { | ||||
|                 // Take first and load it.
 | ||||
|                 this.openCompetency(this.competencies[0].id); | ||||
|                 this.openCompetency(this.competencies[0].competency.id); | ||||
|             } | ||||
|         }).finally(() => { | ||||
|             this.competenciesLoaded = true; | ||||
| @ -72,7 +75,7 @@ export class AddonCompetencyCompetenciesPage { | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected fetchCompetencies(): Promise<void> { | ||||
|         let promise; | ||||
|         let promise: Promise<AddonCompetencyDataForPlanPageResult | AddonCompetencyDataForCourseCompetenciesPageResult>; | ||||
| 
 | ||||
|         if (this.planId) { | ||||
|             promise = this.competencyProvider.getLearningPlan(this.planId); | ||||
| @ -83,13 +86,16 @@ export class AddonCompetencyCompetenciesPage { | ||||
|         } | ||||
| 
 | ||||
|         return promise.then((response) => { | ||||
|             if (response.competencycount <= 0) { | ||||
|                 return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound')); | ||||
|             } | ||||
| 
 | ||||
|             if (this.planId) { | ||||
|                 this.title = response.plan.name; | ||||
|                 this.userId = response.plan.userid; | ||||
|                 const resp = <AddonCompetencyDataForPlanPageResult> response; | ||||
| 
 | ||||
|                 if (resp.competencycount <= 0) { | ||||
|                     return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound')); | ||||
|                 } | ||||
| 
 | ||||
|                 this.title = resp.plan.name; | ||||
|                 this.userId = resp.plan.userid; | ||||
|             } else { | ||||
|                 this.title = this.translate.instant('addon.competency.coursecompetencies'); | ||||
|             } | ||||
|  | ||||
| @ -51,22 +51,22 @@ | ||||
|                     <core-format-text [text]="activity.name"></core-format-text> | ||||
|                 </a> | ||||
|             </ion-item> | ||||
|             <ion-item text-wrap *ngIf="competency.usercompetency.status"> | ||||
|             <ion-item text-wrap *ngIf="userCompetency.status"> | ||||
|                 <strong>{{ 'addon.competency.reviewstatus' | translate }}</strong> | ||||
|                 {{ competency.usercompetency.statusname }} | ||||
|                 {{ userCompetency.statusname }} | ||||
|             </ion-item> | ||||
|             <ion-item text-wrap> | ||||
|                 <strong>{{ 'addon.competency.proficient' | translate }}</strong> | ||||
|                 <ion-badge color="success" *ngIf="competency.usercompetency.proficiency"> | ||||
|                 <ion-badge color="success" *ngIf="userCompetency.proficiency"> | ||||
|                     {{ 'core.yes' | translate }} | ||||
|                 </ion-badge> | ||||
|                 <ion-badge color="danger" *ngIf="!competency.usercompetency.proficiency"> | ||||
|                 <ion-badge color="danger" *ngIf="!userCompetency.proficiency"> | ||||
|                     {{ 'core.no' | translate }} | ||||
|                 </ion-badge> | ||||
|             </ion-item> | ||||
|             <ion-item text-wrap> | ||||
|                 <strong>{{ 'addon.competency.rating' | translate }}</strong> | ||||
|                 <ion-badge color="dark">{{ competency.usercompetency.gradename }}</ion-badge> | ||||
|                 <ion-badge color="dark">{{ userCompetency.gradename }}</ion-badge> | ||||
|             </ion-item> | ||||
|         </ion-card> | ||||
| 
 | ||||
|  | ||||
| @ -18,8 +18,14 @@ import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | ||||
| import { | ||||
|     AddonCompetencyProvider, AddonCompetencyUserCompetencySummary, AddonCompetencyUserCompetencySummaryInPlan, | ||||
|     AddonCompetencyUserCompetencySummaryInCourse, AddonCompetencyUserCompetencyPlan, | ||||
|     AddonCompetencyUserCompetency, AddonCompetencyUserCompetencyCourse | ||||
| } from '../../providers/competency'; | ||||
| import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||
| import { CoreUserSummary } from '@core/user/providers/user'; | ||||
| import { CoreCourseModuleSummary } from '@core/course/providers/course'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a learning plan. | ||||
| @ -36,9 +42,10 @@ export class AddonCompetencyCompetencyPage { | ||||
|     courseId: number; | ||||
|     userId: number; | ||||
|     planStatus: number; | ||||
|     coursemodules: any; | ||||
|     user: any; | ||||
|     competency: any; | ||||
|     coursemodules: CoreCourseModuleSummary[]; | ||||
|     user: CoreUserSummary; | ||||
|     competency: AddonCompetencyUserCompetencySummary; | ||||
|     userCompetency: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse; | ||||
| 
 | ||||
|     constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, | ||||
|             private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, | ||||
| @ -79,7 +86,8 @@ export class AddonCompetencyCompetencyPage { | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected fetchCompetency(): Promise<void> { | ||||
|         let promise; | ||||
|         let promise: Promise<AddonCompetencyUserCompetencySummaryInPlan | AddonCompetencyUserCompetencySummaryInCourse>; | ||||
| 
 | ||||
|         if (this.planId) { | ||||
|             this.planStatus = null; | ||||
|             promise = this.competencyProvider.getCompetencyInPlan(this.planId, this.competencyId); | ||||
| @ -90,23 +98,21 @@ export class AddonCompetencyCompetencyPage { | ||||
|         } | ||||
| 
 | ||||
|         return promise.then((competency) => { | ||||
|             competency.usercompetencysummary.usercompetency = competency.usercompetencysummary.usercompetencyplan || | ||||
|                 competency.usercompetencysummary.usercompetency; | ||||
| 
 | ||||
|             this.competency = competency.usercompetencysummary; | ||||
|             this.userCompetency = this.competency.usercompetencyplan || this.competency.usercompetency; | ||||
| 
 | ||||
|             if (this.planId) { | ||||
|                 this.planStatus = competency.plan.status; | ||||
|                 this.planStatus = (<AddonCompetencyUserCompetencySummaryInPlan> competency).plan.status; | ||||
|                 this.competency.usercompetency.statusname = | ||||
|                     this.competencyHelperProvider.getCompetencyStatusName(this.competency.usercompetency.status); | ||||
|             } else { | ||||
|                 this.competency.usercompetency = this.competency.usercompetencycourse; | ||||
|                 this.coursemodules = competency.coursemodules; | ||||
|                 this.userCompetency = this.competency.usercompetencycourse; | ||||
|                 this.coursemodules = (<AddonCompetencyUserCompetencySummaryInCourse> competency).coursemodules; | ||||
|             } | ||||
| 
 | ||||
|             if (this.competency.user.id != this.sitesProvider.getCurrentSiteUserId()) { | ||||
|                 this.competency.user.profileimageurl = this.competency.user.profileimageurl || true; | ||||
| 
 | ||||
|                 // Get the user profile image from the returned object.
 | ||||
|                 // Get the user profile from the returned object.
 | ||||
|                 this.user = this.competency.user; | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ import { Component, Optional } from '@angular/core'; | ||||
| import { IonicPage, NavController, NavParams } from 'ionic-angular'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | ||||
| import { AddonCompetencyProvider, AddonCompetencySummary } from '../../providers/competency'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a learning plan. | ||||
| @ -29,7 +29,7 @@ import { AddonCompetencyProvider } from '../../providers/competency'; | ||||
| export class AddonCompetencyCompetencySummaryPage { | ||||
|     competencyLoaded = false; | ||||
|     competencyId: number; | ||||
|     competency: any; | ||||
|     competency: AddonCompetencySummary; | ||||
| 
 | ||||
|     constructor(private navCtrl: NavController, navParams: NavParams, private domUtils: CoreDomUtilsProvider, | ||||
|             @Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) { | ||||
| @ -41,8 +41,7 @@ export class AddonCompetencyCompetencySummaryPage { | ||||
|      */ | ||||
|     ionViewDidLoad(): void { | ||||
|         this.fetchCompetency().then(() => { | ||||
|             const name = this.competency.competency && this.competency.competency.competency && | ||||
|                     this.competency.competency.competency.shortname; | ||||
|             const name = this.competency.competency && this.competency.competency.shortname; | ||||
| 
 | ||||
|             this.competencyProvider.logCompetencyView(this.competencyId, name).catch(() => { | ||||
|                 // Ignore errors.
 | ||||
|  | ||||
| @ -46,7 +46,8 @@ | ||||
|                 </ion-item> | ||||
|                 <a ion-item text-wrap *ngFor="let competency of plan.competencies" (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname"> | ||||
|                     <h2>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></h2> | ||||
|                     <ion-badge item-end [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge> | ||||
|                     <ion-badge *ngIf="competency.usercompetencyplan" item-end [color]="competency.usercompetencyplan.proficiency ? 'success' : 'danger'">{{ competency.usercompetencyplan.gradename }}</ion-badge> | ||||
|                     <ion-badge *ngIf="!competency.usercompetencyplan" item-end [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge> | ||||
|                 </a> | ||||
|             </ion-list> | ||||
|         </ion-card> | ||||
|  | ||||
| @ -17,7 +17,7 @@ import { IonicPage, NavController, NavParams } from 'ionic-angular'; | ||||
| import { CoreAppProvider } from '@providers/app'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | ||||
| import { AddonCompetencyProvider, AddonCompetencyDataForPlanPageResult } from '../../providers/competency'; | ||||
| import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||
| 
 | ||||
| /** | ||||
| @ -31,7 +31,7 @@ import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||
| export class AddonCompetencyPlanPage { | ||||
|     protected planId: number; | ||||
|     planLoaded = false; | ||||
|     plan: any; | ||||
|     plan: AddonCompetencyDataForPlanPageResult; | ||||
|     user: any; | ||||
| 
 | ||||
|     constructor(private navCtrl: NavController, navParams: NavParams, private appProvider: CoreAppProvider, | ||||
| @ -62,9 +62,6 @@ export class AddonCompetencyPlanPage { | ||||
|                 this.user = user; | ||||
|             }); | ||||
| 
 | ||||
|             plan.competencies.forEach((competency) => { | ||||
|                 competency.usercompetency = competency.usercompetencyplan || competency.usercompetency; | ||||
|             }); | ||||
|             this.plan = plan; | ||||
|         }).catch((message) => { | ||||
|             this.domUtils.showErrorModalDefault(message, 'Error getting learning plan data.'); | ||||
|  | ||||
| @ -16,7 +16,7 @@ import { Component, ViewChild } from '@angular/core'; | ||||
| import { IonicPage, NavParams } from 'ionic-angular'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { AddonCompetencyProvider } from '../../providers/competency'; | ||||
| import { AddonCompetencyProvider, AddonCompetencyPlan } from '../../providers/competency'; | ||||
| import { AddonCompetencyHelperProvider } from '../../providers/helper'; | ||||
| 
 | ||||
| /** | ||||
| @ -33,7 +33,7 @@ export class AddonCompetencyPlanListPage { | ||||
|     protected userId: number; | ||||
|     protected planId: number; | ||||
|     plansLoaded = false; | ||||
|     plans = []; | ||||
|     plans: AddonCompetencyPlan[] = []; | ||||
| 
 | ||||
|     constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private competencyProvider: AddonCompetencyProvider, | ||||
|             private competencyHelperProvider: AddonCompetencyHelperProvider) { | ||||
| @ -66,7 +66,7 @@ export class AddonCompetencyPlanListPage { | ||||
|      */ | ||||
|     protected fetchLearningPlans(): Promise<void> { | ||||
|         return this.competencyProvider.getLearningPlans(this.userId).then((plans) => { | ||||
|             plans.forEach((plan) => { | ||||
|             plans.forEach((plan: AddonCompetencyPlanFormatted) => { | ||||
|                 plan.statusname = this.competencyHelperProvider.getPlanStatusName(plan.status); | ||||
|                 switch (plan.status) { | ||||
|                     case AddonCompetencyProvider.STATUS_ACTIVE: | ||||
| @ -109,3 +109,10 @@ export class AddonCompetencyPlanListPage { | ||||
|         this.splitviewCtrl.push('AddonCompetencyPlanPage', { planId }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Competency plan with some calculated data. | ||||
|  */ | ||||
| type AddonCompetencyPlanFormatted = AddonCompetencyPlan & { | ||||
|     statuscolor?: string; // Calculated in the app. Color of the plan's status.
 | ||||
| }; | ||||
|  | ||||
| @ -17,6 +17,9 @@ import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreCommentsArea } from '@core/comments/providers/comments'; | ||||
| import { CoreUserSummary } from '@core/user/providers/user'; | ||||
| import { CoreCourseSummary, CoreCourseModuleSummary } from '@core/course/providers/course'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle caompetency learning plans. | ||||
| @ -147,7 +150,7 @@ export class AddonCompetencyProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise to be resolved when the plans are retrieved. | ||||
|      */ | ||||
|     getLearningPlans(userId?: number, siteId?: string): Promise<any> { | ||||
|     getLearningPlans(userId?: number, siteId?: string): Promise<AddonCompetencyPlan[]> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| 
 | ||||
| @ -161,7 +164,9 @@ export class AddonCompetencyProvider { | ||||
|                     updateFrequency: CoreSite.FREQUENCY_RARELY | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('tool_lp_data_for_plans_page', params, preSets).then((response) => { | ||||
|             return site.read('tool_lp_data_for_plans_page', params, preSets) | ||||
|                     .then((response: AddonCompetencyDataForPlansPageResult): any => { | ||||
| 
 | ||||
|                 if (response.plans) { | ||||
|                     return response.plans; | ||||
|                 } | ||||
| @ -176,9 +181,9 @@ export class AddonCompetencyProvider { | ||||
|      * | ||||
|      * @param planId ID of the plan. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise to be resolved when the plans are retrieved. | ||||
|      * @return Promise to be resolved when the plan is retrieved. | ||||
|      */ | ||||
|     getLearningPlan(planId: number, siteId?: string): Promise<any> { | ||||
|     getLearningPlan(planId: number, siteId?: string): Promise<AddonCompetencyDataForPlanPageResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
|             this.logger.debug('Get plan ' + planId); | ||||
| @ -191,7 +196,9 @@ export class AddonCompetencyProvider { | ||||
|                     updateFrequency: CoreSite.FREQUENCY_RARELY | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('tool_lp_data_for_plan_page', params, preSets).then((response) => { | ||||
|             return site.read('tool_lp_data_for_plan_page', params, preSets) | ||||
|                     .then((response: AddonCompetencyDataForPlanPageResult): any => { | ||||
| 
 | ||||
|                 if (response.plan) { | ||||
|                     return response; | ||||
|                 } | ||||
| @ -207,9 +214,11 @@ export class AddonCompetencyProvider { | ||||
|      * @param planId ID of the plan. | ||||
|      * @param competencyId ID of the competency. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise to be resolved when the plans are retrieved. | ||||
|      * @return Promise to be resolved when the competency is retrieved. | ||||
|      */ | ||||
|     getCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise<any> { | ||||
|     getCompetencyInPlan(planId: number, competencyId: number, siteId?: string) | ||||
|             : Promise<AddonCompetencyUserCompetencySummaryInPlan> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
|             this.logger.debug('Get competency ' + competencyId + ' in plan ' + planId); | ||||
| @ -223,7 +232,9 @@ export class AddonCompetencyProvider { | ||||
|                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets).then((response) => { | ||||
|             return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets) | ||||
|                     .then((response: AddonCompetencyUserCompetencySummaryInPlan): any => { | ||||
| 
 | ||||
|                 if (response.usercompetencysummary) { | ||||
|                     return response; | ||||
|                 } | ||||
| @ -241,10 +252,10 @@ export class AddonCompetencyProvider { | ||||
|      * @param userId ID of the user. If not defined, current user. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). | ||||
|      * @return Promise to be resolved when the plans are retrieved. | ||||
|      * @return Promise to be resolved when the competency is retrieved. | ||||
|      */ | ||||
|     getCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean) | ||||
|             : Promise<any> { | ||||
|             : Promise<AddonCompetencyUserCompetencySummaryInCourse> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| @ -266,7 +277,9 @@ export class AddonCompetencyProvider { | ||||
|                 preSets.emergencyCache = false; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets).then((response) => { | ||||
|             return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets) | ||||
|                     .then((response: AddonCompetencyUserCompetencySummaryInCourse): any => { | ||||
| 
 | ||||
|                 if (response.usercompetencysummary) { | ||||
|                     return response; | ||||
|                 } | ||||
| @ -283,9 +296,11 @@ export class AddonCompetencyProvider { | ||||
|      * @param userId ID of the user. If not defined, current user. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). | ||||
|      * @return Promise to be resolved when the plans are retrieved. | ||||
|      * @return Promise to be resolved when the competency summary is retrieved. | ||||
|      */ | ||||
|     getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any> { | ||||
|     getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean) | ||||
|             : Promise<AddonCompetencySummary> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| 
 | ||||
| @ -305,7 +320,9 @@ export class AddonCompetencyProvider { | ||||
|                 preSets.emergencyCache = false; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('tool_lp_data_for_user_competency_summary', params, preSets).then((response) => { | ||||
|             return site.read('tool_lp_data_for_user_competency_summary', params, preSets) | ||||
|                     .then((response: AddonCompetencyUserCompetencySummary): any => { | ||||
| 
 | ||||
|                 if (response.competency) { | ||||
|                     return response.competency; | ||||
|                 } | ||||
| @ -324,7 +341,9 @@ export class AddonCompetencyProvider { | ||||
|      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). | ||||
|      * @return Promise to be resolved when the course competencies are retrieved. | ||||
|      */ | ||||
|     getCourseCompetencies(courseId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any> { | ||||
|     getCourseCompetencies(courseId: number, userId?: number, siteId?: string, ignoreCache?: boolean) | ||||
|             : Promise<AddonCompetencyDataForCourseCompetenciesPageResult> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
|             this.logger.debug('Get course competencies for course ' + courseId); | ||||
| @ -342,7 +361,9 @@ export class AddonCompetencyProvider { | ||||
|                 preSets.emergencyCache = false; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('tool_lp_data_for_course_competencies_page', params, preSets).then((response) => { | ||||
|             return site.read('tool_lp_data_for_course_competencies_page', params, preSets) | ||||
|                     .then((response: AddonCompetencyDataForCourseCompetenciesPageResult): any => { | ||||
| 
 | ||||
|                 if (response.competencies) { | ||||
|                     return response; | ||||
|                 } | ||||
| @ -356,11 +377,13 @@ export class AddonCompetencyProvider { | ||||
|                 return response; | ||||
|             } | ||||
| 
 | ||||
|             const promises = response.competencies.map((competency) => | ||||
|             let promises: Promise<AddonCompetencyUserCompetencySummaryInCourse>[]; | ||||
| 
 | ||||
|             promises = response.competencies.map((competency) => | ||||
|                 this.getCompetencyInCourse(courseId, competency.competency.id, userId, siteId) | ||||
|             ); | ||||
| 
 | ||||
|             return Promise.all(promises).then((responses: any[]) => { | ||||
|             return Promise.all(promises).then((responses: AddonCompetencyUserCompetencySummaryInCourse[]) => { | ||||
|                 responses.forEach((resp, index) => { | ||||
|                     response.competencies[index].usercompetencycourse = resp.usercompetencysummary.usercompetencycourse; | ||||
|                 }); | ||||
| @ -486,7 +509,7 @@ export class AddonCompetencyProvider { | ||||
|      * @return Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, name?: string, userId?: number, | ||||
|             siteId?: string): Promise<any> { | ||||
|             siteId?: string): Promise<void> { | ||||
|         if (planId && competencyId) { | ||||
| 
 | ||||
|             return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| @ -509,7 +532,11 @@ export class AddonCompetencyProvider { | ||||
|                     userid: userId | ||||
|                 }, siteId); | ||||
| 
 | ||||
|                 return site.write(wsName, params, preSets); | ||||
|                 return site.write(wsName, params, preSets).then((success: boolean) => { | ||||
|                     if (!success) { | ||||
|                         return Promise.reject(null); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
| @ -527,7 +554,7 @@ export class AddonCompetencyProvider { | ||||
|      * @return Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logCompetencyInCourseView(courseId: number, competencyId: number, name?: string, userId?: number, siteId?: string) | ||||
|             : Promise<any> { | ||||
|             : Promise<void> { | ||||
| 
 | ||||
|         if (courseId && competencyId) { | ||||
|             return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| @ -548,7 +575,11 @@ export class AddonCompetencyProvider { | ||||
|                     userid: userId | ||||
|                 }, siteId); | ||||
| 
 | ||||
|                 return site.write(wsName, params, preSets); | ||||
|                 return site.write(wsName, params, preSets).then((success: boolean) => { | ||||
|                     if (!success) { | ||||
|                         return Promise.reject(null); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
| @ -563,7 +594,7 @@ export class AddonCompetencyProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise<any> { | ||||
|     logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise<void> { | ||||
|         if (competencyId) { | ||||
|             return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|                 const params = { | ||||
| @ -576,10 +607,401 @@ export class AddonCompetencyProvider { | ||||
| 
 | ||||
|                 this.pushNotificationsProvider.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId); | ||||
| 
 | ||||
|                 return site.write('core_competency_competency_viewed', params, preSets); | ||||
|                 return site.write(wsName, params, preSets).then((success: boolean) => { | ||||
|                     if (!success) { | ||||
|                         return Promise.reject(null); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         return Promise.reject(null); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's plan_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyPlan = { | ||||
|     name: string; // Name.
 | ||||
|     description: string; // Description.
 | ||||
|     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     userid: number; // Userid.
 | ||||
|     templateid: number; // Templateid.
 | ||||
|     origtemplateid: number; // Origtemplateid.
 | ||||
|     status: number; // Status.
 | ||||
|     duedate: number; // Duedate.
 | ||||
|     reviewerid: number; // Reviewerid.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
|     statusname: string; // Statusname.
 | ||||
|     isbasedontemplate: boolean; // Isbasedontemplate.
 | ||||
|     canmanage: boolean; // Canmanage.
 | ||||
|     canrequestreview: boolean; // Canrequestreview.
 | ||||
|     canreview: boolean; // Canreview.
 | ||||
|     canbeedited: boolean; // Canbeedited.
 | ||||
|     isactive: boolean; // Isactive.
 | ||||
|     isdraft: boolean; // Isdraft.
 | ||||
|     iscompleted: boolean; // Iscompleted.
 | ||||
|     isinreview: boolean; // Isinreview.
 | ||||
|     iswaitingforreview: boolean; // Iswaitingforreview.
 | ||||
|     isreopenallowed: boolean; // Isreopenallowed.
 | ||||
|     iscompleteallowed: boolean; // Iscompleteallowed.
 | ||||
|     isunlinkallowed: boolean; // Isunlinkallowed.
 | ||||
|     isrequestreviewallowed: boolean; // Isrequestreviewallowed.
 | ||||
|     iscancelreviewrequestallowed: boolean; // Iscancelreviewrequestallowed.
 | ||||
|     isstartreviewallowed: boolean; // Isstartreviewallowed.
 | ||||
|     isstopreviewallowed: boolean; // Isstopreviewallowed.
 | ||||
|     isapproveallowed: boolean; // Isapproveallowed.
 | ||||
|     isunapproveallowed: boolean; // Isunapproveallowed.
 | ||||
|     duedateformatted: string; // Duedateformatted.
 | ||||
|     commentarea: CoreCommentsArea; | ||||
|     reviewer?: CoreUserSummary; | ||||
|     template?: AddonCompetencyTemplate; | ||||
|     url: string; // Url.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's template_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyTemplate = { | ||||
|     shortname: string; // Shortname.
 | ||||
|     description: string; // Description.
 | ||||
|     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     duedate: number; // Duedate.
 | ||||
|     visible: boolean; // Visible.
 | ||||
|     contextid: number; // Contextid.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
|     duedateformatted: string; // Duedateformatted.
 | ||||
|     cohortscount: number; // Cohortscount.
 | ||||
|     planscount: number; // Planscount.
 | ||||
|     canmanage: boolean; // Canmanage.
 | ||||
|     canread: boolean; // Canread.
 | ||||
|     contextname: string; // Contextname.
 | ||||
|     contextnamenoprefix: string; // Contextnamenoprefix.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's competency_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyCompetency = { | ||||
|     shortname: string; // Shortname.
 | ||||
|     idnumber: string; // Idnumber.
 | ||||
|     description: string; // Description.
 | ||||
|     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     sortorder: number; // Sortorder.
 | ||||
|     parentid: number; // Parentid.
 | ||||
|     path: string; // Path.
 | ||||
|     ruleoutcome: number; // Ruleoutcome.
 | ||||
|     ruletype: string; // Ruletype.
 | ||||
|     ruleconfig: string; // Ruleconfig.
 | ||||
|     scaleid: number; // Scaleid.
 | ||||
|     scaleconfiguration: string; // Scaleconfiguration.
 | ||||
|     competencyframeworkid: number; // Competencyframeworkid.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's competency_path_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyPath = { | ||||
|     ancestors: AddonCompetencyPathNode[]; // Ancestors.
 | ||||
|     framework: AddonCompetencyPathNode; | ||||
|     pluginbaseurl: string; // Pluginbaseurl.
 | ||||
|     pagecontextid: number; // Pagecontextid.
 | ||||
|     showlinks: boolean; // Showlinks.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's path_node_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyPathNode = { | ||||
|     id: number; // Id.
 | ||||
|     name: string; // Name.
 | ||||
|     first: boolean; // First.
 | ||||
|     last: boolean; // Last.
 | ||||
|     position: number; // Position.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's user_competency_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyUserCompetency = { | ||||
|     userid: number; // Userid.
 | ||||
|     competencyid: number; // Competencyid.
 | ||||
|     status: number; // Status.
 | ||||
|     reviewerid: number; // Reviewerid.
 | ||||
|     proficiency: boolean; // Proficiency.
 | ||||
|     grade: number; // Grade.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
|     canrequestreview: boolean; // Canrequestreview.
 | ||||
|     canreview: boolean; // Canreview.
 | ||||
|     gradename: string; // Gradename.
 | ||||
|     isrequestreviewallowed: boolean; // Isrequestreviewallowed.
 | ||||
|     iscancelreviewrequestallowed: boolean; // Iscancelreviewrequestallowed.
 | ||||
|     isstartreviewallowed: boolean; // Isstartreviewallowed.
 | ||||
|     isstopreviewallowed: boolean; // Isstopreviewallowed.
 | ||||
|     isstatusidle: boolean; // Isstatusidle.
 | ||||
|     isstatusinreview: boolean; // Isstatusinreview.
 | ||||
|     isstatuswaitingforreview: boolean; // Isstatuswaitingforreview.
 | ||||
|     proficiencyname: string; // Proficiencyname.
 | ||||
|     reviewer?: CoreUserSummary; | ||||
|     statusname: string; // Statusname.
 | ||||
|     url: string; // Url.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's user_competency_plan_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyUserCompetencyPlan = { | ||||
|     userid: number; // Userid.
 | ||||
|     competencyid: number; // Competencyid.
 | ||||
|     proficiency: boolean; // Proficiency.
 | ||||
|     grade: number; // Grade.
 | ||||
|     planid: number; // Planid.
 | ||||
|     sortorder: number; // Sortorder.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
|     gradename: string; // Gradename.
 | ||||
|     proficiencyname: string; // Proficiencyname.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's user_competency_summary_in_plan_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyUserCompetencySummaryInPlan = { | ||||
|     usercompetencysummary: AddonCompetencyUserCompetencySummary; | ||||
|     plan: AddonCompetencyPlan; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's user_competency_summary_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyUserCompetencySummary = { | ||||
|     showrelatedcompetencies: boolean; // Showrelatedcompetencies.
 | ||||
|     cangrade: boolean; // Cangrade.
 | ||||
|     competency: AddonCompetencySummary; | ||||
|     user: CoreUserSummary; | ||||
|     usercompetency?: AddonCompetencyUserCompetency; | ||||
|     usercompetencyplan?: AddonCompetencyUserCompetencyPlan; | ||||
|     usercompetencycourse?: AddonCompetencyUserCompetencyCourse; | ||||
|     evidence: AddonCompetencyEvidence[]; // Evidence.
 | ||||
|     commentarea?: CoreCommentsArea; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's competency_summary_exporter. | ||||
|  */ | ||||
| export type AddonCompetencySummary = { | ||||
|     linkedcourses: CoreCourseSummary; // Linkedcourses.
 | ||||
|     relatedcompetencies: AddonCompetencyCompetency[]; // Relatedcompetencies.
 | ||||
|     competency: AddonCompetencyCompetency; | ||||
|     framework: AddonCompetencyFramework; | ||||
|     hascourses: boolean; // Hascourses.
 | ||||
|     hasrelatedcompetencies: boolean; // Hasrelatedcompetencies.
 | ||||
|     scaleid: number; // Scaleid.
 | ||||
|     scaleconfiguration: string; // Scaleconfiguration.
 | ||||
|     taxonomyterm: string; // Taxonomyterm.
 | ||||
|     comppath: AddonCompetencyPath; | ||||
|     pluginbaseurl: string; // Pluginbaseurl.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's competency_framework_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyFramework = { | ||||
|     shortname: string; // Shortname.
 | ||||
|     idnumber: string; // Idnumber.
 | ||||
|     description: string; // Description.
 | ||||
|     descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     visible: boolean; // Visible.
 | ||||
|     scaleid: number; // Scaleid.
 | ||||
|     scaleconfiguration: string; // Scaleconfiguration.
 | ||||
|     contextid: number; // Contextid.
 | ||||
|     taxonomies: string; // Taxonomies.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
|     canmanage: boolean; // Canmanage.
 | ||||
|     competenciescount: number; // Competenciescount.
 | ||||
|     contextname: string; // Contextname.
 | ||||
|     contextnamenoprefix: string; // Contextnamenoprefix.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's user_competency_course_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyUserCompetencyCourse = { | ||||
|     userid: number; // Userid.
 | ||||
|     courseid: number; // Courseid.
 | ||||
|     competencyid: number; // Competencyid.
 | ||||
|     proficiency: boolean; // Proficiency.
 | ||||
|     grade: number; // Grade.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
|     gradename: string; // Gradename.
 | ||||
|     proficiencyname: string; // Proficiencyname.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's evidence_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyEvidence = { | ||||
|     usercompetencyid: number; // Usercompetencyid.
 | ||||
|     contextid: number; // Contextid.
 | ||||
|     action: number; // Action.
 | ||||
|     actionuserid: number; // Actionuserid.
 | ||||
|     descidentifier: string; // Descidentifier.
 | ||||
|     desccomponent: string; // Desccomponent.
 | ||||
|     desca: string; // Desca.
 | ||||
|     url: string; // Url.
 | ||||
|     grade: number; // Grade.
 | ||||
|     note: string; // Note.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
|     actionuser?: CoreUserSummary; | ||||
|     description: string; // Description.
 | ||||
|     gradename: string; // Gradename.
 | ||||
|     userdate: string; // Userdate.
 | ||||
|     candelete: boolean; // Candelete.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's user_competency_summary_in_course_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyUserCompetencySummaryInCourse = { | ||||
|     usercompetencysummary: AddonCompetencyUserCompetencySummary; | ||||
|     course: CoreCourseSummary; | ||||
|     coursemodules: CoreCourseModuleSummary[]; // Coursemodules.
 | ||||
|     plans: AddonCompetencyPlan[]; // Plans.
 | ||||
|     pluginbaseurl: string; // Pluginbaseurl.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's course_competency_settings_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyCourseCompetencySettings = { | ||||
|     courseid: number; // Courseid.
 | ||||
|     pushratingstouserplans: boolean; // Pushratingstouserplans.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's course_competency_statistics_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyCourseCompetencyStatistics = { | ||||
|     competencycount: number; // Competencycount.
 | ||||
|     proficientcompetencycount: number; // Proficientcompetencycount.
 | ||||
|     proficientcompetencypercentage: number; // Proficientcompetencypercentage.
 | ||||
|     proficientcompetencypercentageformatted: string; // Proficientcompetencypercentageformatted.
 | ||||
|     leastproficient: AddonCompetencyCompetency[]; // Leastproficient.
 | ||||
|     leastproficientcount: number; // Leastproficientcount.
 | ||||
|     canbegradedincourse: boolean; // Canbegradedincourse.
 | ||||
|     canmanagecoursecompetencies: boolean; // Canmanagecoursecompetencies.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by competency's course_competency_exporter. | ||||
|  */ | ||||
| export type AddonCompetencyCourseCompetency = { | ||||
|     courseid: number; // Courseid.
 | ||||
|     competencyid: number; // Competencyid.
 | ||||
|     sortorder: number; // Sortorder.
 | ||||
|     ruleoutcome: number; // Ruleoutcome.
 | ||||
|     id: number; // Id.
 | ||||
|     timecreated: number; // Timecreated.
 | ||||
|     timemodified: number; // Timemodified.
 | ||||
|     usermodified: number; // Usermodified.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS tool_lp_data_for_plans_page. | ||||
|  */ | ||||
| export type AddonCompetencyDataForPlansPageResult = { | ||||
|     userid: number; // The learning plan user id.
 | ||||
|     plans: AddonCompetencyPlan[]; | ||||
|     pluginbaseurl: string; // Url to the tool_lp plugin folder on this Moodle site.
 | ||||
|     navigation: string[]; | ||||
|     canreaduserevidence: boolean; // Can the current user view the user's evidence.
 | ||||
|     canmanageuserplans: boolean; // Can the current user manage the user's plans.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS tool_lp_data_for_plan_page. | ||||
|  */ | ||||
| export type AddonCompetencyDataForPlanPageResult = { | ||||
|     plan: AddonCompetencyPlan; | ||||
|     contextid: number; // Context ID.
 | ||||
|     pluginbaseurl: string; // Plugin base URL.
 | ||||
|     competencies: AddonCompetencyDataForPlanPageCompetency[]; | ||||
|     competencycount: number; // Count of competencies.
 | ||||
|     proficientcompetencycount: number; // Count of proficientcompetencies.
 | ||||
|     proficientcompetencypercentage: number; // Percentage of competencies proficient.
 | ||||
|     proficientcompetencypercentageformatted: string; // Displayable percentage.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Competency data returned by tool_lp_data_for_plan_page. | ||||
|  */ | ||||
| export type AddonCompetencyDataForPlanPageCompetency = { | ||||
|     competency: AddonCompetencyCompetency; | ||||
|     comppath: AddonCompetencyPath; | ||||
|     usercompetency?: AddonCompetencyUserCompetency; | ||||
|     usercompetencyplan?: AddonCompetencyUserCompetencyPlan; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS tool_lp_data_for_course_competencies_page. | ||||
|  */ | ||||
| export type AddonCompetencyDataForCourseCompetenciesPageResult = { | ||||
|     courseid: number; // The current course id.
 | ||||
|     pagecontextid: number; // The current page context ID.
 | ||||
|     gradableuserid?: number; // Current user id, if the user is a gradable user.
 | ||||
|     canmanagecompetencyframeworks: boolean; // User can manage competency frameworks.
 | ||||
|     canmanagecoursecompetencies: boolean; // User can manage linked course competencies.
 | ||||
|     canconfigurecoursecompetencies: boolean; // User can configure course competency settings.
 | ||||
|     cangradecompetencies: boolean; // User can grade competencies.
 | ||||
|     settings: AddonCompetencyCourseCompetencySettings; | ||||
|     statistics: AddonCompetencyCourseCompetencyStatistics; | ||||
|     competencies: AddonCompetencyDataForCourseCompetenciesPageCompetency[]; | ||||
|     manageurl: string; // Url to the manage competencies page.
 | ||||
|     pluginbaseurl: string; // Url to the course competencies page.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Competency data returned by tool_lp_data_for_course_competencies_page. | ||||
|  */ | ||||
| export type AddonCompetencyDataForCourseCompetenciesPageCompetency = { | ||||
|     competency: AddonCompetencyCompetency; | ||||
|     coursecompetency: AddonCompetencyCourseCompetency; | ||||
|     coursemodules: CoreCourseModuleSummary[]; | ||||
|     usercompetencycourse?: AddonCompetencyUserCompetencyCourse; | ||||
|     ruleoutcomeoptions: { | ||||
|         value: number; // The option value.
 | ||||
|         text: string; // The name of the option.
 | ||||
|         selected: boolean; // If this is the currently selected option.
 | ||||
|     }[]; | ||||
|     comppath: AddonCompetencyPath; | ||||
|     plans: AddonCompetencyPlan[]; | ||||
| }; | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
|         <ion-card *ngIf="completion && tracked"> | ||||
|             <ion-item text-wrap> | ||||
|                 <h2>{{ 'addon.coursecompletion.status' | translate }}</h2> | ||||
|                 <p>{{ completion.statusText | translate }}</p> | ||||
|                 <p>{{ statusText | translate }}</p> | ||||
|             </ion-item> | ||||
|             <ion-item text-wrap> | ||||
|                 <h2>{{ 'addon.coursecompletion.required' | translate }}</h2> | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| import { Component, Input, OnInit } from '@angular/core'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { AddonCourseCompletionProvider } from '../../providers/coursecompletion'; | ||||
| import { AddonCourseCompletionProvider, AddonCourseCompletionCourseCompletionStatus } from '../../providers/coursecompletion'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays the course completion report. | ||||
| @ -29,9 +29,10 @@ export class AddonCourseCompletionReportComponent implements OnInit { | ||||
|     @Input() userId: number; | ||||
| 
 | ||||
|     completionLoaded = false; | ||||
|     completion: any; | ||||
|     completion: AddonCourseCompletionCourseCompletionStatus; | ||||
|     showSelfComplete: boolean; | ||||
|     tracked = true; // Whether completion is tracked.
 | ||||
|     statusText: string; | ||||
| 
 | ||||
|     constructor( | ||||
|         private sitesProvider: CoreSitesProvider, | ||||
| @ -59,7 +60,7 @@ export class AddonCourseCompletionReportComponent implements OnInit { | ||||
|     protected fetchCompletion(): Promise<any> { | ||||
|         return this.courseCompletionProvider.getCompletion(this.courseId, this.userId).then((completion) => { | ||||
| 
 | ||||
|             completion.statusText = this.courseCompletionProvider.getCompletedStatusText(completion); | ||||
|             this.statusText = this.courseCompletionProvider.getCompletedStatusText(completion); | ||||
| 
 | ||||
|             this.completion = completion; | ||||
|             this.showSelfComplete = this.courseCompletionProvider.canMarkSelfCompleted(this.userId, completion); | ||||
|  | ||||
| @ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreWSExternalWarning } from '@providers/ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle course completion. | ||||
| @ -43,7 +44,7 @@ export class AddonCourseCompletionProvider { | ||||
|      * @param completion Course completion. | ||||
|      * @return True if user can mark course as self completed, false otherwise. | ||||
|      */ | ||||
|     canMarkSelfCompleted(userId: number, completion: any): boolean { | ||||
|     canMarkSelfCompleted(userId: number, completion: AddonCourseCompletionCourseCompletionStatus): boolean { | ||||
|         let selfCompletionActive = false, | ||||
|             alreadyMarked = false; | ||||
| 
 | ||||
| @ -68,7 +69,7 @@ export class AddonCourseCompletionProvider { | ||||
|      * @param completion Course completion. | ||||
|      * @return Language code of the text to show. | ||||
|      */ | ||||
|     getCompletedStatusText(completion: any): string { | ||||
|     getCompletedStatusText(completion: AddonCourseCompletionCourseCompletionStatus): string { | ||||
|         if (completion.completed) { | ||||
|             return 'addon.coursecompletion.completed'; | ||||
|         } else { | ||||
| @ -96,7 +97,9 @@ export class AddonCourseCompletionProvider { | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise to be resolved when the completion is retrieved. | ||||
|      */ | ||||
|     getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string): Promise<any> { | ||||
|     getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string) | ||||
|             : Promise<AddonCourseCompletionCourseCompletionStatus> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
|             preSets = preSets || {}; | ||||
| @ -112,7 +115,9 @@ export class AddonCourseCompletionProvider { | ||||
|             preSets.updateFrequency = preSets.updateFrequency || CoreSite.FREQUENCY_SOMETIMES; | ||||
|             preSets.cacheErrors = ['notenroled']; | ||||
| 
 | ||||
|             return site.read('core_completion_get_course_completion_status', data, preSets).then((data) => { | ||||
|             return site.read('core_completion_get_course_completion_status', data, preSets) | ||||
|                     .then((data: AddonCourseCompletionGetCourseCompletionStatusResult): any => { | ||||
| 
 | ||||
|                 if (data.completionstatus) { | ||||
|                     return data.completionstatus; | ||||
|                 } | ||||
| @ -243,17 +248,56 @@ export class AddonCourseCompletionProvider { | ||||
|      * Mark a course as self completed. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @return Resolved on success. | ||||
|      * @return Promise resolved on success. | ||||
|      */ | ||||
|     markCourseAsSelfCompleted(courseId: number): Promise<any> { | ||||
|     markCourseAsSelfCompleted(courseId: number): Promise<void> { | ||||
|         const params = { | ||||
|             courseid: courseId | ||||
|         }; | ||||
| 
 | ||||
|         return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params).then((response) => { | ||||
|         return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params) | ||||
|                 .then((response: AddonCourseCompletionMarkCourseSelfCompletedResult) => { | ||||
| 
 | ||||
|             if (!response.status) { | ||||
|                 return Promise.reject(null); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Completion status returned by core_completion_get_course_completion_status. | ||||
|  */ | ||||
| export type AddonCourseCompletionCourseCompletionStatus = { | ||||
|     completed: boolean; // True if the course is complete, false otherwise.
 | ||||
|     aggregation: number; // Aggregation method 1 means all, 2 means any.
 | ||||
|     completions: { | ||||
|         type: number; // Completion criteria type.
 | ||||
|         title: string; // Completion criteria Title.
 | ||||
|         status: string; // Completion status (Yes/No) a % or number.
 | ||||
|         complete: boolean; // Completion status (true/false).
 | ||||
|         timecompleted: number; // Timestamp for criteria completetion.
 | ||||
|         details: { | ||||
|             type: string; // Type description.
 | ||||
|             criteria: string; // Criteria description.
 | ||||
|             requirement: string; // Requirement description.
 | ||||
|             status: string; // Status description, can be anything.
 | ||||
|         }; // Details.
 | ||||
|     }[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_completion_get_course_completion_status. | ||||
|  */ | ||||
| export type AddonCourseCompletionGetCourseCompletionStatusResult = { | ||||
|     completionstatus: AddonCourseCompletionCourseCompletionStatus; // Course status.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_completion_mark_course_self_completed. | ||||
|  */ | ||||
| export type AddonCourseCompletionMarkCourseSelfCompletedResult = { | ||||
|     status: boolean; // Status, true if success.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
|  | ||||
| @ -20,7 +20,7 @@ import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { AddonFilesProvider } from '../../providers/files'; | ||||
| import { AddonFilesProvider, AddonFilesFile, AddonFilesGetUserPrivateFilesInfoResult } from '../../providers/files'; | ||||
| import { AddonFilesHelperProvider } from '../../providers/helper'; | ||||
| 
 | ||||
| /** | ||||
| @ -40,10 +40,10 @@ export class AddonFilesListPage implements OnDestroy { | ||||
|     root: string; // The root of the files loaded: 'my' or 'site'.
 | ||||
|     path: string; // The path of the directory being loaded. If empty path it means the root is being loaded.
 | ||||
|     userQuota: number; // The user quota (in bytes).
 | ||||
|     filesInfo: any; // Info about private files (size, number of files, etc.).
 | ||||
|     filesInfo: AddonFilesGetUserPrivateFilesInfoResult; // Info about private files (size, number of files, etc.).
 | ||||
|     spaceUsed: string; // Space used in a readable format.
 | ||||
|     userQuotaReadable: string; // User quota in a readable format.
 | ||||
|     files: any[]; // List of files.
 | ||||
|     files: AddonFilesFile[]; // List of files.
 | ||||
|     component: string; // Component to link the file downloads to.
 | ||||
|     filesLoaded: boolean; // Whether the files are loaded.
 | ||||
| 
 | ||||
| @ -147,7 +147,7 @@ export class AddonFilesListPage implements OnDestroy { | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected fetchFiles(): Promise<any> { | ||||
|         let promise; | ||||
|         let promise: Promise<AddonFilesFile[]>; | ||||
| 
 | ||||
|         if (!this.path) { | ||||
|             // The path is unknown, the user must be requesting a root.
 | ||||
|  | ||||
| @ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreWSExternalWarning } from '@providers/ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle my files and site files. | ||||
| @ -73,7 +74,7 @@ export class AddonFilesProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the files. | ||||
|      */ | ||||
|     getFiles(params: any, siteId?: string): Promise<any[]> { | ||||
|     getFiles(params: any, siteId?: string): Promise<AddonFilesFile[]> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const preSets = { | ||||
| @ -82,15 +83,15 @@ export class AddonFilesProvider { | ||||
|             }; | ||||
| 
 | ||||
|             return site.read('core_files_get_files', params, preSets); | ||||
|         }).then((result) => { | ||||
|             const entries = []; | ||||
|         }).then((result: AddonFilesGetFilesResult) => { | ||||
|             const entries: AddonFilesFile[] = []; | ||||
| 
 | ||||
|             if (result.files) { | ||||
|                 result.files.forEach((entry) => { | ||||
|                     if (entry.isdir) { | ||||
|                         // Create a "link" to load the folder.
 | ||||
|                         entry.link = { | ||||
|                             contextid: entry.contextid || '', | ||||
|                             contextid: entry.contextid || null, | ||||
|                             component: entry.component || '', | ||||
|                             filearea: entry.filearea || '', | ||||
|                             itemid: entry.itemid || 0, | ||||
| @ -135,7 +136,7 @@ export class AddonFilesProvider { | ||||
|      * | ||||
|      * @return Promise resolved with the files. | ||||
|      */ | ||||
|     getPrivateFiles(): Promise<any[]> { | ||||
|     getPrivateFiles(): Promise<AddonFilesFile[]> { | ||||
|         return this.getFiles(this.getPrivateFilesRootParams()); | ||||
|     } | ||||
| 
 | ||||
| @ -164,7 +165,7 @@ export class AddonFilesProvider { | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved with the info. | ||||
|      */ | ||||
|     getPrivateFilesInfo(userId?: number, siteId?: string): Promise<any> { | ||||
|     getPrivateFilesInfo(userId?: number, siteId?: string): Promise<AddonFilesGetUserPrivateFilesInfoResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| 
 | ||||
| @ -204,7 +205,7 @@ export class AddonFilesProvider { | ||||
|      * | ||||
|      * @return Promise resolved with the files. | ||||
|      */ | ||||
|     getSiteFiles(): Promise<any[]> { | ||||
|     getSiteFiles(): Promise<AddonFilesFile[]> { | ||||
|         return this.getFiles(this.getSiteFilesRootParams()); | ||||
|     } | ||||
| 
 | ||||
| @ -388,7 +389,7 @@ export class AddonFilesProvider { | ||||
|      * @param siteid ID of the site. If not defined, use current site. | ||||
|      * @return Promise resolved in success, rejected otherwise. | ||||
|      */ | ||||
|     moveFromDraftToPrivate(draftId: number, siteId?: string): Promise<any> { | ||||
|     moveFromDraftToPrivate(draftId: number, siteId?: string): Promise<null> { | ||||
|         const params = { | ||||
|                 draftid: draftId | ||||
|             }, | ||||
| @ -414,3 +415,63 @@ export class AddonFilesProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * File data returned by core_files_get_files. | ||||
|  */ | ||||
| export type AddonFilesFile = { | ||||
|     contextid: number; | ||||
|     component: string; | ||||
|     filearea: string; | ||||
|     itemid: number; | ||||
|     filepath: string; | ||||
|     filename: string; | ||||
|     isdir: boolean; | ||||
|     url: string; | ||||
|     timemodified: number; | ||||
|     timecreated?: number; // Time created.
 | ||||
|     filesize?: number; // File size.
 | ||||
|     author?: string; // File owner.
 | ||||
|     license?: string; // File license.
 | ||||
| } & AddonFilesFileCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_files_get_files. | ||||
|  */ | ||||
| export type AddonFilesGetFilesResult = { | ||||
|     parents: { | ||||
|         contextid: number; | ||||
|         component: string; | ||||
|         filearea: string; | ||||
|         itemid: number; | ||||
|         filepath: string; | ||||
|         filename: string; | ||||
|     }[]; | ||||
|     files: AddonFilesFile[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_user_get_private_files_info. | ||||
|  */ | ||||
| export type AddonFilesGetUserPrivateFilesInfoResult = { | ||||
|     filecount: number; // Number of files in the area.
 | ||||
|     foldercount: number; // Number of folders in the area.
 | ||||
|     filesize: number; // Total size of the files in the area.
 | ||||
|     filesizewithoutreferences: number; // Total size of the area excluding file references.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for AddonFilesFile. | ||||
|  */ | ||||
| export type AddonFilesFileCalculatedData = { | ||||
|     link?: { // Calculated in the app. A link to open the folder.
 | ||||
|         contextid?: number; // Folder's contextid.
 | ||||
|         component?: string; // Folder's component.
 | ||||
|         filearea?: string; // Folder's filearea.
 | ||||
|         itemid?: number; // Folder's itemid.
 | ||||
|         filepath?: string; // Folder's filepath.
 | ||||
|         filename?: string; // Folder's filename.
 | ||||
|     }; | ||||
|     imgPath?: string; // Path to file icon's image.
 | ||||
| }; | ||||
|  | ||||
| @ -16,7 +16,7 @@ import { Component, OnDestroy } from '@angular/core'; | ||||
| import { IonicPage } from 'ionic-angular'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | ||||
| import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifier'; | ||||
| import { AddonMessageOutputAirnotifierProvider, AddonMessageOutputAirnotifierDevice } from '../../providers/airnotifier'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of devices. | ||||
| @ -28,7 +28,7 @@ import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifi | ||||
| }) | ||||
| export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | ||||
| 
 | ||||
|     devices = []; | ||||
|     devices: AddonMessageOutputAirnotifierDeviceFormatted[] = []; | ||||
|     devicesLoaded = false; | ||||
| 
 | ||||
|     protected updateTimeout: any; | ||||
| @ -54,7 +54,7 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | ||||
|             const pushId = this.pushNotificationsProvider.getPushId(); | ||||
| 
 | ||||
|             // Convert enabled to boolean and search current device.
 | ||||
|             devices.forEach((device) => { | ||||
|             devices.forEach((device: AddonMessageOutputAirnotifierDeviceFormatted) => { | ||||
|                 device.enable = !!device.enable; | ||||
|                 device.current = pushId && pushId == device.pushid; | ||||
|             }); | ||||
| @ -110,8 +110,9 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | ||||
|      * @param device The device object. | ||||
|      * @param enable True to enable the device, false to disable it. | ||||
|      */ | ||||
|     enableDevice(device: any, enable: boolean): void { | ||||
|     enableDevice(device: AddonMessageOutputAirnotifierDeviceFormatted, enable: boolean): void { | ||||
|         device.updating = true; | ||||
| 
 | ||||
|         this.airnotifierProivder.enableDevice(device.id, enable).then(() => { | ||||
|             // Update the list of devices since it was modified.
 | ||||
|             this.updateDevicesAfterDelay(); | ||||
| @ -135,3 +136,11 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * User device with some calculated data. | ||||
|  */ | ||||
| type AddonMessageOutputAirnotifierDeviceFormatted = AddonMessageOutputAirnotifierDevice & { | ||||
|     current?: boolean; // Calculated in the app. Whether it's the current device.
 | ||||
|     updating?: boolean; // Calculated in the app. Whether the device enable is being updated right now.
 | ||||
| }; | ||||
|  | ||||
| @ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreConfigConstants } from '../../../../configconstants'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreWSExternalWarning } from '@providers/ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle Airnotifier message output. | ||||
| @ -39,14 +40,16 @@ export class AddonMessageOutputAirnotifierProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if success. | ||||
|      */ | ||||
|     enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise<any> { | ||||
|     enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise<void> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const data = { | ||||
|                 deviceid: deviceId, | ||||
|                 enable: enable ? 1 : 0 | ||||
|             }; | ||||
| 
 | ||||
|             return site.write('message_airnotifier_enable_device', data).then((result) => { | ||||
|             return site.write('message_airnotifier_enable_device', data) | ||||
|                     .then((result: AddonMessageOutputAirnotifierEnableDeviceResult) => { | ||||
| 
 | ||||
|                 if (!result.success) { | ||||
|                     // Fail. Reject with warning message if any.
 | ||||
|                     if (result.warnings && result.warnings.length) { | ||||
| @ -74,7 +77,7 @@ export class AddonMessageOutputAirnotifierProvider { | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved with the devices. | ||||
|      */ | ||||
|     getUserDevices(siteId?: string): Promise<any> { | ||||
|     getUserDevices(siteId?: string): Promise<AddonMessageOutputAirnotifierDevice[]> { | ||||
|         this.logger.debug('Get user devices'); | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| @ -86,7 +89,8 @@ export class AddonMessageOutputAirnotifierProvider { | ||||
|                 updateFrequency: CoreSite.FREQUENCY_RARELY | ||||
|             }; | ||||
| 
 | ||||
|             return site.read('message_airnotifier_get_user_devices', data, preSets).then((data) => { | ||||
|             return site.read('message_airnotifier_get_user_devices', data, preSets) | ||||
|                     .then((data: AddonMessageOutputAirnotifierGetUserDevicesResult) => { | ||||
|                 return data.devices; | ||||
|             }); | ||||
|         }); | ||||
| @ -115,3 +119,36 @@ export class AddonMessageOutputAirnotifierProvider { | ||||
|                 this.sitesProvider.wsAvailableInCurrentSite('message_airnotifier_get_user_devices'); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Device data returned by WS message_airnotifier_get_user_devices. | ||||
|  */ | ||||
| export type AddonMessageOutputAirnotifierDevice = { | ||||
|     id: number; // Device id (in the message_airnotifier table).
 | ||||
|     appid: string; // The app id, something like com.moodle.moodlemobile.
 | ||||
|     name: string; // The device name, 'occam' or 'iPhone' etc.
 | ||||
|     model: string; // The device model 'Nexus4' or 'iPad1,1' etc.
 | ||||
|     platform: string; // The device platform 'iOS' or 'Android' etc.
 | ||||
|     version: string; // The device version '6.1.2' or '4.2.2' etc.
 | ||||
|     pushid: string; // The device PUSH token/key/identifier/registration id.
 | ||||
|     uuid: string; // The device UUID.
 | ||||
|     enable: number | boolean; // Whether the device is enabled or not.
 | ||||
|     timecreated: number; // Time created.
 | ||||
|     timemodified: number; // Time modified.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS message_airnotifier_enable_device. | ||||
|  */ | ||||
| export type AddonMessageOutputAirnotifierEnableDeviceResult = { | ||||
|     success: boolean; // True if success.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS message_airnotifier_get_user_devices. | ||||
|  */ | ||||
| export type AddonMessageOutputAirnotifierGetUserDevicesResult = { | ||||
|     devices: AddonMessageOutputAirnotifierDevice[]; // List of devices.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
|  | ||||
| @ -16,7 +16,7 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@ | ||||
| import { Content } from 'ionic-angular'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { AddonMessagesProvider } from '../../providers/messages'; | ||||
| import { AddonMessagesProvider, AddonMessagesConversationMember } from '../../providers/messages'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| 
 | ||||
| /** | ||||
| @ -33,7 +33,7 @@ export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestro | ||||
|     loaded = false; | ||||
|     canLoadMore = false; | ||||
|     loadMoreError = false; | ||||
|     contacts = []; | ||||
|     contacts: AddonMessagesConversationMember[] = []; | ||||
|     selectedUserId: number; | ||||
| 
 | ||||
|     protected memberInfoObserver; | ||||
|  | ||||
| @ -16,7 +16,7 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@ | ||||
| import { Content } from 'ionic-angular'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { AddonMessagesProvider } from '../../providers/messages'; | ||||
| import { AddonMessagesProvider, AddonMessagesConversationMember } from '../../providers/messages'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| 
 | ||||
| /** | ||||
| @ -33,7 +33,7 @@ export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy | ||||
|     loaded = false; | ||||
|     canLoadMore = false; | ||||
|     loadMoreError = false; | ||||
|     requests = []; | ||||
|     requests: AddonMessagesConversationMember[] = []; | ||||
|     selectedUserId: number; | ||||
| 
 | ||||
|     protected memberInfoObserver; | ||||
|  | ||||
| @ -16,7 +16,9 @@ import { Component } from '@angular/core'; | ||||
| import { NavParams } from 'ionic-angular'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { AddonMessagesProvider } from '../../providers/messages'; | ||||
| import { | ||||
|     AddonMessagesProvider, AddonMessagesGetContactsResult, AddonMessagesSearchContactsContact | ||||
| } from '../../providers/messages'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreAppProvider } from '@providers/app'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| @ -42,7 +44,10 @@ export class AddonMessagesContactsComponent { | ||||
|     searchType = 'search'; | ||||
|     loadingMessage = ''; | ||||
|     hasContacts = false; | ||||
|     contacts = { | ||||
|     contacts: AddonMessagesGetContactsFormatted = { | ||||
|         online: [], | ||||
|         offline: [], | ||||
|         strangers: [], | ||||
|         search: [] | ||||
|     }; | ||||
|     searchString = ''; | ||||
| @ -205,7 +210,7 @@ export class AddonMessagesContactsComponent { | ||||
|             this.searchString = query; | ||||
|             this.contactTypes = ['search']; | ||||
| 
 | ||||
|             this.contacts['search'] = this.sortUsers(result); | ||||
|             this.contacts.search = this.sortUsers(result); | ||||
|         }).catch((error) => { | ||||
|             this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true); | ||||
|         }); | ||||
| @ -234,3 +239,10 @@ export class AddonMessagesContactsComponent { | ||||
|         this.memberInfoObserver && this.memberInfoObserver.off(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Contacts with some calculated data. | ||||
|  */ | ||||
| export type AddonMessagesGetContactsFormatted = AddonMessagesGetContactsResult & { | ||||
|     search?: AddonMessagesSearchContactsContact[]; // Calculated in the app. Result of searching users.
 | ||||
| }; | ||||
|  | ||||
| @ -14,7 +14,9 @@ | ||||
| 
 | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { IonicPage, NavParams, ViewController } from 'ionic-angular'; | ||||
| import { AddonMessagesProvider } from '../../providers/messages'; | ||||
| import { | ||||
|     AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember | ||||
| } from '../../providers/messages'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| 
 | ||||
| /** | ||||
| @ -28,8 +30,8 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| export class AddonMessagesConversationInfoPage implements OnInit { | ||||
| 
 | ||||
|     loaded = false; | ||||
|     conversation: any; | ||||
|     members = []; | ||||
|     conversation: AddonMessagesConversationFormatted; | ||||
|     members: AddonMessagesConversationMember[] = []; | ||||
|     canLoadMore = false; | ||||
|     loadMoreError = false; | ||||
| 
 | ||||
|  | ||||
| @ -17,7 +17,10 @@ import { IonicPage, NavParams, NavController, Content, ModalController } from 'i | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { AddonMessagesProvider } from '../../providers/messages'; | ||||
| import { | ||||
|     AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember, AddonMessagesConversationMessage, | ||||
|     AddonMessagesGetMessagesMessage | ||||
| } from '../../providers/messages'; | ||||
| import { AddonMessagesOfflineProvider } from '../../providers/messages-offline'; | ||||
| import { AddonMessagesSyncProvider } from '../../providers/sync'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| @ -54,7 +57,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|     protected messagesBeingSent = 0; | ||||
|     protected pagesLoaded = 1; | ||||
|     protected lastMessage = {text: '', timecreated: 0}; | ||||
|     protected keepMessageMap = {}; | ||||
|     protected keepMessageMap: {[hash: string]: boolean} = {}; | ||||
|     protected syncObserver: any; | ||||
|     protected oldContentHeight = 0; | ||||
|     protected keyboardObserver: any; | ||||
| @ -64,7 +67,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|     protected showLoadingModal = false; // Whether to show a loading modal while fetching data.
 | ||||
| 
 | ||||
|     conversationId: number; // Conversation ID. Undefined if it's a new individual conversation.
 | ||||
|     conversation: any; // The conversation object (if it exists).
 | ||||
|     conversation: AddonMessagesConversationFormatted; // The conversation object (if it exists).
 | ||||
|     userId: number; // User ID you're talking to (only if group messaging not enabled or it's a new individual conversation).
 | ||||
|     currentUserId: number; | ||||
|     title: string; | ||||
| @ -74,18 +77,18 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|     showKeyboard = false; | ||||
|     canLoadMore = false; | ||||
|     loadMoreError = false; | ||||
|     messages = []; | ||||
|     messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[] = []; | ||||
|     showDelete = false; | ||||
|     canDelete = false; | ||||
|     groupMessagingEnabled: boolean; | ||||
|     isGroup = false; | ||||
|     members: any = {}; // Members that wrote a message, indexed by ID.
 | ||||
|     members: {[id: number]: AddonMessagesConversationMember} = {}; // Members that wrote a message, indexed by ID.
 | ||||
|     favouriteIcon = 'fa-star'; | ||||
|     favouriteIconSlash = false; | ||||
|     deleteIcon = 'trash'; | ||||
|     blockIcon = 'close-circle'; | ||||
|     addRemoveIcon = 'person'; | ||||
|     otherMember: any; // Other member information (individual conversations only).
 | ||||
|     otherMember: AddonMessagesConversationMember; // Other member information (individual conversations only).
 | ||||
|     footerType: 'message' | 'blocked' | 'requiresContact' | 'requestSent' | 'requestReceived' | 'unable'; | ||||
|     requestContactSent = false; | ||||
|     requestContactReceived = false; | ||||
| @ -139,7 +142,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @param message Message to be added. | ||||
|      * @param keep If set the keep flag or not. | ||||
|      */ | ||||
|     protected addMessage(message: any, keep: boolean = true): void { | ||||
|     protected addMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, | ||||
|             keep: boolean = true): void { | ||||
| 
 | ||||
|         /* Create a hash to identify the message. The text of online messages isn't reliable because it can have random data | ||||
|            like VideoJS ID. Try to use id and fallback to text for offline messages. */ | ||||
|         message.hash = Md5.hashAsciiStr(String(message.id || message.text || '')) + '#' + message.timecreated + '#' + | ||||
| @ -158,7 +163,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * | ||||
|      * @param hash Hash of the message to be removed. | ||||
|      */ | ||||
|     protected removeMessage(hash: any): void { | ||||
|     protected removeMessage(hash: string): void { | ||||
|         if (this.keepMessageMap[hash]) { | ||||
|             // Selected to keep it, clear the flag.
 | ||||
|             this.keepMessageMap[hash] = false; | ||||
| @ -261,10 +266,11 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|                     if (!this.title && this.messages.length) { | ||||
|                         // Didn't receive the fullname via argument. Try to get it from messages.
 | ||||
|                         // It's possible that name cannot be resolved when no messages were yet exchanged.
 | ||||
|                         if (this.messages[0].useridto != this.currentUserId) { | ||||
|                             this.title = this.messages[0].usertofullname || ''; | ||||
|                         const firstMessage = <AddonMessagesGetMessagesMessageFormatted> this.messages[0]; | ||||
|                         if (firstMessage.useridto != this.currentUserId) { | ||||
|                             this.title = firstMessage.usertofullname || ''; | ||||
|                         } else { | ||||
|                             this.title = this.messages[0].userfromfullname || ''; | ||||
|                             this.title = firstMessage.userfromfullname || ''; | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
| @ -302,7 +308,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected fetchMessages(): Promise<any> { | ||||
|     protected fetchMessages(): Promise<void> { | ||||
|         this.loadMoreError = false; | ||||
| 
 | ||||
|         if (this.messagesBeingSent > 0) { | ||||
| @ -341,7 +347,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|                     return this.getDiscussionMessages(this.pagesLoaded); | ||||
|                 }); | ||||
|             } | ||||
|         }).then((messages) => { | ||||
|         }).then((messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) => { | ||||
|             this.loadMessages(messages); | ||||
|         }).finally(() => { | ||||
|             this.fetching = false; | ||||
| @ -353,7 +359,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * | ||||
|      * @param messages Messages to load. | ||||
|      */ | ||||
|     protected loadMessages(messages: any[]): void { | ||||
|     protected loadMessages(messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) | ||||
|             : void { | ||||
| 
 | ||||
|         if (this.viewDestroyed) { | ||||
|             return; | ||||
|         } | ||||
| @ -382,7 +390,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|         this.messagesProvider.sortMessages(this.messages); | ||||
| 
 | ||||
|         // Calculate which messages need to display the date or user data.
 | ||||
|         this.messages.forEach((message, index): any => { | ||||
|         this.messages.forEach((message, index) => { | ||||
|             message.showDate = this.showDate(message, this.messages[index - 1]); | ||||
|             message.showUserData = this.showUserData(message, this.messages[index - 1]); | ||||
|             message.showTail = this.showTail(message, this.messages[index + 1]); | ||||
| @ -411,20 +419,22 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @return Promise resolved with a boolean: whether the conversation exists or not. | ||||
|      */ | ||||
|     protected getConversation(conversationId: number, userId: number): Promise<boolean> { | ||||
|         let promise, | ||||
|             fallbackConversation; | ||||
|         let promise: Promise<number>, | ||||
|             fallbackConversation: AddonMessagesConversationFormatted; | ||||
| 
 | ||||
|         // Try to get the conversationId if we don't have it.
 | ||||
|         if (conversationId) { | ||||
|             promise = Promise.resolve(conversationId); | ||||
|         } else { | ||||
|             let subPromise: Promise<AddonMessagesConversationFormatted>; | ||||
| 
 | ||||
|             if (userId == this.currentUserId && this.messagesProvider.isSelfConversationEnabled()) { | ||||
|                 promise = this.messagesProvider.getSelfConversation(); | ||||
|                 subPromise = this.messagesProvider.getSelfConversation(); | ||||
|             } else { | ||||
|                 promise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true); | ||||
|                 subPromise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true); | ||||
|             } | ||||
| 
 | ||||
|             promise = promise.then((conversation) => { | ||||
|             promise = subPromise.then((conversation) => { | ||||
|                 fallbackConversation = conversation; | ||||
| 
 | ||||
|                 return conversation.id; | ||||
| @ -437,14 +447,14 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|                 // Ignore errors.
 | ||||
|             }).then(() => { | ||||
|                 return this.messagesProvider.getConversation(conversationId, undefined, true); | ||||
|             }).catch((error) => { | ||||
|             }).catch((error): any => { | ||||
|                 // Get conversation failed, use the fallback one if we have it.
 | ||||
|                 if (fallbackConversation) { | ||||
|                     return fallbackConversation; | ||||
|                 } | ||||
| 
 | ||||
|                 return Promise.reject(error); | ||||
|             }).then((conversation) => { | ||||
|             }).then((conversation: AddonMessagesConversationFormatted) => { | ||||
|                 this.conversation = conversation; | ||||
| 
 | ||||
|                 if (conversation) { | ||||
| @ -495,7 +505,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @param offset Offset for message list. | ||||
|      * @return Promise resolved with the list of messages. | ||||
|      */ | ||||
|     protected getConversationMessages(pagesToLoad: number, offset: number = 0): Promise<any[]> { | ||||
|     protected getConversationMessages(pagesToLoad: number, offset: number = 0) | ||||
|             : Promise<AddonMessagesConversationMessageFormatted[]> { | ||||
| 
 | ||||
|         const excludePending = offset > 0; | ||||
| 
 | ||||
|         return this.messagesProvider.getConversationMessages(this.conversationId, excludePending, offset).then((result) => { | ||||
| @ -535,7 +547,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, | ||||
|             lfSentUnread: number = 0, lfSentRead: number = 0): Promise<any> { | ||||
|             lfSentUnread: number = 0, lfSentRead: number = 0): Promise<AddonMessagesGetMessagesMessageFormatted[]> { | ||||
| 
 | ||||
|         // Only get offline messages if we're loading the first "page".
 | ||||
|         const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0; | ||||
| @ -547,7 +559,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|             pagesToLoad--; | ||||
|             if (pagesToLoad > 0 && result.canLoadMore) { | ||||
|                 // More pages to load. Calculate new limit froms.
 | ||||
|                 result.messages.forEach((message) => { | ||||
|                 result.messages.forEach((message: AddonMessagesGetMessagesMessageFormatted) => { | ||||
|                     if (!message.pending) { | ||||
|                         if (message.useridfrom == this.userId) { | ||||
|                             if (message.read) { | ||||
| @ -598,7 +610,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|                 for (const x in this.messages) { | ||||
|                     const message = this.messages[x]; | ||||
|                     // If an unread message is found, mark all messages as read.
 | ||||
|                     if (message.useridfrom != this.currentUserId && message.read == 0) { | ||||
|                     if (message.useridfrom != this.currentUserId && | ||||
|                             (<AddonMessagesGetMessagesMessageFormatted> message).read == 0) { | ||||
|                         messageUnreadFound = true; | ||||
|                         break; | ||||
|                     } | ||||
| @ -616,7 +629,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|                     promise = this.messagesProvider.markAllMessagesRead(this.userId).then(() => { | ||||
|                         // Mark all messages as read.
 | ||||
|                         this.messages.forEach((message) => { | ||||
|                             message.read = 1; | ||||
|                             (<AddonMessagesGetMessagesMessageFormatted> message).read = 1; | ||||
|                         }); | ||||
|                     }); | ||||
|                 } | ||||
| @ -630,10 +643,10 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|             // Mark each message as read one by one.
 | ||||
|             this.messages.forEach((message) => { | ||||
|                 // If the message is unread, call this.messagesProvider.markMessageRead.
 | ||||
|                 if (message.useridfrom != this.currentUserId && message.read == 0) { | ||||
|                 if (message.useridfrom != this.currentUserId && (<AddonMessagesGetMessagesMessageFormatted> message).read == 0) { | ||||
|                     promises.push(this.messagesProvider.markMessageRead(message.id).then(() => { | ||||
|                         readChanged = true; | ||||
|                         message.read = 1; | ||||
|                         (<AddonMessagesGetMessagesMessageFormatted> message).read = 1; | ||||
|                     })); | ||||
|                 } | ||||
|             }); | ||||
| @ -703,7 +716,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|                     if (!message.pending && message.useridfrom != this.currentUserId) { | ||||
|                         found++; | ||||
|                         if (found == this.conversation.unreadcount) { | ||||
|                             this.unreadMessageFrom = parseInt(message.id, 10); | ||||
|                             this.unreadMessageFrom = Number(message.id); | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| @ -713,13 +726,13 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|             let previousMessageRead = false; | ||||
| 
 | ||||
|             for (const x in this.messages) { | ||||
|                 const message = this.messages[x]; | ||||
|                 const message = <AddonMessagesGetMessagesMessageFormatted> this.messages[x]; | ||||
|                 if (message.useridfrom != this.currentUserId) { | ||||
|                     const unreadFrom = message.read == 0 && previousMessageRead; | ||||
| 
 | ||||
|                     if (unreadFrom) { | ||||
|                         // Save where the label is placed.
 | ||||
|                         this.unreadMessageFrom = parseInt(message.id, 10); | ||||
|                         this.unreadMessageFrom = Number(message.id); | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
| @ -808,8 +821,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * | ||||
|      * @param message Message to be copied. | ||||
|      */ | ||||
|     copyMessage(message: any): void { | ||||
|         const text = this.textUtils.decodeHTMLEntities(message.smallmessage || message.text || ''); | ||||
|     copyMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): void { | ||||
|         const text = this.textUtils.decodeHTMLEntities( | ||||
|                 (<AddonMessagesGetMessagesMessageFormatted> message).smallmessage || message.text || ''); | ||||
|         this.utils.copyToClipboard(text); | ||||
|     } | ||||
| 
 | ||||
| @ -819,7 +833,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @param message Message object to delete. | ||||
|      * @param index Index where the message is to delete it from the view. | ||||
|      */ | ||||
|     deleteMessage(message: any, index: number): void { | ||||
|     deleteMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, index: number) | ||||
|             : void { | ||||
| 
 | ||||
|         const canDeleteAll = this.conversation && this.conversation.candeletemessagesforallusers, | ||||
|             langKey = message.pending || canDeleteAll || this.isSelf ? 'core.areyousure' : | ||||
|                     'addon.messages.deletemessageconfirmation', | ||||
| @ -860,7 +876,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     loadPrevious(infiniteComplete?: any): Promise<any> { | ||||
|     loadPrevious(infiniteComplete?: any): Promise<void> { | ||||
|         let infiniteHeight = this.infinite ? this.infinite.getHeight() : 0; | ||||
|         const scrollHeight = this.domUtils.getScrollHeight(this.content); | ||||
| 
 | ||||
| @ -962,7 +978,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @param text Message text. | ||||
|      */ | ||||
|     sendMessage(text: string): void { | ||||
|         let message; | ||||
|         let message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted; | ||||
| 
 | ||||
|         this.hideUnreadLabel(); | ||||
| 
 | ||||
| @ -970,6 +986,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|         this.scrollBottom = true; | ||||
| 
 | ||||
|         message = { | ||||
|             id: null, | ||||
|             pending: true, | ||||
|             sending: true, | ||||
|             useridfrom: this.currentUserId, | ||||
| @ -985,7 +1002,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|         // If there is an ongoing fetch, wait for it to finish.
 | ||||
|         // Otherwise, if a message is sent while fetching it could disappear until the next fetch.
 | ||||
|         this.waitForFetch().finally(() => { | ||||
|             let promise; | ||||
|             let promise: Promise<{sent: boolean, message: any}>; | ||||
| 
 | ||||
|             if (this.conversationId) { | ||||
|                 promise = this.messagesProvider.sendMessageToConversation(this.conversation, text); | ||||
| @ -1050,7 +1067,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @param prevMessage Previous message where to compare the date with. | ||||
|      * @return If date has changed and should be shown. | ||||
|      */ | ||||
|     showDate(message: any, prevMessage?: any): boolean { | ||||
|     showDate(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, | ||||
|             prevMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { | ||||
| 
 | ||||
|         if (!prevMessage) { | ||||
|             // First message, show it.
 | ||||
|             return true; | ||||
| @ -1068,7 +1087,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @param prevMessage Previous message. | ||||
|      * @return Whether user data should be shown. | ||||
|      */ | ||||
|     showUserData(message: any, prevMessage?: any): boolean { | ||||
|     showUserData(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, | ||||
|             prevMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { | ||||
| 
 | ||||
|         return this.isGroup && message.useridfrom != this.currentUserId && this.members[message.useridfrom] && | ||||
|             (!prevMessage || prevMessage.useridfrom != message.useridfrom || message.showDate); | ||||
|     } | ||||
| @ -1080,7 +1101,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|      * @param nextMessage Next message. | ||||
|      * @return Whether user data should be shown. | ||||
|      */ | ||||
|     showTail(message: any, nextMessage?: any): boolean { | ||||
|     showTail(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, | ||||
|             nextMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { | ||||
|         return !nextMessage || nextMessage.useridfrom != message.useridfrom || nextMessage.showDate; | ||||
|     } | ||||
| 
 | ||||
| @ -1422,3 +1444,26 @@ export class AddonMessagesDiscussionPage implements OnDestroy { | ||||
|         this.viewDestroyed = true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Conversation message with some calculated data. | ||||
|  */ | ||||
| type AddonMessagesConversationMessageFormatted = AddonMessagesConversationMessage & { | ||||
|     pending?: boolean; // Calculated in the app. Whether the message is pending to be sent.
 | ||||
|     sending?: boolean; // Calculated in the app. Whether the message is being sent right now.
 | ||||
|     hash?: string; // Calculated in the app. A hash to identify the message.
 | ||||
|     showDate?: boolean; // Calculated in the app. Whether to show the date before the message.
 | ||||
|     showUserData?: boolean; // Calculated in the app. Whether to show the user data in the message.
 | ||||
|     showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Message with some calculated data. | ||||
|  */ | ||||
| type AddonMessagesGetMessagesMessageFormatted = AddonMessagesGetMessagesMessage & { | ||||
|     sending?: boolean; // Calculated in the app. Whether the message is being sent right now.
 | ||||
|     hash?: string; // Calculated in the app. A hash to identify the message.
 | ||||
|     showDate?: boolean; // Calculated in the app. Whether to show the date before the message.
 | ||||
|     showUserData?: boolean; // Calculated in the app. Whether to show the user data in the message.
 | ||||
|     showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message.
 | ||||
| }; | ||||
|  | ||||
| @ -17,7 +17,9 @@ import { IonicPage, Platform, NavController, NavParams, Content } from 'ionic-an | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { AddonMessagesProvider } from '../../providers/messages'; | ||||
| import { | ||||
|     AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMessage | ||||
| } from '../../providers/messages'; | ||||
| import { AddonMessagesOfflineProvider } from '../../providers/messages-offline'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| @ -45,19 +47,19 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|     selectedConversationId: number; | ||||
|     selectedUserId: number; | ||||
|     contactRequestsCount = 0; | ||||
|     favourites: any = { | ||||
|     favourites: AddonMessagesGroupConversationOption = { | ||||
|         type: null, | ||||
|         favourites: true, | ||||
|         count: 0, | ||||
|         unread: 0 | ||||
|         unread: 0, | ||||
|     }; | ||||
|     group: any = { | ||||
|     group: AddonMessagesGroupConversationOption = { | ||||
|         type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP, | ||||
|         favourites: false, | ||||
|         count: 0, | ||||
|         unread: 0 | ||||
|     }; | ||||
|     individual: any = { | ||||
|     individual: AddonMessagesGroupConversationOption = { | ||||
|         type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, | ||||
|         favourites: false, | ||||
|         count: 0, | ||||
| @ -331,7 +333,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected fetchDataForExpandedOption(): Promise<any> { | ||||
|     protected fetchDataForExpandedOption(): Promise<void> { | ||||
|         const expandedOption = this.getExpandedOption(); | ||||
| 
 | ||||
|         if (expandedOption) { | ||||
| @ -349,12 +351,12 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * @param getCounts Whether to get counts data. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     fetchDataForOption(option: any, loadingMore?: boolean, getCounts?: boolean): Promise<void> { | ||||
|     fetchDataForOption(option: AddonMessagesGroupConversationOption, loadingMore?: boolean, getCounts?: boolean): Promise<void> { | ||||
|         option.loadMoreError = false; | ||||
| 
 | ||||
|         const limitFrom = loadingMore ? option.conversations.length : 0, | ||||
|             promises = []; | ||||
|         let data, | ||||
|         let data: {conversations: AddonMessagesConversationForList[], canLoadMore: boolean}, | ||||
|             offlineMessages; | ||||
| 
 | ||||
|         // Get the conversations and, if needed, the offline messages. Always try to get the latest data.
 | ||||
| @ -422,7 +424,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * @param option The option to search in. If not defined, search in all options. | ||||
|      * @return Conversation. | ||||
|      */ | ||||
|     protected findConversation(conversationId: number, userId?: number, option?: any): any { | ||||
|     protected findConversation(conversationId: number, userId?: number, option?: AddonMessagesGroupConversationOption) | ||||
|             : AddonMessagesConversationForList { | ||||
| 
 | ||||
|         if (conversationId) { | ||||
|             const conversations = option ? (option.conversations || []) : ((this.favourites.conversations || []) | ||||
|                     .concat(this.group.conversations || []).concat(this.individual.conversations || [])); | ||||
| @ -445,7 +449,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * | ||||
|      * @return Option currently expanded. | ||||
|      */ | ||||
|     protected getExpandedOption(): any { | ||||
|     protected getExpandedOption(): AddonMessagesGroupConversationOption { | ||||
|         if (this.favourites.expanded) { | ||||
|             return this.favourites; | ||||
|         } else if (this.group.expanded) { | ||||
| @ -495,9 +499,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * | ||||
|      * @param option The option to fetch data for. | ||||
|      * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. | ||||
|      * @return Resolved when done. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     loadMoreConversations(option: any, infiniteComplete?: any): Promise<any> { | ||||
|     loadMoreConversations(option: AddonMessagesGroupConversationOption, infiniteComplete?: any): Promise<void> { | ||||
|         return this.fetchDataForOption(option, true).catch((error) => { | ||||
|             this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); | ||||
|             option.loadMoreError = true; | ||||
| @ -513,7 +517,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * @param messages Offline messages. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected loadOfflineMessages(option: any, messages: any[]): Promise<any> { | ||||
|     protected loadOfflineMessages(option: AddonMessagesGroupConversationOption, messages: any[]): Promise<any> { | ||||
|         const promises = []; | ||||
| 
 | ||||
|         messages.forEach((message) => { | ||||
| @ -588,7 +592,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * @param conversation Conversation where to put the last message. | ||||
|      * @param message Offline message to add. | ||||
|      */ | ||||
|     protected addLastOfflineMessage(conversation: any, message: any): void { | ||||
|     protected addLastOfflineMessage(conversation: any, message: AddonMessagesConversationMessage): void { | ||||
|         conversation.lastmessage = message.text; | ||||
|         conversation.lastmessagedate = message.timecreated / 1000; | ||||
|         conversation.lastmessagepending = true; | ||||
| @ -601,7 +605,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * @param conversation Conversation to check. | ||||
|      * @return Option object. | ||||
|      */ | ||||
|     protected getConversationOption(conversation: any): any { | ||||
|     protected getConversationOption(conversation: AddonMessagesConversationForList): AddonMessagesGroupConversationOption { | ||||
|         if (conversation.isfavourite) { | ||||
|             return this.favourites; | ||||
|         } else if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP) { | ||||
| @ -618,7 +622,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * @param refreshUnreadCounts Whether to refresh unread counts. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise<any> { | ||||
|     refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise<void> { | ||||
|         // Don't invalidate conversations and so, they always try to get latest data.
 | ||||
|         const promises = [ | ||||
|             this.messagesProvider.invalidateContactRequestsCountCache(this.siteId) | ||||
| @ -638,7 +642,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * | ||||
|      * @param option The option to expand/collapse. | ||||
|      */ | ||||
|     toggle(option: any): void { | ||||
|     toggle(option: AddonMessagesGroupConversationOption): void { | ||||
|         if (option.expanded) { | ||||
|             // Already expanded, close it.
 | ||||
|             option.expanded = false; | ||||
| @ -658,7 +662,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * @param getCounts Whether to get counts data. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected expandOption(option: any, getCounts?: boolean): Promise<any> { | ||||
|     protected expandOption(option: AddonMessagesGroupConversationOption, getCounts?: boolean): Promise<void> { | ||||
|         // Collapse all and expand the right one.
 | ||||
|         this.favourites.expanded = false; | ||||
|         this.group.expanded = false; | ||||
| @ -715,3 +719,25 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|         this.memberInfoObserver && this.memberInfoObserver.off(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Conversation options. | ||||
|  */ | ||||
| export type AddonMessagesGroupConversationOption = { | ||||
|     type: number; // Option type.
 | ||||
|     favourites: boolean; // Whether it contains favourites conversations.
 | ||||
|     count: number; // Number of conversations.
 | ||||
|     unread?: number; // Number of unread conversations.
 | ||||
|     expanded?: boolean; // Whether the option is currently expanded.
 | ||||
|     loading?: boolean; // Whether the option is being loaded.
 | ||||
|     canLoadMore?: boolean; // Whether it can load more data.
 | ||||
|     loadMoreError?: boolean; // Whether there was an error loading more conversations.
 | ||||
|     conversations?: AddonMessagesConversationForList[]; // List of conversations.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Formatted conversation with some calculated data for the list. | ||||
|  */ | ||||
| export type AddonMessagesConversationForList = AddonMessagesConversationFormatted & { | ||||
|     lastmessagepending?: boolean; // Calculated in the app. Whether last message is pending to be sent.
 | ||||
| }; | ||||
|  | ||||
| @ -16,7 +16,7 @@ import { Component, OnDestroy, ViewChild } from '@angular/core'; | ||||
| import { IonicPage } from 'ionic-angular'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { AddonMessagesProvider } from '../../providers/messages'; | ||||
| import { AddonMessagesProvider, AddonMessagesConversationMember, AddonMessagesMessageAreaContact } from '../../providers/messages'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreAppProvider } from '@providers/app'; | ||||
| @ -38,21 +38,21 @@ export class AddonMessagesSearchPage implements OnDestroy { | ||||
|     contacts = { | ||||
|         type: 'contacts', | ||||
|         titleString: 'addon.messages.contacts', | ||||
|         results: [], | ||||
|         results: <AddonMessagesConversationMember[]> [], | ||||
|         canLoadMore: false, | ||||
|         loadingMore: false | ||||
|     }; | ||||
|     nonContacts = { | ||||
|         type: 'noncontacts', | ||||
|         titleString: 'addon.messages.noncontacts', | ||||
|         results: [], | ||||
|         results: <AddonMessagesConversationMember[]> [], | ||||
|         canLoadMore: false, | ||||
|         loadingMore: false | ||||
|     }; | ||||
|     messages = { | ||||
|         type: 'messages', | ||||
|         titleString: 'addon.messages.messages', | ||||
|         results: [], | ||||
|         results: <AddonMessagesMessageAreaContact[]> [], | ||||
|         canLoadMore: false, | ||||
|         loadingMore: false, | ||||
|         loadMoreError: false | ||||
| @ -116,9 +116,9 @@ export class AddonMessagesSearchPage implements OnDestroy { | ||||
|         this.displaySearching = !loadMore; | ||||
| 
 | ||||
|         const promises = []; | ||||
|         let newContacts = []; | ||||
|         let newNonContacts = []; | ||||
|         let newMessages = []; | ||||
|         let newContacts: AddonMessagesConversationMember[] = []; | ||||
|         let newNonContacts: AddonMessagesConversationMember[] = []; | ||||
|         let newMessages: AddonMessagesMessageAreaContact[] = []; | ||||
|         let canLoadMoreContacts = false; | ||||
|         let canLoadMoreNonContacts = false; | ||||
|         let canLoadMoreMessages = false; | ||||
|  | ||||
| @ -14,7 +14,10 @@ | ||||
| 
 | ||||
| import { Component, OnDestroy } from '@angular/core'; | ||||
| import { IonicPage } from 'ionic-angular'; | ||||
| import { AddonMessagesProvider } from '../../providers/messages'; | ||||
| import { | ||||
|     AddonMessagesProvider, AddonMessagesMessagePreferences, AddonMessagesMessagePreferencesNotification, | ||||
|     AddonMessagesMessagePreferencesNotificationProcessor | ||||
| } from '../../providers/messages'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { CoreAppProvider } from '@providers/app'; | ||||
| import { CoreConfigProvider } from '@providers/config'; | ||||
| @ -34,7 +37,7 @@ import { CoreConstants } from '@core/constants'; | ||||
| export class AddonMessagesSettingsPage implements OnDestroy { | ||||
|     protected updateTimeout: any; | ||||
| 
 | ||||
|     preferences: any; | ||||
|     preferences: AddonMessagesMessagePreferences; | ||||
|     preferencesLoaded: boolean; | ||||
|     contactablePrivacy: number | boolean; | ||||
|     advancedContactable = false; // Whether the site supports "advanced" contactable privacy.
 | ||||
| @ -78,9 +81,9 @@ export class AddonMessagesSettingsPage implements OnDestroy { | ||||
|     /** | ||||
|      * Fetches preference data. | ||||
|      * | ||||
|      * @return Resolved when done. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected fetchPreferences(): Promise<any> { | ||||
|     protected fetchPreferences(): Promise<void> { | ||||
|         return this.messagesProvider.getMessagePreferences().then((preferences) => { | ||||
|             if (this.groupMessagingEnabled) { | ||||
|                 // Simplify the preferences.
 | ||||
| @ -90,11 +93,12 @@ export class AddonMessagesSettingsPage implements OnDestroy { | ||||
|                         return notification.preferencekey == AddonMessagesProvider.NOTIFICATION_PREFERENCES_KEY; | ||||
|                     }); | ||||
| 
 | ||||
|                     for (const notification of component.notifications) { | ||||
|                         for (const processor of notification.processors) { | ||||
|                     component.notifications.forEach((notification) => { | ||||
|                         notification.processors.forEach( | ||||
|                                 (processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted) => { | ||||
|                             processor.checked = processor.loggedin.checked || processor.loggedoff.checked; | ||||
|                         } | ||||
|                     } | ||||
|                         }); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
| @ -168,14 +172,16 @@ export class AddonMessagesSettingsPage implements OnDestroy { | ||||
|      * @param state State name, ['loggedin', 'loggedoff']. | ||||
|      * @param processor Notification processor. | ||||
|      */ | ||||
|     changePreference(notification: any, state: string, processor: any): void { | ||||
|     changePreference(notification: AddonMessagesMessagePreferencesNotificationFormatted, state: string, | ||||
|             processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted): void { | ||||
| 
 | ||||
|         if (this.groupMessagingEnabled) { | ||||
|             // Update both states at the same time.
 | ||||
|             const valueArray = [], | ||||
|                 promises = []; | ||||
|             let value = 'none'; | ||||
| 
 | ||||
|             notification.processors.forEach((processor) => { | ||||
|             notification.processors.forEach((processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted) => { | ||||
|                 if (processor.checked) { | ||||
|                     valueArray.push(processor.name); | ||||
|                 } | ||||
| @ -268,3 +274,17 @@ export class AddonMessagesSettingsPage implements OnDestroy { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Message preferences notification with some caclulated data. | ||||
|  */ | ||||
| type AddonMessagesMessagePreferencesNotificationFormatted = AddonMessagesMessagePreferencesNotification & { | ||||
|     updating?: boolean | {[state: string]: boolean}; // Calculated in the app. Whether the notification is being updated.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Message preferences notification processor with some caclulated data. | ||||
|  */ | ||||
| type AddonMessagesMessagePreferencesNotificationProcessorFormatted = AddonMessagesMessagePreferencesNotificationProcessor & { | ||||
|     checked?: boolean; // Calculated in the app. Whether the processor is checked either for loggedin or loggedoff.
 | ||||
| }; | ||||
|  | ||||
| @ -248,7 +248,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr | ||||
|                     } | ||||
| 
 | ||||
|                     const currentUserId = site.getUserId(), | ||||
|                         message = conv.messages[0]; // Treat only the last message, is the one we're interested.
 | ||||
|                         message: any = conv.messages[0]; // Treat only the last message, is the one we're interested.
 | ||||
| 
 | ||||
|                     if (!message || message.useridfrom == currentUserId) { | ||||
|                         // No last message or not from current user. Return empty list.
 | ||||
|  | ||||
| @ -23,6 +23,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreWSExternalWarning } from '@providers/ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle messages. | ||||
| @ -89,9 +90,9 @@ export class AddonMessagesProvider { | ||||
|      * | ||||
|      * @param userId User ID of the person to block. | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Resolved when done. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     blockContact(userId: number, siteId?: string): Promise<any> { | ||||
|     blockContact(userId: number, siteId?: string): Promise<void> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             let promise; | ||||
|             if (site.wsAvailable('core_message_block_user')) { | ||||
| @ -313,7 +314,9 @@ export class AddonMessagesProvider { | ||||
|      * @param userId User ID viewing the conversation. | ||||
|      * @return Formatted conversation. | ||||
|      */ | ||||
|     protected formatConversation(conversation: any, userId: number): any { | ||||
|     protected formatConversation(conversation: AddonMessagesConversationFormatted, userId: number) | ||||
|             : AddonMessagesConversationFormatted { | ||||
| 
 | ||||
|         const numMessages = conversation.messages.length, | ||||
|             lastMessage = numMessages ? conversation.messages[numMessages - 1] : null; | ||||
| 
 | ||||
| @ -536,10 +539,10 @@ export class AddonMessagesProvider { | ||||
|      * Get all the contacts of the current user. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Resolved with the WS data. | ||||
|      * @return Promise resolved with the WS data. | ||||
|      * @deprecated since Moodle 3.6 | ||||
|      */ | ||||
|     getAllContacts(siteId?: string): Promise<any> { | ||||
|     getAllContacts(siteId?: string): Promise<AddonMessagesGetContactsResult> { | ||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
|         return this.getContacts(siteId).then((contacts) => { | ||||
| @ -562,9 +565,9 @@ export class AddonMessagesProvider { | ||||
|      * Get all the users blocked by the current user. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Resolved with the WS data. | ||||
|      * @return Promise resolved with the WS data. | ||||
|      */ | ||||
|     getBlockedContacts(siteId?: string): Promise<any> { | ||||
|     getBlockedContacts(siteId?: string): Promise<AddonMessagesGetBlockedUsersResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const userId = site.getUserId(), | ||||
|                 params = { | ||||
| @ -585,19 +588,24 @@ export class AddonMessagesProvider { | ||||
|      * This excludes the blocked users. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Resolved with the WS data. | ||||
|      * @return Promise resolved with the WS data. | ||||
|      * @deprecated since Moodle 3.6 | ||||
|      */ | ||||
|     getContacts(siteId?: string): Promise<any> { | ||||
|     getContacts(siteId?: string): Promise<AddonMessagesGetContactsResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const preSets = { | ||||
|                 cacheKey: this.getCacheKeyForContacts(), | ||||
|                 updateFrequency: CoreSite.FREQUENCY_OFTEN | ||||
|             }; | ||||
| 
 | ||||
|             return site.read('core_message_get_contacts', undefined, preSets).then((contacts) => { | ||||
|             return site.read('core_message_get_contacts', undefined, preSets).then((contacts: AddonMessagesGetContactsResult) => { | ||||
|                 // Filter contacts with negative ID, they are notifications.
 | ||||
|                 const validContacts = {}; | ||||
|                 const validContacts: AddonMessagesGetContactsResult = { | ||||
|                     online: [], | ||||
|                     offline: [], | ||||
|                     strangers: [] | ||||
|                 }; | ||||
| 
 | ||||
|                 for (const typeName in contacts) { | ||||
|                     if (!validContacts[typeName]) { | ||||
|                         validContacts[typeName] = []; | ||||
| @ -621,11 +629,11 @@ export class AddonMessagesProvider { | ||||
|      * @param limitFrom Position of the first contact to fetch. | ||||
|      * @param limitNum Number of contacts to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Resolved with the list of user contacts. | ||||
|      * @return Promise resolved with the list of user contacts. | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     getUserContacts(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS , siteId?: string): | ||||
|             Promise<{contacts: any[], canLoadMore: boolean}> { | ||||
|             Promise<{contacts: AddonMessagesConversationMember[], canLoadMore: boolean}> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params = { | ||||
| @ -638,7 +646,9 @@ export class AddonMessagesProvider { | ||||
|                 updateFrequency: CoreSite.FREQUENCY_OFTEN | ||||
|             }; | ||||
| 
 | ||||
|             return site.read('core_message_get_user_contacts', params, preSets).then((contacts) => { | ||||
|             return site.read('core_message_get_user_contacts', params, preSets) | ||||
|                     .then((contacts: AddonMessagesConversationMember[]) => { | ||||
| 
 | ||||
|                 if (!contacts || !contacts.length) { | ||||
|                     return { contacts: [], canLoadMore: false }; | ||||
|                 } | ||||
| @ -663,11 +673,11 @@ export class AddonMessagesProvider { | ||||
|      * @param limitFrom Position of the first contact request to fetch. | ||||
|      * @param limitNum Number of contact requests to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Resolved with the list of contact requests. | ||||
|      * @return Promise resolved with the list of contact requests. | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     getContactRequests(limitFrom: number = 0, limitNum: number =  AddonMessagesProvider.LIMIT_CONTACTS, siteId?: string): | ||||
|             Promise<{requests: any[], canLoadMore: boolean}> { | ||||
|             Promise<{requests: AddonMessagesConversationMember[], canLoadMore: boolean}> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const data = { | ||||
| @ -680,7 +690,9 @@ export class AddonMessagesProvider { | ||||
|                 updateFrequency: CoreSite.FREQUENCY_OFTEN | ||||
|             }; | ||||
| 
 | ||||
|             return site.read('core_message_get_contact_requests', data, preSets).then((requests) => { | ||||
|             return site.read('core_message_get_contact_requests', data, preSets) | ||||
|                     .then((requests: AddonMessagesConversationMember[]) => { | ||||
| 
 | ||||
|                 if (!requests || !requests.length) { | ||||
|                     return { requests: [], canLoadMore: false }; | ||||
|                 } | ||||
| @ -716,7 +728,7 @@ export class AddonMessagesProvider { | ||||
|                 typeExpected: 'number' | ||||
|             }; | ||||
| 
 | ||||
|             return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count) => { | ||||
|             return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count: number) => { | ||||
|                 // Notify the new count so all badges are updated.
 | ||||
|                 this.eventsProvider.trigger(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, { count }, site.id); | ||||
| 
 | ||||
| @ -745,7 +757,7 @@ export class AddonMessagesProvider { | ||||
|      */ | ||||
|     getConversation(conversationId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, | ||||
|             messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2, | ||||
|             newestFirst: boolean = true, siteId?: string, userId?: number): Promise<any> { | ||||
|             newestFirst: boolean = true, siteId?: string, userId?: number): Promise<AddonMessagesConversationFormatted> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| @ -765,7 +777,7 @@ export class AddonMessagesProvider { | ||||
|                     newestmessagesfirst: newestFirst ? 1 : 0 | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_get_conversation', params, preSets).then((conversation) => { | ||||
|             return site.read('core_message_get_conversation', params, preSets).then((conversation: AddonMessagesConversation) => { | ||||
|                 return this.formatConversation(conversation, userId); | ||||
|             }); | ||||
|         }); | ||||
| @ -792,7 +804,8 @@ export class AddonMessagesProvider { | ||||
|      */ | ||||
|     getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, | ||||
|             messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2, | ||||
|             newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean): Promise<any> { | ||||
|             newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean) | ||||
|             : Promise<AddonMessagesConversationFormatted> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| @ -813,7 +826,8 @@ export class AddonMessagesProvider { | ||||
|                     newestmessagesfirst: newestFirst ? 1 : 0 | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_get_conversation_between_users', params, preSets).then((conversation) => { | ||||
|             return site.read('core_message_get_conversation_between_users', params, preSets) | ||||
|                     .then((conversation: AddonMessagesConversation) => { | ||||
|                 return this.formatConversation(conversation, userId); | ||||
|             }); | ||||
|         }); | ||||
| @ -826,12 +840,11 @@ export class AddonMessagesProvider { | ||||
|      * @param limitFrom Offset for members list. | ||||
|      * @param limitTo Limit of members. | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @param userId User ID. If not defined, current user in the site. | ||||
|      * @return Promise resolved with the response. | ||||
|      * @param userId User ID. If not defined, current user in | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     getConversationMembers(conversationId: number, limitFrom: number = 0, limitTo?: number, includeContactRequests?: boolean, | ||||
|             siteId?: string, userId?: number): Promise<any> { | ||||
|             siteId?: string, userId?: number): Promise<{members: AddonMessagesConversationMember[], canLoadMore: boolean}> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| @ -853,18 +866,21 @@ export class AddonMessagesProvider { | ||||
|                     includeprivacyinfo: 1, | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_get_conversation_members', params, preSets).then((members) => { | ||||
|                 const result: any = {}; | ||||
|             return site.read('core_message_get_conversation_members', params, preSets) | ||||
|                     .then((members: AddonMessagesConversationMember[]) => { | ||||
| 
 | ||||
|                 if (limitTo < 1) { | ||||
|                     result.canLoadMore = false; | ||||
|                     result.members = members; | ||||
|                     return { | ||||
|                         canLoadMore: false, | ||||
|                         members: members | ||||
|                     }; | ||||
|                 } else { | ||||
|                     result.canLoadMore = members.length > limitTo; | ||||
|                     result.members = members.slice(0, limitTo); | ||||
|                     return { | ||||
|                         canLoadMore: members.length > limitTo, | ||||
|                         members: members.slice(0, limitTo) | ||||
|                     }; | ||||
|                 } | ||||
| 
 | ||||
|                 return result; | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| @ -884,7 +900,8 @@ export class AddonMessagesProvider { | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     getConversationMessages(conversationId: number, excludePending: boolean, limitFrom: number = 0, limitTo?: number, | ||||
|             newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number): Promise<any> { | ||||
|             newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number) | ||||
|             : Promise<AddonMessagesGetConversationMessagesResult> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| @ -913,7 +930,9 @@ export class AddonMessagesProvider { | ||||
|                 preSets['emergencyCache'] = false; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_message_get_conversation_messages', params, preSets).then((result) => { | ||||
|             return site.read('core_message_get_conversation_messages', params, preSets) | ||||
|                     .then((result: AddonMessagesGetConversationMessagesResult) => { | ||||
| 
 | ||||
|                 if (limitTo < 1) { | ||||
|                     result.canLoadMore = false; | ||||
|                     result.messages = result.messages; | ||||
| @ -975,7 +994,8 @@ export class AddonMessagesProvider { | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number, | ||||
|             forceCache?: boolean, ignoreCache?: boolean): Promise<{conversations: any[], canLoadMore: boolean}> { | ||||
|             forceCache?: boolean, ignoreCache?: boolean) | ||||
|             : Promise<{conversations: AddonMessagesConversationFormatted[], canLoadMore: boolean}> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| @ -1017,7 +1037,7 @@ export class AddonMessagesProvider { | ||||
|                 } | ||||
| 
 | ||||
|                 return Promise.reject(error); | ||||
|             }).then((response) => { | ||||
|             }).then((response: AddonMessagesGetConversationsResult) => { | ||||
|                 // Format the conversations, adding some calculated fields.
 | ||||
|                 const conversations = response.conversations.slice(0, this.LIMIT_MESSAGES).map((conversation) => { | ||||
|                         return this.formatConversation(conversation, userId); | ||||
| @ -1053,7 +1073,9 @@ export class AddonMessagesProvider { | ||||
|                 cacheKey: this.getCacheKeyForConversationCounts() | ||||
|             }; | ||||
| 
 | ||||
|             return site.read('core_message_get_conversation_counts', {}, preSets).then((result) => { | ||||
|             return site.read('core_message_get_conversation_counts', {}, preSets) | ||||
|                     .then((result: AddonMessagesGetConversationCountsResult) => { | ||||
| 
 | ||||
|                 const counts = { | ||||
|                     favourites: result.favourites, | ||||
|                     individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], | ||||
| @ -1080,10 +1102,14 @@ export class AddonMessagesProvider { | ||||
|      * @return Promise resolved with messages and a boolean telling if can load more messages. | ||||
|      */ | ||||
|     getDiscussion(userId: number, excludePending: boolean, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, | ||||
|             lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string): Promise<any> { | ||||
|             lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string) | ||||
|             : Promise<{messages: AddonMessagesGetMessagesMessage[], canLoadMore: boolean}> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const result = {}, | ||||
|             const result = { | ||||
|                     messages: <AddonMessagesGetMessagesMessage[]> [], | ||||
|                     canLoadMore: false | ||||
|                 }, | ||||
|                 preSets = { | ||||
|                     cacheKey: this.getCacheKeyForDiscussion(userId) | ||||
|                 }, | ||||
| @ -1107,7 +1133,7 @@ export class AddonMessagesProvider { | ||||
|             // Get message received by current user.
 | ||||
|             return this.getRecentMessages(params, preSets, lfReceivedUnread, lfReceivedRead, toDisplay, site.getId()) | ||||
|                     .then((response) => { | ||||
|                 result['messages'] = response; | ||||
|                 result.messages = response; | ||||
|                 params.useridto = userId; | ||||
|                 params.useridfrom = site.getUserId(); | ||||
|                 hasReceived = response.length > 0; | ||||
| @ -1115,16 +1141,16 @@ export class AddonMessagesProvider { | ||||
|                 // Get message sent by current user.
 | ||||
|                 return this.getRecentMessages(params, preSets, lfSentUnread, lfSentRead, toDisplay, siteId); | ||||
|             }).then((response) => { | ||||
|                 result['messages'] = result['messages'].concat(response); | ||||
|                 result.messages = result.messages.concat(response); | ||||
|                 hasSent = response.length > 0; | ||||
| 
 | ||||
|                 if (result['messages'].length > this.LIMIT_MESSAGES) { | ||||
|                 if (result.messages.length > this.LIMIT_MESSAGES) { | ||||
|                     // Sort messages and get the more recent ones.
 | ||||
|                     result['canLoadMore'] = true; | ||||
|                     result['messages'] = this.sortMessages(result['messages']); | ||||
|                     result['messages'] = result['messages'].slice(-this.LIMIT_MESSAGES); | ||||
|                     result.canLoadMore = true; | ||||
|                     result.messages = this.sortMessages(result['messages']); | ||||
|                     result.messages = result.messages.slice(-this.LIMIT_MESSAGES); | ||||
|                 } else { | ||||
|                     result['canLoadMore'] = result['messages'].length == this.LIMIT_MESSAGES && (!hasReceived || !hasSent); | ||||
|                     result.canLoadMore = result.messages.length == this.LIMIT_MESSAGES && (!hasReceived || !hasSent); | ||||
|                 } | ||||
| 
 | ||||
|                 if (excludePending) { | ||||
| @ -1140,7 +1166,7 @@ export class AddonMessagesProvider { | ||||
|                         message.text = message.smallmessage; | ||||
|                     }); | ||||
| 
 | ||||
|                     result['messages'] = result['messages'].concat(offlineMessages); | ||||
|                     result.messages = result.messages.concat(offlineMessages); | ||||
| 
 | ||||
|                     return result; | ||||
|                 }); | ||||
| @ -1153,11 +1179,11 @@ export class AddonMessagesProvider { | ||||
|      * If the site is 3.6 or higher, please use getConversations. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Resolved with an object where the keys are the user ID of the other user. | ||||
|      * @return Promise resolved with an object where the keys are the user ID of the other user. | ||||
|      */ | ||||
|     getDiscussions(siteId?: string): Promise<any> { | ||||
|     getDiscussions(siteId?: string): Promise<{[userId: number]: AddonMessagesDiscussion}> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const discussions = {}, | ||||
|             const discussions: {[userId: number]: AddonMessagesDiscussion} = {}, | ||||
|                 currentUserId = site.getUserId(), | ||||
|                 params = { | ||||
|                     useridto: currentUserId, | ||||
| @ -1171,7 +1197,7 @@ export class AddonMessagesProvider { | ||||
|             /** | ||||
|              * Convenience function to treat a recent message, adding it to discussions list if needed. | ||||
|              */ | ||||
|             const treatRecentMessage = (message: any, userId: number, userFullname: string): void => { | ||||
|             const treatRecentMessage = (message: AddonMessagesGetMessagesMessage, userId: number, userFullname: string): void => { | ||||
|                 if (typeof discussions[userId] === 'undefined') { | ||||
|                     discussions[userId] = { | ||||
|                         fullname: userFullname, | ||||
| @ -1272,7 +1298,7 @@ export class AddonMessagesProvider { | ||||
|      * @return Promise resolved with the member info. | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise<any> { | ||||
|     getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise<AddonMessagesConversationMember> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| 
 | ||||
| @ -1287,7 +1313,9 @@ export class AddonMessagesProvider { | ||||
|                     includeprivacyinfo: 1, | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_get_member_info', params, preSets).then((members) => { | ||||
|             return site.read('core_message_get_member_info', params, preSets) | ||||
|                     .then((members: AddonMessagesConversationMember[]): any => { | ||||
| 
 | ||||
|                 if (!members || members.length < 1) { | ||||
|                     // Should never happen.
 | ||||
|                     return Promise.reject(null); | ||||
| @ -1313,7 +1341,7 @@ export class AddonMessagesProvider { | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved with the message preferences. | ||||
|      */ | ||||
|     getMessagePreferences(siteId?: string): Promise<any> { | ||||
|     getMessagePreferences(siteId?: string): Promise<AddonMessagesMessagePreferences> { | ||||
|         this.logger.debug('Get message preferences'); | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| @ -1322,7 +1350,9 @@ export class AddonMessagesProvider { | ||||
|                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_get_user_message_preferences', {}, preSets).then((data) => { | ||||
|             return site.read('core_message_get_user_message_preferences', {}, preSets) | ||||
|                     .then((data: AddonMessagesGetUserMessagePreferencesResult): any => { | ||||
| 
 | ||||
|                 if (data.preferences) { | ||||
|                     data.preferences.blocknoncontacts = data.blocknoncontacts; | ||||
| 
 | ||||
| @ -1341,15 +1371,18 @@ export class AddonMessagesProvider { | ||||
|      * @param preSets Set of presets for the WS. | ||||
|      * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved with the data. | ||||
|      */ | ||||
|     protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string): Promise<any> { | ||||
|     protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string) | ||||
|             : Promise<AddonMessagesGetMessagesResult> { | ||||
| 
 | ||||
|         params['type'] = 'conversations'; | ||||
|         params['newestfirst'] = 1; | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const userId = site.getUserId(); | ||||
| 
 | ||||
|             return site.read('core_message_get_messages', params, preSets).then((response) => { | ||||
|             return site.read('core_message_get_messages', params, preSets).then((response: AddonMessagesGetMessagesResult) => { | ||||
|                 response.messages.forEach((message) => { | ||||
|                     message.read = params.read == 0 ? 0 : 1; | ||||
|                     // Convert times to milliseconds.
 | ||||
| @ -1377,9 +1410,10 @@ export class AddonMessagesProvider { | ||||
|      * @param limitFromRead Number of unread messages already fetched, so fetch will be done from this number. | ||||
|      * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved with the data. | ||||
|      */ | ||||
|     protected getRecentMessages(params: any, preSets: any, limitFromUnread: number = 0, limitFromRead: number = 0, | ||||
|             toDisplay: boolean = true, siteId?: string): Promise<any> { | ||||
|             toDisplay: boolean = true, siteId?: string): Promise<AddonMessagesGetMessagesMessage[]> { | ||||
|         limitFromUnread = limitFromUnread || 0; | ||||
|         limitFromRead = limitFromRead || 0; | ||||
| 
 | ||||
| @ -1427,7 +1461,7 @@ export class AddonMessagesProvider { | ||||
|      * @since 3.7 | ||||
|      */ | ||||
|     getSelfConversation(messageOffset: number = 0, messageLimit: number = 1, newestFirst: boolean = true, siteId?: string, | ||||
|             userId?: number): Promise<any> { | ||||
|             userId?: number): Promise<AddonMessagesConversationFormatted> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             userId = userId || site.getUserId(); | ||||
| @ -1442,7 +1476,8 @@ export class AddonMessagesProvider { | ||||
|                     newestmessagesfirst: newestFirst ? 1 : 0 | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_get_self_conversation', params, preSets).then((conversation) => { | ||||
|             return site.read('core_message_get_self_conversation', params, preSets) | ||||
|                     .then((conversation: AddonMessagesConversation) => { | ||||
|                 return this.formatConversation(conversation, userId); | ||||
|             }); | ||||
|         }); | ||||
| @ -1466,7 +1501,8 @@ export class AddonMessagesProvider { | ||||
|                     cacheKey: this.getCacheKeyForUnreadConversationCounts() | ||||
|                 }; | ||||
| 
 | ||||
|                 promise = site.read('core_message_get_unread_conversation_counts', {}, preSets).then((result) => { | ||||
|                 promise = site.read('core_message_get_unread_conversation_counts', {}, preSets) | ||||
|                         .then((result: AddonMessagesGetUnreadConversationCountsResult) => { | ||||
|                     return { | ||||
|                         favourites: result.favourites, | ||||
|                         individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], | ||||
| @ -1485,7 +1521,7 @@ export class AddonMessagesProvider { | ||||
|                         typeExpected: 'number' | ||||
|                     }; | ||||
| 
 | ||||
|                 promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count) => { | ||||
|                 promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count: number) => { | ||||
|                     return { favourites: 0, individual: count, group: 0, self: 0 }; | ||||
|                 }); | ||||
|             } else { | ||||
| @ -1536,7 +1572,7 @@ export class AddonMessagesProvider { | ||||
|      * @return Promise resolved with the message unread count. | ||||
|      */ | ||||
|     getUnreadReceivedMessages(toDisplay: boolean = true, forceCache: boolean = false, ignoreCache: boolean = false, | ||||
|             siteId?: string): Promise<any> { | ||||
|             siteId?: string): Promise<AddonMessagesGetMessagesResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params = { | ||||
|                     read: 0, | ||||
| @ -2049,7 +2085,7 @@ export class AddonMessagesProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean marking success or not. | ||||
|      */ | ||||
|     markMessageRead(messageId: number, siteId?: string): Promise<any> { | ||||
|     markMessageRead(messageId: number, siteId?: string): Promise<AddonMessagesMarkMessageReadResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params = { | ||||
|                 messageid: messageId, | ||||
| @ -2067,7 +2103,7 @@ export class AddonMessagesProvider { | ||||
|      * @return Promise resolved if success. | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     markAllConversationMessagesRead(conversationId?: number): Promise<any> { | ||||
|     markAllConversationMessagesRead(conversationId?: number): Promise<null> { | ||||
|         const params = { | ||||
|                 userid: this.sitesProvider.getCurrentSiteUserId(), | ||||
|                 conversationid: conversationId | ||||
| @ -2085,7 +2121,7 @@ export class AddonMessagesProvider { | ||||
|      * @param userIdFrom User Id for the sender. | ||||
|      * @return Promise resolved with boolean marking success or not. | ||||
|      */ | ||||
|     markAllMessagesRead(userIdFrom?: number): Promise<any> { | ||||
|     markAllMessagesRead(userIdFrom?: number): Promise<boolean> { | ||||
|         const params = { | ||||
|                 useridto: this.sitesProvider.getCurrentSiteUserId(), | ||||
|                 useridfrom: userIdFrom | ||||
| @ -2217,8 +2253,9 @@ export class AddonMessagesProvider { | ||||
|      * @param query The query string. | ||||
|      * @param limit The number of results to return, 0 for none. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the contacts. | ||||
|      */ | ||||
|     searchContacts(query: string, limit: number = 100, siteId?: string): Promise<any> { | ||||
|     searchContacts(query: string, limit: number = 100, siteId?: string): Promise<AddonMessagesSearchContactsContact[]> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const data = { | ||||
|                     searchtext: query, | ||||
| @ -2228,7 +2265,9 @@ export class AddonMessagesProvider { | ||||
|                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_search_contacts', data, preSets).then((contacts) => { | ||||
|             return site.read('core_message_search_contacts', data, preSets) | ||||
|                     .then((contacts: AddonMessagesSearchContactsContact[]) => { | ||||
| 
 | ||||
|                 if (limit && contacts.length > limit) { | ||||
|                     contacts = contacts.splice(0, limit); | ||||
|                 } | ||||
| @ -2250,7 +2289,7 @@ export class AddonMessagesProvider { | ||||
|      * @return Promise resolved with the results. | ||||
|      */ | ||||
|     searchMessages(query: string, userId?: number, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, | ||||
|             siteId?: string): Promise<{messages: any[], canLoadMore: boolean}> { | ||||
|             siteId?: string): Promise<{messages: AddonMessagesMessageAreaContact[], canLoadMore: boolean}> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params = { | ||||
| @ -2263,13 +2302,15 @@ export class AddonMessagesProvider { | ||||
|                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_data_for_messagearea_search_messages', params, preSets).then((result) => { | ||||
|             return site.read('core_message_data_for_messagearea_search_messages', params, preSets) | ||||
|                     .then((result: AddonMessagesDataForMessageAreaSearchMessagesResult) => { | ||||
| 
 | ||||
|                 if (!result.contacts || !result.contacts.length) { | ||||
|                     return { messages: [], canLoadMore: false }; | ||||
|                 } | ||||
| 
 | ||||
|                 result.contacts.forEach((result) => { | ||||
|                     result.id = result.userid; | ||||
|                 result.contacts.forEach((contact) => { | ||||
|                     contact.id = contact.userid; | ||||
|                 }); | ||||
| 
 | ||||
|                 this.userProvider.storeUsers(result.contacts, site.id); | ||||
| @ -2297,7 +2338,8 @@ export class AddonMessagesProvider { | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     searchUsers(query: string, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, siteId?: string): | ||||
|             Promise<{contacts: any[], nonContacts: any[], canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> { | ||||
|             Promise<{contacts: AddonMessagesConversationMember[], nonContacts: AddonMessagesConversationMember[], | ||||
|                 canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const data = { | ||||
| @ -2310,7 +2352,7 @@ export class AddonMessagesProvider { | ||||
|                     getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
 | ||||
|                 }; | ||||
| 
 | ||||
|             return site.read('core_message_message_search_users', data, preSets).then((result) => { | ||||
|             return site.read('core_message_message_search_users', data, preSets).then((result: AddonMessagesSearchUsersResult) => { | ||||
|                 const contacts = result.contacts || []; | ||||
|                 const nonContacts = result.noncontacts || []; | ||||
| 
 | ||||
| @ -2341,7 +2383,9 @@ export class AddonMessagesProvider { | ||||
|      *         - sent (Boolean) True if message was sent to server, false if stored in device. | ||||
|      *         - message (Object) If sent=false, contains the stored message. | ||||
|      */ | ||||
|     sendMessage(toUserId: number, message: string, siteId?: string): Promise<any> { | ||||
|     sendMessage(toUserId: number, message: string, siteId?: string) | ||||
|             : Promise<{sent: boolean, message: AddonMessagesSendInstantMessagesMessage}> { | ||||
| 
 | ||||
|         // Convenience function to store a message to be synchronized later.
 | ||||
|         const storeOffline = (): Promise<any> => { | ||||
|             return this.messagesOffline.saveMessage(toUserId, message, siteId).then((entry) => { | ||||
| @ -2395,7 +2439,7 @@ export class AddonMessagesProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if success, rejected if failure. | ||||
|      */ | ||||
|     sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise<any> { | ||||
|     sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise<AddonMessagesSendInstantMessagesMessage> { | ||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
|         const messages = [ | ||||
| @ -2430,7 +2474,7 @@ export class AddonMessagesProvider { | ||||
|      * @return Promise resolved if success, rejected if failure. Promise resolved doesn't mean that messages | ||||
|      *         have been sent, the resolve param can contain errors for messages not sent. | ||||
|      */ | ||||
|     sendMessagesOnline(messages: any, siteId?: string): Promise<any> { | ||||
|     sendMessagesOnline(messages: any[], siteId?: string): Promise<AddonMessagesSendInstantMessagesMessage[]> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const data = { | ||||
|                 messages: messages | ||||
| @ -2451,7 +2495,9 @@ export class AddonMessagesProvider { | ||||
|      *         - message (any) If sent=false, contains the stored message. | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     sendMessageToConversation(conversation: any, message: string, siteId?: string): Promise<any> { | ||||
|     sendMessageToConversation(conversation: any, message: string, siteId?: string) | ||||
|             : Promise<{sent: boolean, message: AddonMessagesSendMessagesToConversationMessage}> { | ||||
| 
 | ||||
|         // Convenience function to store a message to be synchronized later.
 | ||||
|         const storeOffline = (): Promise<any> => { | ||||
|             return this.messagesOffline.saveConversationMessage(conversation, message, siteId).then((entry) => { | ||||
| @ -2506,7 +2552,8 @@ export class AddonMessagesProvider { | ||||
|      * @return Promise resolved if success, rejected if failure. | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string): Promise<any> { | ||||
|     sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string) | ||||
|             : Promise<AddonMessagesSendMessagesToConversationMessage> { | ||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
|         const messages = [ | ||||
| @ -2534,7 +2581,9 @@ export class AddonMessagesProvider { | ||||
|      * @return Promise resolved if success, rejected if failure. | ||||
|      * @since 3.6 | ||||
|      */ | ||||
|     sendMessagesToConversationOnline(conversationId: number, messages: any, siteId?: string): Promise<any> { | ||||
|     sendMessagesToConversationOnline(conversationId: number, messages: any[], siteId?: string) | ||||
|             : Promise<AddonMessagesSendMessagesToConversationMessage[]> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params = { | ||||
|                 conversationid: conversationId, | ||||
| @ -2603,10 +2652,10 @@ export class AddonMessagesProvider { | ||||
|      * @param conversations Array of conversations. | ||||
|      * @return Conversations sorted with most recent last. | ||||
|      */ | ||||
|     sortConversations(conversations: any[]): any[] { | ||||
|     sortConversations(conversations: AddonMessagesConversationFormatted[]): AddonMessagesConversationFormatted[] { | ||||
|         return conversations.sort((a, b) => { | ||||
|             const timeA = parseInt(a.lastmessagedate, 10), | ||||
|                 timeB = parseInt(b.lastmessagedate, 10); | ||||
|             const timeA = Number(a.lastmessagedate), | ||||
|                 timeB = Number(b.lastmessagedate); | ||||
| 
 | ||||
|             if (timeA == timeB && a.id) { | ||||
|                 // Same time, sort by ID.
 | ||||
| @ -2651,7 +2700,9 @@ export class AddonMessagesProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, message: any, siteId?: string): Promise<any> { | ||||
|     protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, | ||||
|             message: AddonMessagesGetMessagesMessage | AddonMessagesConversationMessage, siteId?: string): Promise<any> { | ||||
| 
 | ||||
|         const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT; | ||||
| 
 | ||||
|         // Get the last received message.
 | ||||
| @ -2675,7 +2726,7 @@ export class AddonMessagesProvider { | ||||
|      * | ||||
|      * @param contactTypes List of contacts grouped in types. | ||||
|      */ | ||||
|     protected storeUsersFromAllContacts(contactTypes: any): void { | ||||
|     protected storeUsersFromAllContacts(contactTypes: AddonMessagesGetContactsResult): void { | ||||
|         for (const x in contactTypes) { | ||||
|             this.userProvider.storeUsers(contactTypes[x]); | ||||
|         } | ||||
| @ -2735,3 +2786,377 @@ export class AddonMessagesProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Conversation. | ||||
|  */ | ||||
| export type AddonMessagesConversation = { | ||||
|     id: number; // The conversation id.
 | ||||
|     name: string; // The conversation name, if set.
 | ||||
|     subname: string; // A subtitle for the conversation name, if set.
 | ||||
|     imageurl: string; // A link to the conversation picture, if set.
 | ||||
|     type: number; // The type of the conversation (1=individual,2=group,3=self).
 | ||||
|     membercount: number; // Total number of conversation members.
 | ||||
|     ismuted: boolean; // If the user muted this conversation.
 | ||||
|     isfavourite: boolean; // If the user marked this conversation as a favourite.
 | ||||
|     isread: boolean; // If the user has read all messages in the conversation.
 | ||||
|     unreadcount: number; // The number of unread messages in this conversation.
 | ||||
|     members: AddonMessagesConversationMember[]; | ||||
|     messages: AddonMessagesConversationMessage[]; | ||||
|     candeletemessagesforallusers: boolean; // @since 3.7. If the user can delete messages in the conversation for all users.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Conversation with some calculated data. | ||||
|  */ | ||||
| export type AddonMessagesConversationFormatted = AddonMessagesConversation & { | ||||
|     lastmessage?: string; // Calculated in the app. Last message.
 | ||||
|     lastmessagedate?: number; // Calculated in the app. Date the last message was sent.
 | ||||
|     sentfromcurrentuser?: boolean; // Calculated in the app. Whether last message was sent by the current user.
 | ||||
|     name?: string; // Calculated in the app. If private conversation, name of the other user.
 | ||||
|     userid?: number; // Calculated in the app. URL. If private conversation, ID of the other user.
 | ||||
|     showonlinestatus?: boolean; // Calculated in the app. If private conversation, whether to show online status of the other user.
 | ||||
|     isonline?: boolean; // Calculated in the app. If private conversation, whether the other user is online.
 | ||||
|     isblocked?: boolean; // Calculated in the app. If private conversation, whether the other user is blocked.
 | ||||
|     otherUser?: AddonMessagesConversationMember; // Calculated in the app. Other user in the conversation.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Conversation member. | ||||
|  */ | ||||
| export type AddonMessagesConversationMember = { | ||||
|     id: number; // The user id.
 | ||||
|     fullname: string; // The user's name.
 | ||||
|     profileurl: string; // The link to the user's profile page.
 | ||||
|     profileimageurl: string; // User picture URL.
 | ||||
|     profileimageurlsmall: string; // Small user picture URL.
 | ||||
|     isonline: boolean; // The user's online status.
 | ||||
|     showonlinestatus: boolean; // Show the user's online status?.
 | ||||
|     isblocked: boolean; // If the user has been blocked.
 | ||||
|     iscontact: boolean; // Is the user a contact?.
 | ||||
|     isdeleted: boolean; // Is the user deleted?.
 | ||||
|     canmessageevenifblocked: boolean; // If the user can still message even if they get blocked.
 | ||||
|     canmessage: boolean; // If the user can be messaged.
 | ||||
|     requirescontact: boolean; // If the user requires to be contacts.
 | ||||
|     contactrequests?: { // The contact requests.
 | ||||
|         id: number; // The id of the contact request.
 | ||||
|         userid: number; // The id of the user who created the contact request.
 | ||||
|         requesteduserid: number; // The id of the user confirming the request.
 | ||||
|         timecreated: number; // The timecreated timestamp for the contact request.
 | ||||
|     }[]; | ||||
|     conversations?: { // Conversations between users.
 | ||||
|         id: number; // Conversations id.
 | ||||
|         type: number; // Conversation type: private or public.
 | ||||
|         name: string; // Multilang compatible conversation name2.
 | ||||
|         timecreated: number; // The timecreated timestamp for the conversation.
 | ||||
|     }[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Conversation message. | ||||
|  */ | ||||
| export type AddonMessagesConversationMessage = { | ||||
|     id: number; // The id of the message.
 | ||||
|     useridfrom: number; // The id of the user who sent the message.
 | ||||
|     text: string; // The text of the message.
 | ||||
|     timecreated: number; // The timecreated timestamp for the message.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Message preferences. | ||||
|  */ | ||||
| export type AddonMessagesMessagePreferences = { | ||||
|     userid: number; // User id.
 | ||||
|     disableall: number; // Whether all the preferences are disabled.
 | ||||
|     processors: { // Config form values.
 | ||||
|         displayname: string; // Display name.
 | ||||
|         name: string; // Processor name.
 | ||||
|         hassettings: boolean; // Whether has settings.
 | ||||
|         contextid: number; // Context id.
 | ||||
|         userconfigured: number; // Whether is configured by the user.
 | ||||
|     }[]; | ||||
|     components: { // Available components.
 | ||||
|         displayname: string; // Display name.
 | ||||
|         notifications: AddonMessagesMessagePreferencesNotification[]; // List of notificaitons for the component.
 | ||||
|     }[]; | ||||
| } & AddonMessagesMessagePreferencesCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Notification processor in message preferences. | ||||
|  */ | ||||
| export type AddonMessagesMessagePreferencesNotification = { | ||||
|     displayname: string; // Display name.
 | ||||
|     preferencekey: string; // Preference key.
 | ||||
|     processors: AddonMessagesMessagePreferencesNotificationProcessor[]; // Processors values for this notification.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Notification processor in message preferences. | ||||
|  */ | ||||
| export type AddonMessagesMessagePreferencesNotificationProcessor = { | ||||
|     displayname: string; // Display name.
 | ||||
|     name: string; // Processor name.
 | ||||
|     locked: boolean; // Is locked by admin?.
 | ||||
|     lockedmessage?: string; // Text to display if locked.
 | ||||
|     userconfigured: number; // Is configured?.
 | ||||
|     loggedin: { | ||||
|         name: string; // Name.
 | ||||
|         displayname: string; // Display name.
 | ||||
|         checked: boolean; // Is checked?.
 | ||||
|     }; | ||||
|     loggedoff: { | ||||
|         name: string; // Name.
 | ||||
|         displayname: string; // Display name.
 | ||||
|         checked: boolean; // Is checked?.
 | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Message discussion (before 3.6). | ||||
|  */ | ||||
| export type AddonMessagesDiscussion = { | ||||
|     fullname: string; // Full name of the other user in the discussion.
 | ||||
|     profileimageurl: string; // Profile image of the other user in the discussion.
 | ||||
|     message?: { // Last message.
 | ||||
|         id: number; // Message ID.
 | ||||
|         user: number; // User ID that sent the message.
 | ||||
|         message: string; // Text of the message.
 | ||||
|         timecreated: number; // Time the message was sent.
 | ||||
|         pending?: boolean; // Whether the message is pending to be sent.
 | ||||
|     }; | ||||
|     unread?: boolean; // Whether the discussion has unread messages.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Contact for message area. | ||||
|  */ | ||||
| export type AddonMessagesMessageAreaContact = { | ||||
|     userid: number; // The user's id.
 | ||||
|     fullname: string; // The user's name.
 | ||||
|     profileimageurl: string; // User picture URL.
 | ||||
|     profileimageurlsmall: string; // Small user picture URL.
 | ||||
|     ismessaging: boolean; // If we are messaging the user.
 | ||||
|     sentfromcurrentuser: boolean; // Was the last message sent from the current user?.
 | ||||
|     lastmessage: string; // The user's last message.
 | ||||
|     lastmessagedate: number; // Timestamp for last message.
 | ||||
|     messageid: number; // The unique search message id.
 | ||||
|     showonlinestatus: boolean; // Show the user's online status?.
 | ||||
|     isonline: boolean; // The user's online status.
 | ||||
|     isread: boolean; // If the user has read the message.
 | ||||
|     isblocked: boolean; // If the user has been blocked.
 | ||||
|     unreadcount: number; // The number of unread messages in this conversation.
 | ||||
|     conversationid: number; // The id of the conversation.
 | ||||
| } & AddonMessagesMessageAreaContactCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_blocked_users. | ||||
|  */ | ||||
| export type AddonMessagesGetBlockedUsersResult = { | ||||
|     users: AddonMessagesBlockedUser[]; // List of blocked users.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * User data returned by core_message_get_blocked_users. | ||||
|  */ | ||||
| export type AddonMessagesBlockedUser = { | ||||
|     id: number; // User ID.
 | ||||
|     fullname: string; // User full name.
 | ||||
|     profileimageurl?: string; // User picture URL.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_contacts. | ||||
|  */ | ||||
| export type AddonMessagesGetContactsResult = { | ||||
|     online: AddonMessagesGetContactsContact[]; // List of online contacts.
 | ||||
|     offline: AddonMessagesGetContactsContact[]; // List of offline contacts.
 | ||||
|     strangers: AddonMessagesGetContactsContact[]; // List of users that are not in the user's contact list but have sent a message.
 | ||||
| } & AddonMessagesGetContactsCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * User data returned by core_message_get_contacts. | ||||
|  */ | ||||
| export type AddonMessagesGetContactsContact = { | ||||
|     id: number; // User ID.
 | ||||
|     fullname: string; // User full name.
 | ||||
|     profileimageurl?: string; // User picture URL.
 | ||||
|     profileimageurlsmall?: string; // Small user picture URL.
 | ||||
|     unread: number; // Unread message count.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * User data returned by core_message_search_contacts. | ||||
|  */ | ||||
| export type AddonMessagesSearchContactsContact = { | ||||
|     id: number; // User ID.
 | ||||
|     fullname: string; // User full name.
 | ||||
|     profileimageurl?: string; // User picture URL.
 | ||||
|     profileimageurlsmall?: string; // Small user picture URL.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_conversation_messages. | ||||
|  */ | ||||
| export type AddonMessagesGetConversationMessagesResult = { | ||||
|     id: number; // The conversation id.
 | ||||
|     members: AddonMessagesConversationMember[]; | ||||
|     messages: AddonMessagesConversationMessage[]; | ||||
| } & AddonMessagesGetConversationMessagesCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_conversations. | ||||
|  */ | ||||
| export type AddonMessagesGetConversationsResult = { | ||||
|     conversations: AddonMessagesConversation[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_conversation_counts. | ||||
|  */ | ||||
| export type AddonMessagesGetConversationCountsResult = { | ||||
|     favourites: number; // Total number of favourite conversations.
 | ||||
|     types: { | ||||
|         1: number; // Total number of individual conversations.
 | ||||
|         2: number; // Total number of group conversations.
 | ||||
|         3: number; // Total number of self conversations.
 | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_unread_conversation_counts. | ||||
|  */ | ||||
| export type AddonMessagesGetUnreadConversationCountsResult = { | ||||
|     favourites: number; // Total number of unread favourite conversations.
 | ||||
|     types: { | ||||
|         1: number; // Total number of unread individual conversations.
 | ||||
|         2: number; // Total number of unread group conversations.
 | ||||
|         3: number; // Total number of unread self conversations.
 | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_user_message_preferences. | ||||
|  */ | ||||
| export type AddonMessagesGetUserMessagePreferencesResult = { | ||||
|     preferences: AddonMessagesMessagePreferences; | ||||
|     blocknoncontacts: number; // Privacy messaging setting to define who can message you.
 | ||||
|     entertosend: boolean; // User preference for using enter to send messages.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_messages. | ||||
|  */ | ||||
| export type AddonMessagesGetMessagesResult = { | ||||
|     messages: AddonMessagesGetMessagesMessage[]; | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Message data returned by core_message_get_messages. | ||||
|  */ | ||||
| export type AddonMessagesGetMessagesMessage = { | ||||
|     id: number; // Message id.
 | ||||
|     useridfrom: number; // User from id.
 | ||||
|     useridto: number; // User to id.
 | ||||
|     subject: string; // The message subject.
 | ||||
|     text: string; // The message text formated.
 | ||||
|     fullmessage: string; // The message.
 | ||||
|     fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     fullmessagehtml: string; // The message in html.
 | ||||
|     smallmessage: string; // The shorten message.
 | ||||
|     notification: number; // Is a notification?.
 | ||||
|     contexturl: string; // Context URL.
 | ||||
|     contexturlname: string; // Context URL link name.
 | ||||
|     timecreated: number; // Time created.
 | ||||
|     timeread: number; // Time read.
 | ||||
|     usertofullname: string; // User to full name.
 | ||||
|     userfromfullname: string; // User from full name.
 | ||||
|     component?: string; // The component that generated the notification.
 | ||||
|     eventtype?: string; // The type of notification.
 | ||||
|     customdata?: string; // Custom data to be passed to the message processor.
 | ||||
| } & AddonMessagesGetMessagesMessageCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_data_for_messagearea_search_messages. | ||||
|  */ | ||||
| export type AddonMessagesDataForMessageAreaSearchMessagesResult = { | ||||
|     contacts: AddonMessagesMessageAreaContact[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_message_search_users. | ||||
|  */ | ||||
| export type AddonMessagesSearchUsersResult = { | ||||
|     contacts: AddonMessagesConversationMember[]; | ||||
|     noncontacts: AddonMessagesConversationMember[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_mark_message_read. | ||||
|  */ | ||||
| export type AddonMessagesMarkMessageReadResult = { | ||||
|     messageid: number; // The id of the message in the messages table.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_send_instant_messages. | ||||
|  */ | ||||
| export type AddonMessagesSendInstantMessagesMessage = { | ||||
|     msgid: number; // Test this to know if it succeeds:  id of the created message if it succeeded, -1 when failed.
 | ||||
|     clientmsgid?: string; // Your own id for the message.
 | ||||
|     errormessage?: string; // Error message - if it failed.
 | ||||
|     text?: string; // The text of the message.
 | ||||
|     timecreated?: number; // The timecreated timestamp for the message.
 | ||||
|     conversationid?: number; // The conversation id for this message.
 | ||||
|     useridfrom?: number; // The user id who sent the message.
 | ||||
|     candeletemessagesforallusers: boolean; // If the user can delete messages in the conversation for all users.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_send_messages_to_conversation. | ||||
|  */ | ||||
| export type AddonMessagesSendMessagesToConversationMessage = { | ||||
|     id: number; // The id of the message.
 | ||||
|     useridfrom: number; // The id of the user who sent the message.
 | ||||
|     text: string; // The text of the message.
 | ||||
|     timecreated: number; // The timecreated timestamp for the message.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for core_message_get_contacts. | ||||
|  */ | ||||
| export type AddonMessagesGetContactsCalculatedData = { | ||||
|     blocked?: AddonMessagesBlockedUser[]; // Calculated in the app. List of blocked users.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for core_message_get_conversation_messages. | ||||
|  */ | ||||
| export type AddonMessagesGetConversationMessagesCalculatedData = { | ||||
|     canLoadMore?: boolean; // Calculated in the app. Whether more messages can be loaded.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for message preferences. | ||||
|  */ | ||||
| export type AddonMessagesMessagePreferencesCalculatedData = { | ||||
|     blocknoncontacts?: number; // Calculated in the app. Based on the result of core_message_get_user_message_preferences.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for messages returned by core_message_get_messages. | ||||
|  */ | ||||
| export type AddonMessagesGetMessagesMessageCalculatedData = { | ||||
|     pending?: boolean; // Calculated in the app. Whether the message is pending to be sent.
 | ||||
|     read?: number; // Calculated in the app. Whether the message has been read.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for contact for message area. | ||||
|  */ | ||||
| export type AddonMessagesMessageAreaContactCalculatedData = { | ||||
|     id?: number; // Calculated in the app. User ID.
 | ||||
| }; | ||||
|  | ||||
| @ -18,7 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreSyncBaseProvider } from '@classes/base-sync'; | ||||
| import { CoreAppProvider } from '@providers/app'; | ||||
| import { AddonMessagesOfflineProvider } from './messages-offline'; | ||||
| import { AddonMessagesProvider } from './messages'; | ||||
| import { AddonMessagesProvider, AddonMessagesConversationFormatted } from './messages'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| @ -258,7 +258,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { | ||||
|                 // Get conversation name and add errors to warnings array.
 | ||||
|                 return this.messagesProvider.getConversation(conversationId, false, false).catch(() => { | ||||
|                     // Ignore errors.
 | ||||
|                     return {}; | ||||
|                     return <AddonMessagesConversationFormatted> {}; | ||||
|                 }).then((conversation) => { | ||||
|                     errors.forEach((error) => { | ||||
|                         warnings.push(this.translate.instant('addon.messages.warningconversationmessagenotsent', { | ||||
|  | ||||
| @ -21,7 +21,7 @@ import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { coreSlideInOut } from '@classes/animations'; | ||||
| import { AddonNotesProvider } from '../../providers/notes'; | ||||
| import { AddonNotesProvider, AddonNotesNoteFormatted } from '../../providers/notes'; | ||||
| import { AddonNotesOfflineProvider } from '../../providers/notes-offline'; | ||||
| import { AddonNotesSyncProvider } from '../../providers/notes-sync'; | ||||
| 
 | ||||
| @ -44,7 +44,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | ||||
|     type = 'course'; | ||||
|     refreshIcon = 'spinner'; | ||||
|     syncIcon = 'spinner'; | ||||
|     notes: any[]; | ||||
|     notes: AddonNotesNoteFormatted[]; | ||||
|     hasOffline = false; | ||||
|     notesLoaded = false; | ||||
|     user: any; | ||||
| @ -101,21 +101,21 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | ||||
|             // Ignore errors.
 | ||||
|         }).then(() => { | ||||
|             return this.notesProvider.getNotes(this.courseId, this.userId).then((notes) => { | ||||
|                 notes = notes[this.type + 'notes'] || []; | ||||
|                 const notesList: AddonNotesNoteFormatted[] = notes[this.type + 'notes'] || []; | ||||
| 
 | ||||
|                 return this.notesProvider.setOfflineDeletedNotes(notes, this.courseId).then((notes) => { | ||||
|                 return this.notesProvider.setOfflineDeletedNotes(notesList, this.courseId).then((notesList) => { | ||||
| 
 | ||||
|                     this.hasOffline = notes.some((note) => note.offline || note.deleted); | ||||
|                     this.hasOffline = notesList.some((note) => note.offline || note.deleted); | ||||
| 
 | ||||
|                     if (this.userId) { | ||||
|                         this.notes = notes; | ||||
|                         this.notes = notesList; | ||||
| 
 | ||||
|                         // Get the user profile to retrieve the user image.
 | ||||
|                         return this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => { | ||||
|                             this.user = user; | ||||
|                         }); | ||||
|                     } else { | ||||
|                         return this.notesProvider.getNotesUserData(notes, this.courseId).then((notes) => { | ||||
|                         return this.notesProvider.getNotesUserData(notesList, this.courseId).then((notes) => { | ||||
|                             this.notes = notes; | ||||
|                         }); | ||||
|                     } | ||||
| @ -126,7 +126,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | ||||
|         }).finally(() => { | ||||
|             let canDelete = this.notes && this.notes.length > 0; | ||||
|             if (canDelete && this.type == 'personal') { | ||||
|                 canDelete = this.notes.find((note) =>  { | ||||
|                 canDelete = !!this.notes.find((note) =>  { | ||||
|                     return note.usermodified == this.currentUserId; | ||||
|                 }); | ||||
|             } | ||||
| @ -178,6 +178,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | ||||
|     addNote(e: Event): void { | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|         const modal = this.modalCtrl.create('AddonNotesAddPage', { userId: this.userId, courseId: this.courseId, type: this.type }); | ||||
|         modal.onDidDismiss((data) => { | ||||
|             if (data && data.sent && data.type) { | ||||
| @ -192,6 +193,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | ||||
|                 this.typeChanged(); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         modal.present(); | ||||
|     } | ||||
| 
 | ||||
| @ -201,7 +203,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | ||||
|      * @param e Click event. | ||||
|      * @param note Note to delete. | ||||
|      */ | ||||
|     deleteNote(e: Event, note: any): void { | ||||
|     deleteNote(e: Event, note: AddonNotesNoteFormatted): void { | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
| @ -226,7 +228,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { | ||||
|      * @param e Click event. | ||||
|      * @param note Note to delete. | ||||
|      */ | ||||
|     undoDeleteNote(e: Event, note: any): void { | ||||
|     undoDeleteNote(e: Event, note: AddonNotesNoteFormatted): void { | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|  | ||||
| @ -22,6 +22,7 @@ import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { AddonNotesOfflineProvider } from './notes-offline'; | ||||
| import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; | ||||
| import { CoreWSExternalWarning } from '@providers/ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle notes. | ||||
| @ -119,9 +120,9 @@ export class AddonNotesProvider { | ||||
|      * @return Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that notes | ||||
|      *         have been added, the resolve param can contain errors for notes not sent. | ||||
|      */ | ||||
|     addNotesOnline(notes: any[], siteId?: string): Promise<any> { | ||||
|     addNotesOnline(notes: any[], siteId?: string): Promise<AddonNotesCreateNotesNote[]> { | ||||
|         if (!notes || !notes.length) { | ||||
|             return Promise.resolve(); | ||||
|             return Promise.resolve([]); | ||||
|         } | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| @ -142,7 +143,7 @@ export class AddonNotesProvider { | ||||
|      * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes | ||||
|      *         have been deleted, the resolve param can contain errors for notes not deleted. | ||||
|      */ | ||||
|     deleteNote(note: any, courseId: number, siteId?: string): Promise<void> { | ||||
|     deleteNote(note: AddonNotesNoteFormatted, courseId: number, siteId?: string): Promise<void> { | ||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
|         if (note.offline) { | ||||
| @ -190,7 +191,7 @@ export class AddonNotesProvider { | ||||
|                 notes: noteIds | ||||
|             }; | ||||
| 
 | ||||
|             return site.write('core_notes_delete_notes', data).then((response) => { | ||||
|             return site.write('core_notes_delete_notes', data).then((response: CoreWSExternalWarning[]) => { | ||||
|                 // A note was deleted, invalidate the course notes.
 | ||||
|                 return this.invalidateNotes(courseId, undefined, siteId).catch(() => { | ||||
|                     // Ignore errors.
 | ||||
| @ -288,7 +289,9 @@ export class AddonNotesProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise to be resolved when the notes are retrieved. | ||||
|      */ | ||||
|     getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string): Promise<any> { | ||||
|     getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string) | ||||
|             : Promise<AddonNotesGetCourseNotesResult> { | ||||
| 
 | ||||
|         this.logger.debug('Get notes for course ' + courseId); | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| @ -310,7 +313,7 @@ export class AddonNotesProvider { | ||||
|                 preSets.emergencyCache = false; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_notes_get_course_notes', data, preSets).then((notes) => { | ||||
|             return site.read('core_notes_get_course_notes', data, preSets).then((notes: AddonNotesGetCourseNotesResult) => { | ||||
|                 if (onlyOnline) { | ||||
|                     return notes; | ||||
|                 } | ||||
| @ -339,9 +342,11 @@ export class AddonNotesProvider { | ||||
|      * @param notes Array of notes. | ||||
|      * @param courseId ID of the course the notes belong to. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return [description] | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     setOfflineDeletedNotes(notes: any[], courseId: number, siteId?: string): Promise<any> { | ||||
|     setOfflineDeletedNotes(notes: AddonNotesNoteFormatted[], courseId: number, siteId?: string) | ||||
|             : Promise<AddonNotesNoteFormatted[]> { | ||||
| 
 | ||||
|         return this.notesOffline.getCourseDeletedNotes(courseId, siteId).then((deletedNotes) => { | ||||
|             notes.forEach((note) => { | ||||
|                 note.deleted = deletedNotes.some((n) => n.noteid == note.id); | ||||
| @ -358,7 +363,7 @@ export class AddonNotesProvider { | ||||
|      * @param courseId ID of the course the notes belong to. | ||||
|      * @return Promise always resolved. Resolve param is the formatted notes. | ||||
|      */ | ||||
|     getNotesUserData(notes: any[], courseId: number): Promise<any> { | ||||
|     getNotesUserData(notes: AddonNotesNoteFormatted[], courseId: number): Promise<AddonNotesNoteFormatted[]> { | ||||
|         const promises = notes.map((note) => { | ||||
|             // Get the user profile to retrieve the user image.
 | ||||
|             return this.userProvider.getProfile(note.userid, note.courseid, true).then((user) => { | ||||
| @ -400,7 +405,7 @@ export class AddonNotesProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(courseId: number, userId?: number, siteId?: string): Promise<any> { | ||||
|     logView(courseId: number, userId?: number, siteId?: string): Promise<AddonNotesViewNotesResult> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params = { | ||||
|                 courseid: courseId, | ||||
| @ -413,3 +418,57 @@ export class AddonNotesProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Note data returned by core_notes_get_course_notes. | ||||
|  */ | ||||
| export type AddonNotesNote = { | ||||
|     id: number; // Id of this note.
 | ||||
|     courseid: number; // Id of the course.
 | ||||
|     userid: number; // User id.
 | ||||
|     content: string; // The content text formated.
 | ||||
|     format: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     created: number; // Time created (timestamp).
 | ||||
|     lastmodified: number; // Time of last modification (timestamp).
 | ||||
|     usermodified: number; // User id of the creator of this note.
 | ||||
|     publishstate: string; // State of the note (i.e. draft, public, site).
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_notes_get_course_notes. | ||||
|  */ | ||||
| export type AddonNotesGetCourseNotesResult = { | ||||
|     sitenotes?: AddonNotesNote[]; // Site notes.
 | ||||
|     coursenotes?: AddonNotesNote[]; // Couse notes.
 | ||||
|     personalnotes?: AddonNotesNote[]; // Personal notes.
 | ||||
|     canmanagesystemnotes?: boolean; // Whether the user can manage notes at system level.
 | ||||
|     canmanagecoursenotes?: boolean; // Whether the user can manage notes at the given course.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Note returned by WS core_notes_create_notes. | ||||
|  */ | ||||
| export type AddonNotesCreateNotesNote = { | ||||
|     clientnoteid?: string; // Your own id for the note.
 | ||||
|     noteid: number; // ID of the created note when successful, -1 when failed.
 | ||||
|     errormessage?: string; // Error message - if failed.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_notes_view_notes. | ||||
|  */ | ||||
| export type AddonNotesViewNotesResult = { | ||||
|     status: boolean; // Status: true if success.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Notes with some calculated data. | ||||
|  */ | ||||
| export type AddonNotesNoteFormatted = AddonNotesNote & { | ||||
|     offline?: boolean; // Calculated in the app. Whether it's an offline note.
 | ||||
|     deleted?: boolean; // Calculated in the app. Whether the note was deleted in offline.
 | ||||
|     userfullname?: string; // Calculated in the app. Full name of the user the note refers to.
 | ||||
|     userprofileimageurl?: string; // Calculated in the app. Avatar url of the user the note refers to.
 | ||||
| }; | ||||
|  | ||||
| @ -20,7 +20,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreEventsProvider, CoreEventObserver } from '@providers/events'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { AddonNotificationsProvider } from '../../providers/notifications'; | ||||
| import { AddonNotificationsProvider, AddonNotificationsAnyNotification } from '../../providers/notifications'; | ||||
| import { AddonNotificationsHelperProvider } from '../../providers/helper'; | ||||
| import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; | ||||
| 
 | ||||
| @ -34,7 +34,7 @@ import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers | ||||
| }) | ||||
| export class AddonNotificationsListPage { | ||||
| 
 | ||||
|     notifications = []; | ||||
|     notifications: AddonNotificationsAnyNotification[] = []; | ||||
|     notificationsLoaded = false; | ||||
|     canLoadMore = false; | ||||
|     loadMoreError = false; | ||||
| @ -130,11 +130,12 @@ export class AddonNotificationsListPage { | ||||
|      * | ||||
|      * @param notifications Array of notification objects. | ||||
|      */ | ||||
|     protected markNotificationsAsRead(notifications: any[]): void { | ||||
|     protected markNotificationsAsRead(notifications: AddonNotificationsAnyNotification[]): void { | ||||
| 
 | ||||
|         let promise; | ||||
| 
 | ||||
|         if (notifications.length > 0) { | ||||
|             const promises = notifications.map((notification) => { | ||||
|             const promises: Promise<any>[] = notifications.map((notification) => { | ||||
|                 if (notification.read) { | ||||
|                     // Already read, don't mark it.
 | ||||
|                     return Promise.resolve(); | ||||
| @ -202,7 +203,7 @@ export class AddonNotificationsListPage { | ||||
|      * | ||||
|      * @param notification The notification object. | ||||
|      */ | ||||
|     protected formatText(notification: any): void { | ||||
|     protected formatText(notification: AddonNotificationsAnyNotification): void { | ||||
|         const text = notification.mobiletext.replace(/-{4,}/ig, ''); | ||||
|         notification.mobiletext = this.textUtils.replaceNewLines(text, '<br>'); | ||||
|     } | ||||
|  | ||||
| @ -14,7 +14,11 @@ | ||||
| 
 | ||||
| import { Component, OnDestroy, Optional } from '@angular/core'; | ||||
| import { IonicPage, NavController } from 'ionic-angular'; | ||||
| import { AddonNotificationsProvider } from '../../providers/notifications'; | ||||
| import { | ||||
|     AddonNotificationsProvider, AddonNotificationsNotificationPreferences, AddonNotificationsNotificationPreferencesProcessor, | ||||
|     AddonNotificationsNotificationPreferencesComponent, AddonNotificationsNotificationPreferencesNotification, | ||||
|     AddonNotificationsNotificationPreferencesNotificationProcessorState | ||||
| } from '../../providers/notifications'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreSettingsHelper } from '@core/settings/providers/helper'; | ||||
| @ -38,10 +42,10 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| export class AddonNotificationsSettingsPage implements OnDestroy { | ||||
|     protected updateTimeout: any; | ||||
| 
 | ||||
|     components: any[]; | ||||
|     preferences: any; | ||||
|     components: AddonNotificationsNotificationPreferencesComponent[]; | ||||
|     preferences: AddonNotificationsNotificationPreferences; | ||||
|     preferencesLoaded: boolean; | ||||
|     currentProcessor: any; | ||||
|     currentProcessor: AddonNotificationsNotificationPreferencesProcessorFormatted; | ||||
|     notifPrefsEnabled: boolean; | ||||
|     canChangeSound: boolean; | ||||
|     notificationSound: boolean; | ||||
| @ -99,7 +103,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | ||||
|             // Get display data of message output handlers (thery are displayed in the context menu),
 | ||||
|             this.processorHandlers = []; | ||||
|             if (preferences.processors) { | ||||
|                 preferences.processors.forEach((processor) => { | ||||
|                 preferences.processors.forEach((processor: AddonNotificationsNotificationPreferencesProcessorFormatted) => { | ||||
|                     processor.supported = this.messageOutputDelegate.hasHandler(processor.name, true); | ||||
|                     if (processor.hassettings && processor.supported) { | ||||
|                         this.processorHandlers.push(this.messageOutputDelegate.getDisplayData(processor)); | ||||
| @ -118,7 +122,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | ||||
|      * | ||||
|      * @param processor Processor object. | ||||
|      */ | ||||
|     protected loadProcessor(processor: any): void { | ||||
|     protected loadProcessor(processor: AddonNotificationsNotificationPreferencesProcessorFormatted): void { | ||||
|         if (!processor) { | ||||
|             return; | ||||
|         } | ||||
| @ -191,8 +195,9 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | ||||
|      * @param notification Notification object. | ||||
|      * @param state State name, ['loggedin', 'loggedoff']. | ||||
|      */ | ||||
|     changePreference(notification: any, state: string): void { | ||||
|         const processorState = notification.currentProcessor[state]; | ||||
|     changePreference(notification: AddonNotificationsNotificationPreferencesNotificationFormatted, state: string): void { | ||||
|         const processorState: AddonNotificationsNotificationPreferencesNotificationProcessorStateFormatted = | ||||
|                 notification.currentProcessor[state]; | ||||
|         const preferenceName = notification.preferencekey + '_' + processorState.name; | ||||
|         let value; | ||||
| 
 | ||||
| @ -211,6 +216,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | ||||
|         } | ||||
| 
 | ||||
|         processorState.updating = true; | ||||
| 
 | ||||
|         this.userProvider.updateUserPreference(preferenceName, value).then(() => { | ||||
|             // Update the preferences since they were modified.
 | ||||
|             this.updatePreferencesAfterDelay(); | ||||
| @ -264,3 +270,25 @@ export class AddonNotificationsSettingsPage implements OnDestroy { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Notification preferences notification with some calculated data. | ||||
|  */ | ||||
| type AddonNotificationsNotificationPreferencesNotificationFormatted = AddonNotificationsNotificationPreferencesNotification & { | ||||
|     currentProcessor?: AddonNotificationsNotificationPreferencesProcessorFormatted; // Calculated in the app. Current processor.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Notification preferences processor with some calculated data. | ||||
|  */ | ||||
| type AddonNotificationsNotificationPreferencesProcessorFormatted = AddonNotificationsNotificationPreferencesProcessor & { | ||||
|     supported?: boolean; // Calculated in the app. Whether the processor is supported in the app.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * State in notification processor in notification preferences component with some calculated data. | ||||
|  */ | ||||
| type AddonNotificationsNotificationPreferencesNotificationProcessorStateFormatted = | ||||
|         AddonNotificationsNotificationPreferencesNotificationProcessorState & { | ||||
|     updating?: boolean; // Calculated in the app. Whether the state is being updated.
 | ||||
| }; | ||||
|  | ||||
| @ -14,7 +14,9 @@ | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { AddonNotificationsProvider } from './notifications'; | ||||
| import { | ||||
|     AddonNotificationsProvider, AddonNotificationsAnyNotification, AddonNotificationsGetMessagesMessage | ||||
| } from './notifications'; | ||||
| 
 | ||||
| /** | ||||
|  * Service that provides some helper functions for notifications. | ||||
| @ -37,7 +39,7 @@ export class AddonNotificationsHelperProvider { | ||||
|      * @return Promise resolved with notifications and if can load more. | ||||
|      */ | ||||
|     getNotifications(notifications: any[], limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, | ||||
|             siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> { | ||||
|             siteId?: string): Promise<{notifications: AddonNotificationsAnyNotification[], canLoadMore: boolean}> { | ||||
| 
 | ||||
|         notifications = notifications || []; | ||||
|         limit = limit || AddonNotificationsProvider.LIST_LIMIT; | ||||
| @ -80,7 +82,7 @@ export class AddonNotificationsHelperProvider { | ||||
|                         promise = Promise.resolve(unread); | ||||
|                     } | ||||
| 
 | ||||
|                     return promise.then((notifications) => { | ||||
|                     return promise.then((notifications: AddonNotificationsGetMessagesMessage[]) => { | ||||
|                         return { | ||||
|                             notifications: notifications, | ||||
|                             canLoadMore: notifications.length >= limit | ||||
|  | ||||
| @ -20,8 +20,9 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { CoreUserProvider } from '@core/user/providers/user'; | ||||
| import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; | ||||
| import { AddonMessagesProvider } from '@addon/messages/providers/messages'; | ||||
| import { AddonMessagesProvider, AddonMessagesMarkMessageReadResult } from '@addon/messages/providers/messages'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreWSExternalWarning } from '@providers/ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle notifications. | ||||
| @ -51,14 +52,13 @@ export class AddonNotificationsProvider { | ||||
|      * @param read Whether the notifications are read or unread. | ||||
|      * @return Promise resolved with notifications. | ||||
|      */ | ||||
|     protected formatNotificationsData(notifications: any[], read?: boolean): Promise<any> { | ||||
|     protected formatNotificationsData(notifications: AddonNotificationsAnyNotification[], read?: boolean): Promise<any> { | ||||
| 
 | ||||
|         const promises = notifications.map((notification) => { | ||||
| 
 | ||||
|             // Set message to show.
 | ||||
|             if (notification.component && notification.component == 'mod_forum') { | ||||
|                 notification.mobiletext = notification.smallmessage; | ||||
|             } else if (notification.component && notification.component == 'moodle' && notification.name == 'insights') { | ||||
|                 notification.mobiletext = notification.fullmessagehtml; | ||||
|             } else { | ||||
|                 notification.mobiletext = notification.fullmessage; | ||||
|             } | ||||
| @ -117,7 +117,7 @@ export class AddonNotificationsProvider { | ||||
|      * @param siteId Site ID. If not defined, use current site. | ||||
|      * @return Promise resolved with the notification preferences. | ||||
|      */ | ||||
|     getNotificationPreferences(siteId?: string): Promise<any> { | ||||
|     getNotificationPreferences(siteId?: string): Promise<AddonNotificationsNotificationPreferences> { | ||||
|         this.logger.debug('Get notification preferences'); | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| @ -126,7 +126,9 @@ export class AddonNotificationsProvider { | ||||
|                     updateFrequency: CoreSite.FREQUENCY_SOMETIMES | ||||
|             }; | ||||
| 
 | ||||
|             return site.read('core_message_get_user_notification_preferences', {}, preSets).then((data) => { | ||||
|             return site.read('core_message_get_user_notification_preferences', {}, preSets) | ||||
|                     .then((data: AddonNotificationsGetUserNotificationPreferencesResult) => { | ||||
| 
 | ||||
|                 return data.preferences; | ||||
|             }); | ||||
|         }); | ||||
| @ -154,7 +156,7 @@ export class AddonNotificationsProvider { | ||||
|      * @return Promise resolved with notifications. | ||||
|      */ | ||||
|     getNotifications(read: boolean, limitFrom: number, limitNumber: number = 0, toDisplay: boolean = true, | ||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> { | ||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> { | ||||
|         limitNumber = limitNumber || AddonNotificationsProvider.LIST_LIMIT; | ||||
|         this.logger.debug('Get ' + (read ? 'read' : 'unread') + ' notifications from ' + limitFrom + '. Limit: ' + limitNumber); | ||||
| 
 | ||||
| @ -176,7 +178,7 @@ export class AddonNotificationsProvider { | ||||
|             }; | ||||
| 
 | ||||
|             // Get unread notifications.
 | ||||
|             return site.read('core_message_get_messages', data, preSets).then((response) => { | ||||
|             return site.read('core_message_get_messages', data, preSets).then((response: AddonNotificationsGetMessagesResult) => { | ||||
|                 if (response.messages) { | ||||
|                     const notifications = response.messages; | ||||
| 
 | ||||
| @ -209,7 +211,7 @@ export class AddonNotificationsProvider { | ||||
|      * @since 3.2 | ||||
|      */ | ||||
|     getPopupNotifications(offset: number, limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, | ||||
|             siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> { | ||||
|             siteId?: string): Promise<{notifications: AddonNotificationsPopupNotificationFormatted[], canLoadMore: boolean}> { | ||||
| 
 | ||||
|         limit = limit || AddonNotificationsProvider.LIST_LIMIT; | ||||
| 
 | ||||
| @ -230,17 +232,17 @@ export class AddonNotificationsProvider { | ||||
|                 }; | ||||
| 
 | ||||
|             // Get notifications.
 | ||||
|             return site.read('message_popup_get_popup_notifications', data, preSets).then((response) => { | ||||
|             return site.read('message_popup_get_popup_notifications', data, preSets) | ||||
|                     .then((response: AddonNotificationsGetPopupNotificationsResult) => { | ||||
| 
 | ||||
|                 if (response.notifications) { | ||||
|                     const result: any = { | ||||
|                             canLoadMore: response.notifications.length > limit | ||||
|                         }, | ||||
|                         notifications = response.notifications.slice(0, limit); | ||||
|                     const result = { | ||||
|                             canLoadMore: response.notifications.length > limit, | ||||
|                             notifications: response.notifications.slice(0, limit) | ||||
|                         }; | ||||
| 
 | ||||
|                     result.notifications = notifications; | ||||
| 
 | ||||
|                     return this.formatNotificationsData(notifications).then(() => { | ||||
|                         const first = notifications[0]; | ||||
|                     return this.formatNotificationsData(result.notifications).then(() => { | ||||
|                         const first = result.notifications[0]; | ||||
| 
 | ||||
|                         if (this.appProvider.isDesktop() && toDisplay && offset === 0 && first && !first.read) { | ||||
|                             // Store the last received notification. Don't block the user for this.
 | ||||
| @ -269,7 +271,7 @@ export class AddonNotificationsProvider { | ||||
|      * @return Promise resolved with notifications. | ||||
|      */ | ||||
|     getReadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true, | ||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> { | ||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> { | ||||
|         return this.getNotifications(true, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId); | ||||
|     } | ||||
| 
 | ||||
| @ -285,7 +287,7 @@ export class AddonNotificationsProvider { | ||||
|      * @return Promise resolved with notifications. | ||||
|      */ | ||||
|     getUnreadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true, | ||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> { | ||||
|             forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<AddonNotificationsGetMessagesMessage[]> { | ||||
|         return this.getNotifications(false, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId); | ||||
|     } | ||||
| 
 | ||||
| @ -349,7 +351,7 @@ export class AddonNotificationsProvider { | ||||
|      * @return Resolved when done. | ||||
|      * @since 3.2 | ||||
|      */ | ||||
|     markAllNotificationsAsRead(): Promise<any> { | ||||
|     markAllNotificationsAsRead(): Promise<boolean> { | ||||
|         const params = { | ||||
|             useridto: this.sitesProvider.getCurrentSiteUserId() | ||||
|         }; | ||||
| @ -362,10 +364,12 @@ export class AddonNotificationsProvider { | ||||
|      * | ||||
|      * @param notificationId ID of notification to mark as read | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Resolved when done. | ||||
|      * @return Promise resolved when done. | ||||
|      * @since 3.5 | ||||
|      */ | ||||
|     markNotificationRead(notificationId: number, siteId?: string): Promise<any> { | ||||
|     markNotificationRead(notificationId: number, siteId?: string) | ||||
|             : Promise<AddonNotificationsMarkNotificationReadResult | AddonMessagesMarkMessageReadResult> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
|             if (site.wsAvailable('core_message_mark_notification_read')) { | ||||
| @ -436,3 +440,179 @@ export class AddonNotificationsProvider { | ||||
|         return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_user_notification_preferences'); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Preferences returned by core_message_get_user_notification_preferences. | ||||
|  */ | ||||
| export type AddonNotificationsNotificationPreferences = { | ||||
|     userid: number; // User id.
 | ||||
|     disableall: number | boolean; // Whether all the preferences are disabled.
 | ||||
|     processors: AddonNotificationsNotificationPreferencesProcessor[]; // Config form values.
 | ||||
|     components: AddonNotificationsNotificationPreferencesComponent[]; // Available components.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Processor in notification preferences. | ||||
|  */ | ||||
| export type AddonNotificationsNotificationPreferencesProcessor = { | ||||
|     displayname: string; // Display name.
 | ||||
|     name: string; // Processor name.
 | ||||
|     hassettings: boolean; // Whether has settings.
 | ||||
|     contextid: number; // Context id.
 | ||||
|     userconfigured: number; // Whether is configured by the user.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Component in notification preferences. | ||||
|  */ | ||||
| export type AddonNotificationsNotificationPreferencesComponent = { | ||||
|     displayname: string; // Display name.
 | ||||
|     notifications: AddonNotificationsNotificationPreferencesNotification[]; // List of notificaitons for the component.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Notification processor in notification preferences component. | ||||
|  */ | ||||
| export type AddonNotificationsNotificationPreferencesNotification = { | ||||
|     displayname: string; // Display name.
 | ||||
|     preferencekey: string; // Preference key.
 | ||||
|     processors: AddonNotificationsNotificationPreferencesNotificationProcessor[]; // Processors values for this notification.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Notification processor in notification preferences component. | ||||
|  */ | ||||
| export type AddonNotificationsNotificationPreferencesNotificationProcessor = { | ||||
|     displayname: string; // Display name.
 | ||||
|     name: string; // Processor name.
 | ||||
|     locked: boolean; // Is locked by admin?.
 | ||||
|     lockedmessage?: string; // Text to display if locked.
 | ||||
|     userconfigured: number; // Is configured?.
 | ||||
|     loggedin: AddonNotificationsNotificationPreferencesNotificationProcessorState; | ||||
|     loggedoff: AddonNotificationsNotificationPreferencesNotificationProcessorState; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * State in notification processor in notification preferences component. | ||||
|  */ | ||||
| export type AddonNotificationsNotificationPreferencesNotificationProcessorState = { | ||||
|     name: string; // Name.
 | ||||
|     displayname: string; // Display name.
 | ||||
|     checked: boolean; // Is checked?.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_messages. | ||||
|  */ | ||||
| export type AddonNotificationsGetMessagesResult = { | ||||
|     messages: AddonNotificationsGetMessagesMessage[]; | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Message data returned by core_message_get_messages. | ||||
|  */ | ||||
| export type AddonNotificationsGetMessagesMessage = { | ||||
|     id: number; // Message id.
 | ||||
|     useridfrom: number; // User from id.
 | ||||
|     useridto: number; // User to id.
 | ||||
|     subject: string; // The message subject.
 | ||||
|     text: string; // The message text formated.
 | ||||
|     fullmessage: string; // The message.
 | ||||
|     fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     fullmessagehtml: string; // The message in html.
 | ||||
|     smallmessage: string; // The shorten message.
 | ||||
|     notification: number; // Is a notification?.
 | ||||
|     contexturl: string; // Context URL.
 | ||||
|     contexturlname: string; // Context URL link name.
 | ||||
|     timecreated: number; // Time created.
 | ||||
|     timeread: number; // Time read.
 | ||||
|     usertofullname: string; // User to full name.
 | ||||
|     userfromfullname: string; // User from full name.
 | ||||
|     component?: string; // @since 3.7. The component that generated the notification.
 | ||||
|     eventtype?: string; // @since 3.7. The type of notification.
 | ||||
|     customdata?: any; // @since 3.7. Custom data to be passed to the message processor.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Message data returned by core_message_get_messages with some calculated data. | ||||
|  */ | ||||
| export type AddonNotificationsGetMessagesMessageFormatted = | ||||
|         AddonNotificationsGetMessagesMessage & AddonNotificationsNotificationCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS message_popup_get_popup_notifications. | ||||
|  */ | ||||
| export type AddonNotificationsGetPopupNotificationsResult = { | ||||
|     notifications: AddonNotificationsPopupNotification[]; | ||||
|     unreadcount: number; // The number of unread message for the given user.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Notification returned by message_popup_get_popup_notifications. | ||||
|  */ | ||||
| export type AddonNotificationsPopupNotification = { | ||||
|     id: number; // Notification id (this is not guaranteed to be unique within this result set).
 | ||||
|     useridfrom: number; // User from id.
 | ||||
|     useridto: number; // User to id.
 | ||||
|     subject: string; // The notification subject.
 | ||||
|     shortenedsubject: string; // The notification subject shortened with ellipsis.
 | ||||
|     text: string; // The message text formated.
 | ||||
|     fullmessage: string; // The message.
 | ||||
|     fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     fullmessagehtml: string; // The message in html.
 | ||||
|     smallmessage: string; // The shorten message.
 | ||||
|     contexturl: string; // Context URL.
 | ||||
|     contexturlname: string; // Context URL link name.
 | ||||
|     timecreated: number; // Time created.
 | ||||
|     timecreatedpretty: string; // Time created in a pretty format.
 | ||||
|     timeread: number; // Time read.
 | ||||
|     read: boolean; // Notification read status.
 | ||||
|     deleted: boolean; // Notification deletion status.
 | ||||
|     iconurl: string; // URL for notification icon.
 | ||||
|     component?: string; // The component that generated the notification.
 | ||||
|     eventtype?: string; // The type of notification.
 | ||||
|     customdata?: any; // @since 3.7. Custom data to be passed to the message processor.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Notification returned by message_popup_get_popup_notifications. | ||||
|  */ | ||||
| export type AddonNotificationsPopupNotificationFormatted = | ||||
|         AddonNotificationsPopupNotification & AddonNotificationsNotificationCalculatedData; | ||||
| 
 | ||||
| /** | ||||
|  * Any kind of notification that can be retrieved. | ||||
|  */ | ||||
| export type AddonNotificationsAnyNotification = | ||||
|         AddonNotificationsPopupNotificationFormatted | AddonNotificationsGetMessagesMessageFormatted; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_get_user_notification_preferences. | ||||
|  */ | ||||
| export type AddonNotificationsGetUserNotificationPreferencesResult = { | ||||
|     preferences: AddonNotificationsNotificationPreferences; | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS core_message_mark_notification_read. | ||||
|  */ | ||||
| export type AddonNotificationsMarkNotificationReadResult = { | ||||
|     notificationid: number; // Id of the notification.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Calculated data for messages returned by core_message_get_messages. | ||||
|  */ | ||||
| export type AddonNotificationsNotificationCalculatedData = { | ||||
|     mobiletext?: string; // Calculated in the app. Text to display for the notification.
 | ||||
|     moodlecomponent?: string; // Calculated in the app. Moodle's component.
 | ||||
|     notif?: number; // Calculated in the app. Whether it's a notification.
 | ||||
|     notification?: number; // Calculated in the app in some cases. Whether it's a notification.
 | ||||
|     read?: boolean; // Calculated in the app. Whether the notifications is read.
 | ||||
|     courseid?: number; // Calculated in the app. Course the notification belongs to.
 | ||||
|     profileimageurlfrom?: string; // Calculated in the app. Avatar of user that sent the notification.
 | ||||
|     userfromfullname?: string; // Calculated in the app in some cases. User from full name.
 | ||||
| }; | ||||
|  | ||||
| @ -407,3 +407,27 @@ export class CoreCommentsProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by comment_area_exporter. | ||||
|  */ | ||||
| export type CoreCommentsArea = { | ||||
|     component: string; // Component.
 | ||||
|     commentarea: string; // Commentarea.
 | ||||
|     itemid: number; // Itemid.
 | ||||
|     courseid: number; // Courseid.
 | ||||
|     contextid: number; // Contextid.
 | ||||
|     cid: string; // Cid.
 | ||||
|     autostart: boolean; // Autostart.
 | ||||
|     canpost: boolean; // Canpost.
 | ||||
|     canview: boolean; // Canview.
 | ||||
|     count: number; // Count.
 | ||||
|     collapsediconkey: string; // Collapsediconkey.
 | ||||
|     displaytotalcount: boolean; // Displaytotalcount.
 | ||||
|     displaycancel: boolean; // Displaycancel.
 | ||||
|     fullwidth: boolean; // Fullwidth.
 | ||||
|     linktext: string; // Linktext.
 | ||||
|     notoggle: boolean; // Notoggle.
 | ||||
|     template: string; // Template.
 | ||||
|     canpostorhascomments: boolean; // Canpostorhascomments.
 | ||||
| }; | ||||
|  | ||||
| @ -1135,3 +1135,38 @@ export class CoreCourseProvider { | ||||
|         }, siteId); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by course_summary_exporter. | ||||
|  */ | ||||
| export type CoreCourseSummary = { | ||||
|     id: number; // Id.
 | ||||
|     fullname: string; // Fullname.
 | ||||
|     shortname: string; // Shortname.
 | ||||
|     idnumber: string; // Idnumber.
 | ||||
|     summary: string; // Summary.
 | ||||
|     summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     startdate: number; // Startdate.
 | ||||
|     enddate: number; // Enddate.
 | ||||
|     visible: boolean; // Visible.
 | ||||
|     fullnamedisplay: string; // Fullnamedisplay.
 | ||||
|     viewurl: string; // Viewurl.
 | ||||
|     courseimage: string; // Courseimage.
 | ||||
|     progress?: number; // Progress.
 | ||||
|     hasprogress: boolean; // Hasprogress.
 | ||||
|     isfavourite: boolean; // Isfavourite.
 | ||||
|     hidden: boolean; // Hidden.
 | ||||
|     timeaccess?: number; // Timeaccess.
 | ||||
|     showshortname: boolean; // Showshortname.
 | ||||
|     coursecategory: string; // Coursecategory.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by course_module_summary_exporter. | ||||
|  */ | ||||
| export type CoreCourseModuleSummary = { | ||||
|     id: number; // Id.
 | ||||
|     name: string; // Name.
 | ||||
|     url?: string; // Url.
 | ||||
|     iconurl: string; // Iconurl.
 | ||||
| }; | ||||
|  | ||||
| @ -17,74 +17,6 @@ import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||
| 
 | ||||
| /** | ||||
|  * Structure of a tag cloud returned by WS. | ||||
|  */ | ||||
| export interface CoreTagCloud { | ||||
|     tags: CoreTagCloudTag[]; | ||||
|     tagscount: number; | ||||
|     totalcount: number; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Structure of a tag cloud tag returned by WS. | ||||
|  */ | ||||
| export interface CoreTagCloudTag { | ||||
|     name: string; | ||||
|     viewurl: string; | ||||
|     flag: boolean; | ||||
|     isstandard: boolean; | ||||
|     count: number; | ||||
|     size: number; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Structure of a tag collection returned by WS. | ||||
|  */ | ||||
| export interface CoreTagCollection { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     isdefault: boolean; | ||||
|     component: string; | ||||
|     sortoder: number; | ||||
|     searchable: boolean; | ||||
|     customurl: string; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Structure of a tag index returned by WS. | ||||
|  */ | ||||
| export interface 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 interface CoreTagItem { | ||||
|     id: number; | ||||
|     name: string; | ||||
|     rawname: string; | ||||
|     isstandard: boolean; | ||||
|     tagcollid: number; | ||||
|     taginstanceid: number; | ||||
|     taginstancecontextid: number; | ||||
|     itemid: number; | ||||
|     ordering: number; | ||||
|     flag: number; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle tags. | ||||
|  */ | ||||
| @ -343,3 +275,71 @@ export class CoreTagProvider { | ||||
|             + contextId + ':' +  (recursive ? 1 : 0); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 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; | ||||
|     name: string; | ||||
|     isdefault: boolean; | ||||
|     component: string; | ||||
|     sortoder: number; | ||||
|     searchable: boolean; | ||||
|     customurl: string; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 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; | ||||
| }; | ||||
|  | ||||
| @ -634,3 +634,21 @@ export class CoreUserProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by user_summary_exporter. | ||||
|  */ | ||||
| export type CoreUserSummary = { | ||||
|     id: number; // Id.
 | ||||
|     email: string; // Email.
 | ||||
|     idnumber: string; // Idnumber.
 | ||||
|     phone1: string; // Phone1.
 | ||||
|     phone2: string; // Phone2.
 | ||||
|     department: string; // Department.
 | ||||
|     institution: string; // Institution.
 | ||||
|     fullname: string; // Fullname.
 | ||||
|     identity: string; // Identity.
 | ||||
|     profileurl: string; // Profileurl.
 | ||||
|     profileimageurl: string; // Profileimageurl.
 | ||||
|     profileimageurlsmall: string; // Profileimageurlsmall.
 | ||||
| }; | ||||
|  | ||||
| @ -81,126 +81,6 @@ export interface CoreWSAjaxPreSets { | ||||
|     useGet?: boolean; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Error returned by a WS call. | ||||
|  */ | ||||
| export interface CoreWSError { | ||||
|     /** | ||||
|      * The error message. | ||||
|      */ | ||||
|     message: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Name of the exception. Undefined for local errors (fake WS errors). | ||||
|      */ | ||||
|     exception?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The error code. Undefined for local errors (fake WS errors). | ||||
|      */ | ||||
|     errorcode?: string; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * File upload options. | ||||
|  */ | ||||
| export interface CoreWSFileUploadOptions extends FileUploadOptions { | ||||
|     /** | ||||
|      * The file area where to put the file. By default, 'draft'. | ||||
|      */ | ||||
|     fileArea?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Item ID of the area where to put the file. By default, 0. | ||||
|      */ | ||||
|     itemId?: number; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Structure of warnings returned by WS. | ||||
|  */ | ||||
| export type CoreWSExternalWarning = { | ||||
|     /** | ||||
|      * Item. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     item?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Item id. | ||||
|      * @type {number} | ||||
|      */ | ||||
|     itemid?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * The warning code can be used by the client app to implement specific behaviour. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     warningcode: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Untranslated english message to explain the warning. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     message: string; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Structure of files returned by WS. | ||||
|  */ | ||||
| export type CoreWSExternalFile = { | ||||
|     /** | ||||
|      * File name. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     filename?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * File path. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     filepath?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * File size. | ||||
|      * @type {number} | ||||
|      */ | ||||
|     filesize?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * Downloadable file url. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     fileurl?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Time modified. | ||||
|      * @type {number} | ||||
|      */ | ||||
|     timemodified?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * File mime type. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     mimetype?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether is an external file. | ||||
|      * @type {number} | ||||
|      */ | ||||
|     isexternalfile?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * The repository type for external files. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     repositorytype?: string; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * This service allows performing WS calls and download/upload files. | ||||
|  */ | ||||
| @ -948,3 +828,127 @@ export class CoreWSProvider { | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Error returned by a WS call. | ||||
|  */ | ||||
| export interface CoreWSError { | ||||
|     /** | ||||
|      * The error message. | ||||
|      */ | ||||
|     message: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Name of the exception. Undefined for local errors (fake WS errors). | ||||
|      */ | ||||
|     exception?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The error code. Undefined for local errors (fake WS errors). | ||||
|      */ | ||||
|     errorcode?: string; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * File upload options. | ||||
|  */ | ||||
| export interface CoreWSFileUploadOptions extends FileUploadOptions { | ||||
|     /** | ||||
|      * The file area where to put the file. By default, 'draft'. | ||||
|      */ | ||||
|     fileArea?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Item ID of the area where to put the file. By default, 0. | ||||
|      */ | ||||
|     itemId?: number; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Structure of warnings returned by WS. | ||||
|  */ | ||||
| export type CoreWSExternalWarning = { | ||||
|     /** | ||||
|      * Item. | ||||
|      */ | ||||
|     item?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Item id. | ||||
|      */ | ||||
|     itemid?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * The warning code can be used by the client app to implement specific behaviour. | ||||
|      */ | ||||
|     warningcode: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Untranslated english message to explain the warning. | ||||
|      */ | ||||
|     message: string; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Structure of files returned by WS. | ||||
|  */ | ||||
| export type CoreWSExternalFile = { | ||||
|     /** | ||||
|      * File name. | ||||
|      */ | ||||
|     filename?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * File path. | ||||
|      */ | ||||
|     filepath?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * File size. | ||||
|      */ | ||||
|     filesize?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * Downloadable file url. | ||||
|      */ | ||||
|     fileurl?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Time modified. | ||||
|      */ | ||||
|     timemodified?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * File mime type. | ||||
|      */ | ||||
|     mimetype?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether is an external file. | ||||
|      */ | ||||
|     isexternalfile?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * The repository type for external files. | ||||
|      */ | ||||
|     repositorytype?: string; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by date_exporter. | ||||
|  */ | ||||
| export type CoreWSDate = { | ||||
|     seconds: number; // Seconds.
 | ||||
|     minutes: number; // Minutes.
 | ||||
|     hours: number; // Hours.
 | ||||
|     mday: number; // Mday.
 | ||||
|     wday: number; // Wday.
 | ||||
|     mon: number; // Mon.
 | ||||
|     year: number; // Year.
 | ||||
|     yday: number; // Yday.
 | ||||
|     weekday: string; // Weekday.
 | ||||
|     month: string; // Month.
 | ||||
|     timestamp: number; // Timestamp.
 | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user