Merge pull request #2807 from dpalou/MOBILE-3320

Mobile 3320
main
Pau Ferrer Ocaña 2021-06-03 16:35:22 +02:00 committed by GitHub
commit 7d819264d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 77 additions and 39 deletions

View File

@ -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.

View File

@ -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.
*/ */

View File

@ -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) {

View File

@ -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;
} }
/** /**

View File

@ -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">

View File

@ -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);

View File

@ -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();
}, },
}, },
], ],

View File

@ -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);
}); });

View File

@ -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);
} }
/** /**

View File

@ -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"

View File

@ -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();

View File

@ -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;

View File

@ -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,

View File

@ -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>

View File

@ -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;

View File

@ -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 });
} }
} }

View File

@ -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>

View File

@ -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);