commit
7d819264d7
|
@ -546,12 +546,12 @@ export class AddonCalendarHelperProvider {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
const fetchTimestarts: number[] = [];
|
const fetchTimestarts: number[] = [];
|
||||||
const invalidateTimestarts: number[] = [];
|
const invalidateTimestarts: number[] = [];
|
||||||
const promises: Promise<unknown>[] = [];
|
let promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
// Always fetch upcoming events.
|
// Always fetch upcoming events.
|
||||||
promises.push(AddonCalendar.getUpcomingEvents(undefined, undefined, true, site.id));
|
promises.push(AddonCalendar.getUpcomingEvents(undefined, undefined, true, site.id));
|
||||||
|
|
||||||
promises.concat(events.map(async (eventData) => {
|
promises = promises.concat(events.map(async (eventData) => {
|
||||||
|
|
||||||
if (eventData.repeated <= 1) {
|
if (eventData.repeated <= 1) {
|
||||||
// Not repeated.
|
// Not repeated.
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { CoreWSExternalWarning } from '@services/ws';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { AddonMessagesSyncEvents, AddonMessagesSyncProvider } from './messages-sync';
|
import { AddonMessagesSyncEvents, AddonMessagesSyncProvider } from './messages-sync';
|
||||||
|
import { CoreWSError } from '@classes/errors/wserror';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmaMessages:';
|
const ROOT_CACHE_KEY = 'mmaMessages:';
|
||||||
|
|
||||||
|
@ -191,7 +192,14 @@ export class AddonMessagesProvider {
|
||||||
requesteduserid: userId,
|
requesteduserid: userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
await site.write('core_message_create_contact_request', params);
|
const result = await site.write<AddonMessagesCreateContactRequestWSResponse>(
|
||||||
|
'core_message_create_contact_request',
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.warnings?.length) {
|
||||||
|
throw new CoreWSError(result.warnings[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.invalidateAllMemberInfo(userId, site).finally(() => {
|
await this.invalidateAllMemberInfo(userId, site).finally(() => {
|
||||||
|
@ -3483,6 +3491,19 @@ type AddonMessagesConfirmContactRequestWSParams = {
|
||||||
*/
|
*/
|
||||||
type AddonMessagesCreateContactRequestWSParams = AddonMessagesConfirmContactRequestWSParams;
|
type AddonMessagesCreateContactRequestWSParams = AddonMessagesConfirmContactRequestWSParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by core_message_create_contact_request WS.
|
||||||
|
*/
|
||||||
|
export type AddonMessagesCreateContactRequestWSResponse = {
|
||||||
|
request?: {
|
||||||
|
id: number; // Message id.
|
||||||
|
userid: number; // User from id.
|
||||||
|
requesteduserid: number; // User to id.
|
||||||
|
timecreated: number; // Time created.
|
||||||
|
}; // Request record.
|
||||||
|
warnings?: CoreWSExternalWarning[];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params of core_message_decline_contact_request WS.
|
* Params of core_message_decline_contact_request WS.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -48,13 +48,13 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
|
||||||
try {
|
try {
|
||||||
const forum = await AddonModForum.getForum(courseId, module.id);
|
const forum = await AddonModForum.getForum(courseId, module.id);
|
||||||
|
|
||||||
const files = this.getIntroFilesFromInstance(module, forum);
|
let files = this.getIntroFilesFromInstance(module, forum);
|
||||||
|
|
||||||
// Get posts.
|
// Get posts.
|
||||||
const posts = await this.getPostsForPrefetch(forum, { cmId: module.id });
|
const posts = await this.getPostsForPrefetch(forum, { cmId: module.id });
|
||||||
|
|
||||||
// Add posts attachments and embedded files.
|
// Add posts attachments and embedded files.
|
||||||
files.concat(this.getPostsFiles(posts));
|
files = files.concat(this.getPostsFiles(posts));
|
||||||
|
|
||||||
return files;
|
return files;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -573,7 +573,6 @@ class AddonModGlossaryEntriesManager extends CorePageItemsListManager<EntryItem>
|
||||||
*/
|
*/
|
||||||
setOnlineEntries(onlineEntries: AddonModGlossaryEntry[], hasMoreItems: boolean = false): void {
|
setOnlineEntries(onlineEntries: AddonModGlossaryEntry[], hasMoreItems: boolean = false): void {
|
||||||
this.setItems((<EntryItem[]> this.offlineEntries).concat(onlineEntries), hasMoreItems);
|
this.setItems((<EntryItem[]> this.offlineEntries).concat(onlineEntries), hasMoreItems);
|
||||||
this.onlineEntries.concat(onlineEntries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -583,7 +582,6 @@ class AddonModGlossaryEntriesManager extends CorePageItemsListManager<EntryItem>
|
||||||
*/
|
*/
|
||||||
setOfflineEntries(offlineEntries: AddonModGlossaryOfflineEntry[]): void {
|
setOfflineEntries(offlineEntries: AddonModGlossaryOfflineEntry[]): void {
|
||||||
this.setItems((<EntryItem[]> offlineEntries).concat(this.onlineEntries), this.hasMoreItems);
|
this.setItems((<EntryItem[]> offlineEntries).concat(this.onlineEntries), this.hasMoreItems);
|
||||||
this.offlineEntries = offlineEntries;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,11 +19,10 @@
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label></ion-label>
|
||||||
<ion-textarea placeholder="{{ 'addon.notes.note' | translate }}" rows="5" [(ngModel)]="text" name="text"
|
<ion-textarea placeholder="{{ 'addon.notes.note' | translate }}" rows="5" [(ngModel)]="text" name="text"
|
||||||
required="required">
|
required="required">
|
||||||
</ion-textarea>
|
</ion-textarea>
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<div class="ion-padding">
|
<div class="ion-padding">
|
||||||
<ion-button expand="block" type="submit" [disabled]="processing || text.length < 2">
|
<ion-button expand="block" type="submit" [disabled]="processing || text.length < 2">
|
||||||
|
|
|
@ -62,12 +62,10 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider<AddonNotesSyncR
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Get all the courses to be synced.
|
// Get all the courses to be synced.
|
||||||
const courseIds: number[] = [];
|
let courseIds: number[] = [];
|
||||||
notesArray.forEach((notes: (AddonNotesDeletedDBRecord | AddonNotesDBRecord)[]) => {
|
notesArray.forEach((notes: (AddonNotesDeletedDBRecord | AddonNotesDBRecord)[]) => {
|
||||||
const courseIds = notes.map((note) => note.courseid);
|
courseIds = courseIds.concat(notes.map((note) => note.courseid));
|
||||||
|
});
|
||||||
courseIds.concat(courseIds);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
CoreUtils.uniqueArray(courseIds);
|
CoreUtils.uniqueArray(courseIds);
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,8 @@ const preferencesRoutes: Routes = [
|
||||||
CoreCronDelegate.register(AddonNotificationsCronHandler.instance);
|
CoreCronDelegate.register(AddonNotificationsCronHandler.instance);
|
||||||
CorePushNotificationsDelegate.registerClickHandler(AddonNotificationsPushClickHandler.instance);
|
CorePushNotificationsDelegate.registerClickHandler(AddonNotificationsPushClickHandler.instance);
|
||||||
CoreSettingsDelegate.registerHandler(AddonNotificationsSettingsHandler.instance);
|
CoreSettingsDelegate.registerHandler(AddonNotificationsSettingsHandler.instance);
|
||||||
|
|
||||||
|
AddonNotificationsMainMenuHandler.initialize();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -132,16 +132,21 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
// Check InAppBrowser closed.
|
// Check InAppBrowser closed.
|
||||||
CoreEvents.on(CoreEvents.IAB_EXIT, () => {
|
CoreEvents.on(CoreEvents.IAB_EXIT, () => {
|
||||||
CoreLoginHelper.setWaitingForBrowser(false);
|
|
||||||
this.lastInAppUrl = '';
|
this.lastInAppUrl = '';
|
||||||
CoreLoginHelper.checkLogout();
|
|
||||||
|
if (CoreLoginHelper.isWaitingForBrowser()) {
|
||||||
|
CoreLoginHelper.setWaitingForBrowser(false);
|
||||||
|
CoreLoginHelper.checkLogout();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Platform.resume.subscribe(() => {
|
Platform.resume.subscribe(() => {
|
||||||
// Wait a second before setting it to false since in iOS there could be some frozen WS calls.
|
// Wait a second before setting it to false since in iOS there could be some frozen WS calls.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
CoreLoginHelper.setWaitingForBrowser(false);
|
if (CoreLoginHelper.isWaitingForBrowser()) {
|
||||||
CoreLoginHelper.checkLogout();
|
CoreLoginHelper.setWaitingForBrowser(false);
|
||||||
|
CoreLoginHelper.checkLogout();
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -247,7 +247,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
|
|
||||||
await this.calculateMaxSlides();
|
await this.calculateMaxSlides();
|
||||||
|
|
||||||
this.updateSlides();
|
await this.updateSlides();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -378,9 +378,14 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
}
|
}
|
||||||
|
|
||||||
this.maxSlides = 3;
|
this.maxSlides = 3;
|
||||||
const width = this.slidesSwiper.width;
|
let width = this.slidesSwiper.width;
|
||||||
if (!width) {
|
if (!width) {
|
||||||
return;
|
this.slidesSwiper.updateSize();
|
||||||
|
width = this.slidesSwiper.width;
|
||||||
|
|
||||||
|
if (!width) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomLevel = await CoreSettingsHelper.getZoom();
|
const zoomLevel = await CoreSettingsHelper.getZoom();
|
||||||
|
@ -619,7 +624,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
||||||
protected windowResized(): void {
|
protected windowResized(): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.calculateSlides();
|
this.calculateSlides();
|
||||||
});
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
<ng-container *ngFor="let tab of tabs">
|
<ng-container *ngFor="let tab of tabs">
|
||||||
<ion-slide
|
<ion-slide
|
||||||
role="presentation"
|
role="presentation"
|
||||||
[hidden]="!hideUntil"
|
|
||||||
[id]="tab.id! + '-tab'"
|
[id]="tab.id! + '-tab'"
|
||||||
class="tab-slide"
|
class="tab-slide"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
Optional,
|
Optional,
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { IonContent } from '@ionic/angular';
|
import { IonContent } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
@ -90,6 +91,7 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
element: ElementRef,
|
element: ElementRef,
|
||||||
@Optional() protected content: IonContent,
|
@Optional() protected content: IonContent,
|
||||||
protected viewContainerRef: ViewContainerRef,
|
protected viewContainerRef: ViewContainerRef,
|
||||||
|
protected sanitizer: DomSanitizer,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.element = element.nativeElement;
|
this.element = element.nativeElement;
|
||||||
|
@ -504,7 +506,7 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
// Important: We need to look for links first because in 'img' we add new links without core-link.
|
// Important: We need to look for links first because in 'img' we add new links without core-link.
|
||||||
anchors.forEach((anchor) => {
|
anchors.forEach((anchor) => {
|
||||||
// Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually.
|
// Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually.
|
||||||
const linkDir = new CoreLinkDirective(new ElementRef(anchor), this.content);
|
const linkDir = new CoreLinkDirective(new ElementRef(anchor), this.content, this.sanitizer);
|
||||||
linkDir.capture = this.captureLinks ?? true;
|
linkDir.capture = this.captureLinks ?? true;
|
||||||
linkDir.inApp = this.openLinksInApp;
|
linkDir.inApp = this.openLinksInApp;
|
||||||
linkDir.ngOnInit();
|
linkDir.ngOnInit();
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Directive, Input, OnInit, ElementRef, Optional } from '@angular/core';
|
import { Directive, Input, OnInit, ElementRef, Optional, SecurityContext } from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
import { IonContent } from '@ionic/angular';
|
import { IonContent } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreFileHelper } from '@services/file-helper';
|
import { CoreFileHelper } from '@services/file-helper';
|
||||||
|
@ -33,7 +34,7 @@ import { CoreCustomURLSchemes } from '@services/urlschemes';
|
||||||
})
|
})
|
||||||
export class CoreLinkDirective implements OnInit {
|
export class CoreLinkDirective implements OnInit {
|
||||||
|
|
||||||
@Input() href?: string; // Link URL.
|
@Input() href?: string | SafeUrl; // Link URL.
|
||||||
@Input() capture?: boolean | string; // If the link needs to be captured by the app.
|
@Input() capture?: boolean | string; // If the link needs to be captured by the app.
|
||||||
@Input() inApp?: boolean | string; // True to open in embedded browser, false to open in system browser.
|
@Input() inApp?: boolean | string; // True to open in embedded browser, false to open in system browser.
|
||||||
/* Whether the link should be opened with auto-login. Accepts the following values:
|
/* Whether the link should be opened with auto-login. Accepts the following values:
|
||||||
|
@ -47,6 +48,7 @@ export class CoreLinkDirective implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
element: ElementRef,
|
element: ElementRef,
|
||||||
@Optional() protected content: IonContent,
|
@Optional() protected content: IonContent,
|
||||||
|
protected sanitizer: DomSanitizer,
|
||||||
) {
|
) {
|
||||||
this.element = element.nativeElement;
|
this.element = element.nativeElement;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +93,13 @@ export class CoreLinkDirective implements OnInit {
|
||||||
return; // Link already treated, stop.
|
return; // Link already treated, stop.
|
||||||
}
|
}
|
||||||
|
|
||||||
let href = this.href || this.element.getAttribute('href') || this.element.getAttribute('xlink:href');
|
let href: string | null = null;
|
||||||
|
if (this.href) {
|
||||||
|
// Convert the URL back to string if needed.
|
||||||
|
href = typeof this.href === 'string' ? this.href : this.sanitizer.sanitize(SecurityContext.URL, this.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
href = href || this.element.getAttribute('href') || this.element.getAttribute('xlink:href');
|
||||||
|
|
||||||
if (!href || CoreUrlUtils.getUrlScheme(href) == 'javascript') {
|
if (!href || CoreUrlUtils.getUrlScheme(href) == 'javascript') {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function buildTabMainRoutes(injector: Injector, mainRoute: Route): Routes
|
||||||
|
|
||||||
mainRoute.path = mainRoute.path || '';
|
mainRoute.path = mainRoute.path || '';
|
||||||
mainRoute.children = mainRoute.children || [];
|
mainRoute.children = mainRoute.children || [];
|
||||||
mainRoute.children.concat(routes.children);
|
mainRoute.children = mainRoute.children.concat(routes.children);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
mainRoute,
|
mainRoute,
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<core-shared-files-list [siteId]="siteId" [mimetypes]="mimetypes" [isModal]="true" [manage]="manage" [pick]="pick"
|
<core-shared-files-list [siteId]="siteId" [mimetypes]="mimetypes" [isModal]="true" [manage]="manage" [pick]="pick"
|
||||||
[path]="path" [showSitePicker]="showSitePicker" (onPathChanged)="calculateTitle($event)" (onFilePicked)="filePicked($event)">
|
[path]="path" [showSitePicker]="!hideSitePicker" (onPathChanged)="calculateTitle($event)"
|
||||||
|
(onFilePicked)="filePicked($event)">
|
||||||
</core-shared-files-list>
|
</core-shared-files-list>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class CoreSharedFilesListModalComponent implements OnInit {
|
||||||
@Input() manage?: boolean;
|
@Input() manage?: boolean;
|
||||||
@Input() pick?: boolean; // To pick a file you MUST use a modal.
|
@Input() pick?: boolean; // To pick a file you MUST use a modal.
|
||||||
@Input() path?: string;
|
@Input() path?: string;
|
||||||
@Input() showSitePicker?: boolean;
|
@Input() hideSitePicker?: boolean;
|
||||||
|
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class CoreTagListComponent {
|
||||||
fromContextId: tag.taginstancecontextid,
|
fromContextId: tag.taginstancecontextid,
|
||||||
};
|
};
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath('/tag/index', { params, preferCurrentTab: false });
|
CoreNavigator.navigateToSitePath('/tag/index', { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,21 +38,21 @@
|
||||||
</a></p>
|
</a></p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap" *ngIf="user.address">
|
<ion-item class="ion-text-wrap" *ngIf="formattedAddress">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.user.address' | translate}}</h2>
|
<h2>{{ 'core.user.address' | translate}}</h2>
|
||||||
<p><a class="core-anchor" [href]="encodedAddress" core-link auto-login="no">
|
<p><a class="core-anchor" [href]="encodedAddress" core-link auto-login="no">
|
||||||
{{ user.address }}
|
{{ formattedAddress }}
|
||||||
</a></p>
|
</a></p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap" *ngIf="user.city && !user.address">
|
<ion-item class="ion-text-wrap" *ngIf="user.city && !formattedAddress">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.user.city' | translate}}</h2>
|
<h2>{{ 'core.user.city' | translate}}</h2>
|
||||||
<p>{{ user.city }}</p>
|
<p>{{ user.city }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap" *ngIf="user.country && !user.address">
|
<ion-item class="ion-text-wrap" *ngIf="user.country && !formattedAddress">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.user.country' | translate}}</h2>
|
<h2>{{ 'core.user.country' | translate}}</h2>
|
||||||
<p>{{ user.country }}</p>
|
<p>{{ user.country }}</p>
|
||||||
|
|
|
@ -75,7 +75,7 @@ export class CoreUserAboutPage implements OnInit {
|
||||||
|
|
||||||
if (user.address) {
|
if (user.address) {
|
||||||
this.formattedAddress = CoreUserHelper.formatAddress(user.address, user.city, user.country);
|
this.formattedAddress = CoreUserHelper.formatAddress(user.address, user.city, user.country);
|
||||||
this.encodedAddress = CoreTextUtils.buildAddressURL(user.address);
|
this.encodedAddress = CoreTextUtils.buildAddressURL(this.formattedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasContact = !!(user.email || user.phone1 || user.phone2 || user.city || user.country || user.address);
|
this.hasContact = !!(user.email || user.phone1 || user.phone2 || user.city || user.country || user.address);
|
||||||
|
|
Loading…
Reference in New Issue