commit
7dd67a99b9
|
@ -30,8 +30,8 @@ npm-debug.log*
|
||||||
/www
|
/www
|
||||||
/src/assets/lib
|
/src/assets/lib
|
||||||
|
|
||||||
/moodle.*.config.json
|
/moodle.config.*.json
|
||||||
!/moodle.example.config.json
|
!/moodle.config.example.json
|
||||||
|
|
||||||
/src/assets/lang/*
|
/src/assets/lang/*
|
||||||
/src/assets/env.json
|
/src/assets/env.json
|
||||||
|
|
|
@ -37,6 +37,41 @@
|
||||||
],
|
],
|
||||||
"description": "[Moodle] Create a Page class"
|
"description": "[Moodle] Create a Page class"
|
||||||
},
|
},
|
||||||
|
"[Moodle] Module class": {
|
||||||
|
"scope": "typescript",
|
||||||
|
"prefix": "mamodule",
|
||||||
|
"body": [
|
||||||
|
"import { NgModule } from '@angular/core';",
|
||||||
|
"",
|
||||||
|
"@NgModule({",
|
||||||
|
" $0",
|
||||||
|
"})",
|
||||||
|
"export class ${1}Module {}",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "[Moodle] Create a Module class"
|
||||||
|
},
|
||||||
|
"[Moodle] Lazy Module class": {
|
||||||
|
"scope": "typescript",
|
||||||
|
"prefix": "malazymodule",
|
||||||
|
"body": [
|
||||||
|
"import { NgModule } from '@angular/core';",
|
||||||
|
"import { RouterModule, Routes } from '@angular/router';",
|
||||||
|
"",
|
||||||
|
"const routes: Routes = [",
|
||||||
|
" $0",
|
||||||
|
"];",
|
||||||
|
"",
|
||||||
|
"@NgModule({",
|
||||||
|
" imports: [",
|
||||||
|
" RouterModule.forChild(routes),",
|
||||||
|
" ],",
|
||||||
|
"})",
|
||||||
|
"export class ${1}LazyModule {}",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "[Moodle] Create a Lazy Module class"
|
||||||
|
},
|
||||||
"[Moodle] Service Singleton": {
|
"[Moodle] Service Singleton": {
|
||||||
"scope": "typescript",
|
"scope": "typescript",
|
||||||
"prefix": "massingleton",
|
"prefix": "massingleton",
|
||||||
|
@ -74,5 +109,5 @@
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"description": "[Moodle] Create a Pure Singleton"
|
"description": "[Moodle] Create a Pure Singleton"
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"moodle.config.json": "jsonc",
|
"moodle.config.json": "jsonc",
|
||||||
"moodle.*.config.json": "jsonc",
|
"moodle.config.*.json": "jsonc",
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ function getConfig(environment) {
|
||||||
};
|
};
|
||||||
const config = parseJsonc(readFileSync(resolve(__dirname, '../moodle.config.json')).toString());
|
const config = parseJsonc(readFileSync(resolve(__dirname, '../moodle.config.json')).toString());
|
||||||
const envSuffixes = (envSuffixesMap[environment] || []);
|
const envSuffixes = (envSuffixesMap[environment] || []);
|
||||||
const envConfigPath = envSuffixes.map(suffix => resolve(__dirname, `../moodle.${suffix}.config.json`)).find(existsSync);
|
const envConfigPath = envSuffixes.map(suffix => resolve(__dirname, `../moodle.config.${suffix}.json`)).find(existsSync);
|
||||||
|
|
||||||
if (envConfigPath) {
|
if (envConfigPath) {
|
||||||
const envConfig = parseJsonc(readFileSync(envConfigPath).toString());
|
const envConfig = parseJsonc(readFileSync(envConfigPath).toString());
|
||||||
|
|
|
@ -48,5 +48,5 @@ gulp.task('default', gulp.parallel(['lang', 'env']));
|
||||||
|
|
||||||
gulp.task('watch', () => {
|
gulp.task('watch', () => {
|
||||||
gulp.watch(paths.lang, { interval: 500 }, gulp.parallel('lang'));
|
gulp.watch(paths.lang, { interval: 500 }, gulp.parallel('lang'));
|
||||||
gulp.watch(['./moodle.config.json', './moodle.*.config.json'], { interval: 500 }, gulp.parallel('env'));
|
gulp.watch(['./moodle.config.json', './moodle.config.*.json'], { interval: 500 }, gulp.parallel('env'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -380,7 +380,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill user data for Offline discussions (should be already cached).
|
// Fill user data for Offline discussions (should be already cached).
|
||||||
const promises = offlineDiscussions.map(async (discussion: any) => {
|
const promises = offlineDiscussions.map(async (offlineDiscussion) => {
|
||||||
|
const discussion = offlineDiscussion as unknown as AddonModForumDiscussion;
|
||||||
|
|
||||||
if (discussion.parent === 0 || forum.type === 'single') {
|
if (discussion.parent === 0 || forum.type === 'single') {
|
||||||
// Do not show author for first post and type single.
|
// Do not show author for first post and type single.
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -71,8 +71,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
||||||
@Input() discussion?: AddonModForumDiscussion; // Post's' discussion, only for starting posts.
|
@Input() discussion?: AddonModForumDiscussion; // Post's' discussion, only for starting posts.
|
||||||
@Input() component!: string; // Component this post belong to.
|
@Input() component!: string; // Component this post belong to.
|
||||||
@Input() componentId!: number; // Component ID.
|
@Input() componentId!: number; // Component ID.
|
||||||
@Input() replyData: any; // Object with the new post data. Usually shared between posts.
|
@Input() replyData!: AddonModForumReply; // Object with the new post data. Usually shared between posts.
|
||||||
@Input() originalData: any; // Object with the original post data. Usually shared between posts.
|
@Input() originalData!: Omit<AddonModForumReply, 'id'>; // Object with the original post data. Usually shared between posts.
|
||||||
@Input() trackPosts!: boolean; // True if post is being tracked.
|
@Input() trackPosts!: boolean; // True if post is being tracked.
|
||||||
@Input() forum!: AddonModForumData; // The forum the post belongs to. Required for attachments and offline posts.
|
@Input() forum!: AddonModForumData; // The forum the post belongs to. Required for attachments and offline posts.
|
||||||
@Input() accessInfo!: AddonModForumAccessInformation; // Forum access information.
|
@Input() accessInfo!: AddonModForumAccessInformation; // Forum access information.
|
||||||
|
@ -103,7 +103,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
||||||
get showForm(): boolean {
|
get showForm(): boolean {
|
||||||
return this.post.id > 0
|
return this.post.id > 0
|
||||||
? !this.replyData.isEditing && this.replyData.replyingTo === this.post.id
|
? !this.replyData.isEditing && this.replyData.replyingTo === this.post.id
|
||||||
: this.replyData.isEditing && this.replyData.replyingTo === this.post.parentid;
|
: !!this.replyData.isEditing && this.replyData.replyingTo === this.post.parentid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -275,7 +275,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add some HTML to the message if needed.
|
// Add some HTML to the message if needed.
|
||||||
const message = CoreTextUtils.formatHtmlLines(data.message);
|
const message = CoreTextUtils.formatHtmlLines(data.message!);
|
||||||
const files = data.files;
|
const files = data.files;
|
||||||
const options: AddonModForumUpdateDiscussionPostWSOptionsObject = {};
|
const options: AddonModForumUpdateDiscussionPostWSOptionsObject = {};
|
||||||
|
|
||||||
|
@ -295,14 +295,14 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to send it to server.
|
// Try to send it to server.
|
||||||
const sent = await AddonModForum.updatePost(this.post.id, data.subject, message, options);
|
const sent = await AddonModForum.updatePost(this.post.id, data.subject!, message, options);
|
||||||
|
|
||||||
if (sent && this.forum.id) {
|
if (sent && this.forum.id) {
|
||||||
// Data sent to server, delete stored files (if any).
|
// Data sent to server, delete stored files (if any).
|
||||||
AddonModForumHelper.deleteReplyStoredFiles(this.forum.id, this.post.id);
|
AddonModForumHelper.deleteReplyStoredFiles(this.forum.id, this.post.id);
|
||||||
|
|
||||||
this.onPostChange.emit();
|
this.onPostChange.emit();
|
||||||
this.post.subject = data.subject;
|
this.post.subject = data.subject!;
|
||||||
this.post.message = message;
|
this.post.message = message;
|
||||||
this.post.attachments = data.files;
|
this.post.attachments = data.files;
|
||||||
}
|
}
|
||||||
|
@ -419,7 +419,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
||||||
let saveOffline = false;
|
let saveOffline = false;
|
||||||
let message = this.replyData.message;
|
let message = this.replyData.message;
|
||||||
const subject = this.replyData.subject;
|
const subject = this.replyData.subject;
|
||||||
const replyingTo = this.replyData.replyingTo;
|
const replyingTo = this.replyData.replyingTo!;
|
||||||
const files = this.replyData.files || [];
|
const files = this.replyData.files || [];
|
||||||
const options: AddonModForumReplyOptions = {};
|
const options: AddonModForumReplyOptions = {};
|
||||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {
|
||||||
AddonModForumDiscussion,
|
AddonModForumDiscussion,
|
||||||
AddonModForumPost,
|
AddonModForumPost,
|
||||||
AddonModForumProvider,
|
AddonModForumProvider,
|
||||||
|
AddonModForumReply,
|
||||||
} from '../../services/forum';
|
} from '../../services/forum';
|
||||||
import { AddonModForumHelper } from '../../services/forum-helper';
|
import { AddonModForumHelper } from '../../services/forum-helper';
|
||||||
import { AddonModForumOffline } from '../../services/forum-offline';
|
import { AddonModForumOffline } from '../../services/forum-offline';
|
||||||
|
@ -72,18 +73,18 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
||||||
postHasOffline!: boolean;
|
postHasOffline!: boolean;
|
||||||
sort: SortType = 'nested';
|
sort: SortType = 'nested';
|
||||||
trackPosts!: boolean;
|
trackPosts!: boolean;
|
||||||
replyData = {
|
replyData: Omit<AddonModForumReply, 'id'> = {
|
||||||
replyingTo: 0,
|
replyingTo: 0,
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
subject: '',
|
subject: '',
|
||||||
message: null, // Null means empty or just white space.
|
message: null,
|
||||||
files: [],
|
files: [],
|
||||||
isprivatereply: false,
|
isprivatereply: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
originalData = {
|
originalData: Omit<AddonModForumReply, 'id'> = {
|
||||||
subject: null, // Null means original data is not set.
|
subject: null,
|
||||||
message: null, // Null means empty or just white space.
|
message: null,
|
||||||
files: [],
|
files: [],
|
||||||
isprivatereply: false,
|
isprivatereply: false,
|
||||||
};
|
};
|
||||||
|
|
|
@ -368,25 +368,25 @@ export class AddonModForumHelperProvider {
|
||||||
/**
|
/**
|
||||||
* Check if the data of a post/discussion has changed.
|
* Check if the data of a post/discussion has changed.
|
||||||
*
|
*
|
||||||
* @param post Current data.
|
* @param reply Current data.
|
||||||
* @param original Original ata.
|
* @param original Original ata.
|
||||||
* @return True if data has changed, false otherwise.
|
* @return True if data has changed, false otherwise.
|
||||||
*/
|
*/
|
||||||
hasPostDataChanged(post: any, original?: any): boolean {
|
hasPostDataChanged(reply: AddonModForumPostData, original?: AddonModForumPostData): boolean {
|
||||||
if (!original || original.subject == null) {
|
if (!original || original.subject == null) {
|
||||||
// There is no original data, assume it hasn't changed.
|
// There is no original data, assume it hasn't changed.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.subject != original.subject || post.message != original.message) {
|
if (reply.subject != original.subject || reply.message != original.message) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.isprivatereply != original.isprivatereply) {
|
if (reply.isprivatereply != original.isprivatereply) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CoreFileUploader.areFileListDifferent(post.files, original.files);
|
return CoreFileUploader.areFileListDifferent(reply.files ?? [], original.files ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -541,3 +541,13 @@ export class AddonModForumHelperProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddonModForumHelper = makeSingleton(AddonModForumHelperProvider);
|
export const AddonModForumHelper = makeSingleton(AddonModForumHelperProvider);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forum post data used to check changes.
|
||||||
|
*/
|
||||||
|
type AddonModForumPostData = {
|
||||||
|
subject?: string | null;
|
||||||
|
message?: string | null;
|
||||||
|
isprivatereply?: boolean;
|
||||||
|
files?: CoreFileEntry[];
|
||||||
|
};
|
||||||
|
|
|
@ -1407,7 +1407,7 @@ export type AddonModForumDiscussion = {
|
||||||
mailnow: number; // Mail now?.
|
mailnow: number; // Mail now?.
|
||||||
userfullname: string | boolean; // Post author full name.
|
userfullname: string | boolean; // Post author full name.
|
||||||
usermodifiedfullname: string; // Post modifier full name.
|
usermodifiedfullname: string; // Post modifier full name.
|
||||||
userpictureurl: string; // Post author picture.
|
userpictureurl?: string; // Post author picture.
|
||||||
usermodifiedpictureurl: string; // Post modifier picture.
|
usermodifiedpictureurl: string; // Post modifier picture.
|
||||||
numreplies: number; // The number of replies in the discussion.
|
numreplies: number; // The number of replies in the discussion.
|
||||||
numunread: number; // The number of unread discussions.
|
numunread: number; // The number of unread discussions.
|
||||||
|
@ -1564,9 +1564,12 @@ export type AddonModForumAccessInformation = {
|
||||||
*/
|
*/
|
||||||
export type AddonModForumReply = {
|
export type AddonModForumReply = {
|
||||||
id: number;
|
id: number;
|
||||||
subject: string;
|
subject: string | null; // Null means original data is not set.
|
||||||
message: string;
|
message: string | null; // Null means empty or just white space.
|
||||||
files: CoreFileEntry[];
|
files: CoreFileEntry[];
|
||||||
|
replyingTo?: number;
|
||||||
|
isEditing?: boolean;
|
||||||
|
isprivatereply?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -45,7 +45,7 @@ export class AddonModForumDiscussionLinkHandlerService extends CoreContentLinksH
|
||||||
url: string,
|
url: string,
|
||||||
params: Params,
|
params: Params,
|
||||||
courseId?: number,
|
courseId?: number,
|
||||||
data?: any,
|
data?: { instance?: string; cmid?: string; postid?: string },
|
||||||
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||||
data = data || {};
|
data = data || {};
|
||||||
|
|
||||||
|
@ -56,13 +56,13 @@ export class AddonModForumDiscussionLinkHandlerService extends CoreContentLinksH
|
||||||
action: (siteId): void => {
|
action: (siteId): void => {
|
||||||
const discussionId = parseInt(params.d, 10);
|
const discussionId = parseInt(params.d, 10);
|
||||||
const pageParams: Params = {
|
const pageParams: Params = {
|
||||||
forumId: data.instance && parseInt(data.instance, 10),
|
forumId: data?.instance && parseInt(data.instance, 10),
|
||||||
cmId: data.cmid && parseInt(data.cmid, 10),
|
cmId: data?.cmid && parseInt(data.cmid, 10),
|
||||||
courseId: courseId || parseInt(params.courseid, 10) || parseInt(params.cid, 10),
|
courseId: courseId || parseInt(params.courseid, 10) || parseInt(params.cid, 10),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.postid || params.urlHash) {
|
if (data?.postid || params.urlHash) {
|
||||||
pageParams.postId = parseInt(data.postid || params.urlHash.replace('p', ''));
|
pageParams.postId = parseInt(data?.postid || params.urlHash.replace('p', ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.parent) {
|
if (params.parent) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ export class AddonModResourcePrefetchHandlerService extends CoreCourseResourcePr
|
||||||
dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url!);
|
dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url!);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises: Promise<any>[] = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
promises.push(super.downloadOrPrefetch(module, courseId, prefetch, dirPath));
|
promises.push(super.downloadOrPrefetch(module, courseId, prefetch, dirPath));
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import { makeSingleton } from '@singletons';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
const SEPARATOR_35 = /\/\*\*? *3\.5(\.0)? *styles? *\*\//i; // A comment like "/* 3.5 styles */".
|
const SEPARATOR_35 = /\/\*\*? *3\.5(\.0)? *styles? *\*\//i; // A comment like "/* 3.5 styles */".
|
||||||
const TMP_SITE_ID = 'tmpsite';
|
export const TMP_SITE_ID = 'tmpsite';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to handle remote themes. A remote theme is a CSS sheet stored in the site that allows customising the Mobile app.
|
* Service to handle remote themes. A remote theme is a CSS sheet stored in the site that allows customising the Mobile app.
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
After Width: | Height: | Size: 972 B |
|
@ -560,7 +560,7 @@ export class CoreSite {
|
||||||
return CoreUtils.clone(response);
|
return CoreUtils.clone(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = this.getFromCache<T>(method, data, preSets, false).catch(() => {
|
const promise = this.getFromCache<T>(method, data, preSets, false).catch(async () => {
|
||||||
if (preSets.forceOffline) {
|
if (preSets.forceOffline) {
|
||||||
// Don't call the WS, just fail.
|
// Don't call the WS, just fail.
|
||||||
throw new CoreError(
|
throw new CoreError(
|
||||||
|
@ -569,13 +569,15 @@ export class CoreSite {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the WS.
|
// Call the WS.
|
||||||
return this.callOrEnqueueRequest<T>(method, data, preSets, wsPreSets).then((response) => {
|
try {
|
||||||
|
const response = await this.callOrEnqueueRequest<T>(method, data, preSets, wsPreSets);
|
||||||
|
|
||||||
if (preSets.saveToCache) {
|
if (preSets.saveToCache) {
|
||||||
this.saveToCache(method, data, response, preSets);
|
this.saveToCache(method, data, response, preSets);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}).catch((error) => {
|
} catch (error) {
|
||||||
if (error.errorcode == 'invalidtoken' ||
|
if (error.errorcode == 'invalidtoken' ||
|
||||||
(error.errorcode == 'accessexception' && error.message.indexOf('Invalid token - token expired') > -1)) {
|
(error.errorcode == 'accessexception' && error.message.indexOf('Invalid token - token expired') > -1)) {
|
||||||
if (initialToken !== this.token && !retrying) {
|
if (initialToken !== this.token && !retrying) {
|
||||||
|
@ -585,7 +587,9 @@ export class CoreSite {
|
||||||
return this.request<T>(method, data, preSets, true);
|
return this.request<T>(method, data, preSets, true);
|
||||||
} else if (CoreApp.isSSOAuthenticationOngoing()) {
|
} else if (CoreApp.isSSOAuthenticationOngoing()) {
|
||||||
// There's an SSO authentication ongoing, wait for it to finish and try again.
|
// There's an SSO authentication ongoing, wait for it to finish and try again.
|
||||||
return CoreApp.waitForSSOAuthentication().then(() => this.request<T>(method, data, preSets, true));
|
await CoreApp.waitForSSOAuthentication();
|
||||||
|
|
||||||
|
return this.request<T>(method, data, preSets, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session expired, trigger event.
|
// Session expired, trigger event.
|
||||||
|
@ -649,9 +653,7 @@ export class CoreSite {
|
||||||
|
|
||||||
if (preSets.deleteCacheIfWSError && CoreUtils.isWebServiceError(error)) {
|
if (preSets.deleteCacheIfWSError && CoreUtils.isWebServiceError(error)) {
|
||||||
// Delete the cache entry and return the entry. Don't block the user with the delete.
|
// Delete the cache entry and return the entry. Don't block the user with the delete.
|
||||||
this.deleteFromCache(method, data, preSets).catch(() => {
|
CoreUtils.ignoreErrors(this.deleteFromCache(method, data, preSets));
|
||||||
// Ignore errors.
|
|
||||||
});
|
|
||||||
|
|
||||||
throw new CoreWSError(error);
|
throw new CoreWSError(error);
|
||||||
}
|
}
|
||||||
|
@ -660,10 +662,12 @@ export class CoreSite {
|
||||||
preSets.omitExpires = true;
|
preSets.omitExpires = true;
|
||||||
preSets.getFromCache = true;
|
preSets.getFromCache = true;
|
||||||
|
|
||||||
return this.getFromCache<T>(method, data, preSets, true).catch(() => {
|
try {
|
||||||
|
return await this.getFromCache<T>(method, data, preSets, true);
|
||||||
|
} catch (e) {
|
||||||
throw new CoreWSError(error);
|
throw new CoreWSError(error);
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
}).then((response: any) => {
|
}).then((response: any) => {
|
||||||
// Check if the response is an error, this happens if the error was stored in the cache.
|
// Check if the response is an error, this happens if the error was stored in the cache.
|
||||||
|
|
|
@ -38,10 +38,18 @@ export class CoreConstants {
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
|
|
||||||
static readonly SECONDS_YEAR = 31536000;
|
static readonly SECONDS_YEAR = 31536000;
|
||||||
|
static readonly SECONDS_MONTH = 2592000;
|
||||||
static readonly SECONDS_WEEK = 604800;
|
static readonly SECONDS_WEEK = 604800;
|
||||||
static readonly SECONDS_DAY = 86400;
|
static readonly SECONDS_DAY = 86400;
|
||||||
static readonly SECONDS_HOUR = 3600;
|
static readonly SECONDS_HOUR = 3600;
|
||||||
static readonly SECONDS_MINUTE = 60;
|
static readonly SECONDS_MINUTE = 60;
|
||||||
|
static readonly MILLISECONDS_YEAR = 31536000000;
|
||||||
|
static readonly MILLISECONDS_MONTH = 2592000000;
|
||||||
|
static readonly MILLISECONDS_WEEK = 604800000;
|
||||||
|
static readonly MILLISECONDS_DAY = 86400000;
|
||||||
|
static readonly MILLISECONDS_HOUR = 3600000;
|
||||||
|
static readonly MILLISECONDS_MINUTE = 60000;
|
||||||
|
static readonly MILLISECONDS_SECOND = 1000;
|
||||||
static readonly WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB.
|
static readonly WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB.
|
||||||
static readonly DOWNLOAD_THRESHOLD = 10485760; // 10MB.
|
static readonly DOWNLOAD_THRESHOLD = 10485760; // 10MB.
|
||||||
static readonly MINIMUM_FREE_SPACE = 10485760; // 10MB.
|
static readonly MINIMUM_FREE_SPACE = 10485760; // 10MB.
|
||||||
|
@ -131,7 +139,7 @@ export class CoreConstants {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnvironmentConfig = {
|
export interface EnvironmentConfig {
|
||||||
app_id: string;
|
app_id: string;
|
||||||
appname: string;
|
appname: string;
|
||||||
versioncode: number;
|
versioncode: number;
|
||||||
|
@ -167,7 +175,7 @@ type EnvironmentConfig = {
|
||||||
forceOpenLinksIn: 'app' | 'browser';
|
forceOpenLinksIn: 'app' | 'browser';
|
||||||
};
|
};
|
||||||
|
|
||||||
type EnvironmentBuild = {
|
export interface EnvironmentBuild {
|
||||||
version: string;
|
version: string;
|
||||||
isProduction: boolean;
|
isProduction: boolean;
|
||||||
isTesting: boolean;
|
isTesting: boolean;
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { Directive, ElementRef, OnInit, Input, Output, EventEmitter } from '@ang
|
||||||
*
|
*
|
||||||
* Example usage:
|
* Example usage:
|
||||||
*
|
*
|
||||||
* <a ion-button [core-suppress-events] (onClick)="toggle($event)">
|
* <ion-button [core-suppress-events] (onClick)="toggle($event)">
|
||||||
*/
|
*/
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[core-suppress-events]',
|
selector: '[core-suppress-events]',
|
||||||
|
|
|
@ -34,10 +34,10 @@ export class CoreBlockComponent implements OnInit, OnDestroy, DoCheck {
|
||||||
@Input() block!: CoreCourseBlock; // The block to render.
|
@Input() block!: CoreCourseBlock; // The block to render.
|
||||||
@Input() contextLevel!: string; // The context where the block will be used.
|
@Input() contextLevel!: string; // The context where the block will be used.
|
||||||
@Input() instanceId!: number; // The instance ID associated with the context level.
|
@Input() instanceId!: number; // The instance ID associated with the context level.
|
||||||
@Input() extraData: any; // Any extra data to be passed to the block.
|
@Input() extraData!: Record<string, unknown>; // Any extra data to be passed to the block.
|
||||||
|
|
||||||
componentClass?: Type<unknown>; // The class of the component to render.
|
componentClass?: Type<unknown>; // The class of the component to render.
|
||||||
data: any = {}; // Data to pass to the component.
|
data: Record<string, unknown> = {}; // Data to pass to the component.
|
||||||
class?: string; // CSS class to apply to the block.
|
class?: string; // CSS class to apply to the block.
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
|
||||||
|
|
|
@ -140,17 +140,17 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
|
||||||
*
|
*
|
||||||
* @param e Click event.
|
* @param e Click event.
|
||||||
*/
|
*/
|
||||||
prefetchCourse(e: Event): void {
|
async prefetchCourse(e?: Event): Promise<void> {
|
||||||
e.preventDefault();
|
e?.preventDefault();
|
||||||
e.stopPropagation();
|
e?.stopPropagation();
|
||||||
|
|
||||||
/* @ todo try {
|
try {
|
||||||
CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course);
|
await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!this.isDestroyed) {
|
if (!this.isDestroyed) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -112,7 +112,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.siteConfig = config;
|
this.siteConfig = config;
|
||||||
|
|
||||||
await CoreSites.checkRequiredMinimumVersion(config);
|
await CoreSites.checkApplication(config);
|
||||||
|
|
||||||
// Check logoURL if user avatar is not set.
|
// Check logoURL if user avatar is not set.
|
||||||
if (this.userAvatar.startsWith(this.siteUrl + '/theme/image.php')) {
|
if (this.userAvatar.startsWith(this.siteUrl + '/theme/image.php')) {
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
<ion-spinner></ion-spinner>
|
<ion-spinner></ion-spinner>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngFor="let site of sites">
|
<ng-container *ngFor="let site of sites">
|
||||||
<ng-container *ngTemplateOutlet="site; context: {site: site}"></ng-container>
|
<ng-container *ngTemplateOutlet="sitelisting; context: {site: site}"></ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
<!-- Template site selector. -->
|
<!-- Template site selector. -->
|
||||||
<ng-template #site let-site="site">
|
<ng-template #sitelisting let-site="site">
|
||||||
<ion-item button (click)="connect($event, site.url, site)" [attr.aria-label]="site.name" detail="true">
|
<ion-item button (click)="connect($event, site.url, site)" [attr.aria-label]="site.name" detail="true">
|
||||||
<ion-thumbnail *ngIf="siteFinderSettings.displayimage" slot="start">
|
<ion-thumbnail *ngIf="siteFinderSettings.displayimage" slot="start">
|
||||||
<img [src]="site.imageurl" *ngIf="site.imageurl" onError="this.src='assets/icon/icon.png'" alt="" role="presentation">
|
<img [src]="site.imageurl" *ngIf="site.imageurl" onError="this.src='assets/icon/icon.png'" alt="" role="presentation">
|
||||||
|
|
|
@ -348,32 +348,36 @@ export class CoreLoginSitePage implements OnInit {
|
||||||
* @return Promise resolved after logging in.
|
* @return Promise resolved after logging in.
|
||||||
*/
|
*/
|
||||||
protected async login(response: CoreSiteCheckResponse, foundSite?: CoreLoginSiteInfoExtended): Promise<void> {
|
protected async login(response: CoreSiteCheckResponse, foundSite?: CoreLoginSiteInfoExtended): Promise<void> {
|
||||||
await CoreUtils.ignoreErrors(CoreSites.checkApplication(response));
|
try {
|
||||||
|
await CoreSites.checkApplication(response.config);
|
||||||
|
|
||||||
CoreForms.triggerFormSubmittedEvent(this.formElement, true);
|
CoreForms.triggerFormSubmittedEvent(this.formElement, true);
|
||||||
|
|
||||||
if (response.warning) {
|
if (response.warning) {
|
||||||
CoreDomUtils.showErrorModal(response.warning, true, 4000);
|
CoreDomUtils.showErrorModal(response.warning, true, 4000);
|
||||||
}
|
|
||||||
|
|
||||||
if (CoreLoginHelper.isSSOLoginNeeded(response.code)) {
|
|
||||||
// SSO. User needs to authenticate in a browser.
|
|
||||||
CoreLoginHelper.confirmAndOpenBrowserForSSOLogin(
|
|
||||||
response.siteUrl,
|
|
||||||
response.code,
|
|
||||||
response.service,
|
|
||||||
response.config?.launchurl,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const pageParams = { siteUrl: response.siteUrl, siteConfig: response.config };
|
|
||||||
if (foundSite && !this.fixedSites) {
|
|
||||||
pageParams['siteName'] = foundSite.name;
|
|
||||||
pageParams['logoUrl'] = foundSite.imageurl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreNavigator.navigate('/login/credentials', {
|
if (CoreLoginHelper.isSSOLoginNeeded(response.code)) {
|
||||||
params: pageParams,
|
// SSO. User needs to authenticate in a browser.
|
||||||
});
|
CoreLoginHelper.confirmAndOpenBrowserForSSOLogin(
|
||||||
|
response.siteUrl,
|
||||||
|
response.code,
|
||||||
|
response.service,
|
||||||
|
response.config?.launchurl,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const pageParams = { siteUrl: response.siteUrl, siteConfig: response.config };
|
||||||
|
if (foundSite && !this.fixedSites) {
|
||||||
|
pageParams['siteName'] = foundSite.name;
|
||||||
|
pageParams['logoUrl'] = foundSite.imageurl;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreNavigator.navigate('/login/credentials', {
|
||||||
|
params: pageParams,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,7 +545,7 @@ export class CoreLoginSitePage implements OnInit {
|
||||||
// Check if site uses SSO.
|
// Check if site uses SSO.
|
||||||
const response = await CoreSites.checkSite(siteUrl);
|
const response = await CoreSites.checkSite(siteUrl);
|
||||||
|
|
||||||
await CoreSites.checkApplication(response);
|
await CoreSites.checkApplication(response.config);
|
||||||
|
|
||||||
if (!CoreLoginHelper.isSSOLoginNeeded(response.code)) {
|
if (!CoreLoginHelper.isSSOLoginNeeded(response.code)) {
|
||||||
// No SSO, go to credentials page.
|
// No SSO, go to credentials page.
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreSitePublicConfigResponse } from '@classes/site';
|
|
||||||
import { CoreCronHandler } from '@services/cron';
|
import { CoreCronHandler } from '@services/cron';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
@ -40,9 +39,9 @@ export class CoreLoginCronHandlerService implements CoreCronHandler {
|
||||||
// Do not check twice in the same 10 minutes.
|
// Do not check twice in the same 10 minutes.
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
const config = await CoreUtils.ignoreErrors(site.getPublicConfig(), <Partial<CoreSitePublicConfigResponse>> {});
|
const config = await CoreUtils.ignoreErrors(site.getPublicConfig());
|
||||||
|
|
||||||
CoreUtils.ignoreErrors(CoreSites.checkApplication(<any> config));
|
CoreUtils.ignoreErrors(CoreSites.checkApplication(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,6 +40,7 @@ import {
|
||||||
} from './database/pushnotifications';
|
} from './database/pushnotifications';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { CoreWSExternalWarning } from '@services/ws';
|
import { CoreWSExternalWarning } from '@services/ws';
|
||||||
|
import { CoreSitesFactory } from '@services/sites-factory';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to handle push notifications.
|
* Service to handle push notifications.
|
||||||
|
@ -751,7 +752,7 @@ export class CorePushNotificationsProvider {
|
||||||
|
|
||||||
await Promise.all(results.map(async (result) => {
|
await Promise.all(results.map(async (result) => {
|
||||||
// Create a temporary site to unregister.
|
// Create a temporary site to unregister.
|
||||||
const tmpSite = new CoreSite(
|
const tmpSite = CoreSitesFactory.makeSite(
|
||||||
result.siteid,
|
result.siteid,
|
||||||
result.siteurl,
|
result.siteurl,
|
||||||
result.token,
|
result.token,
|
||||||
|
|
|
@ -309,6 +309,7 @@ export class CoreNavigatorService {
|
||||||
* @return Value of the parameter, undefined if not found.
|
* @return Value of the parameter, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getRouteParam<T = unknown>(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined {
|
getRouteParam<T = unknown>(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let value: any;
|
let value: any;
|
||||||
|
|
||||||
if (!routeOptions.params) {
|
if (!routeOptions.params) {
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreSite, CoreSiteConfig, CoreSiteInfo } from '@classes/site';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Provider to create sites instances.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreSitesFactoryService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a site object.
|
||||||
|
*
|
||||||
|
* @param id Site ID.
|
||||||
|
* @param siteUrl Site URL.
|
||||||
|
* @param token Site's WS token.
|
||||||
|
* @param info Site info.
|
||||||
|
* @param privateToken Private token.
|
||||||
|
* @param config Site public config.
|
||||||
|
* @param loggedOut Whether user is logged out.
|
||||||
|
* @return Site instance.
|
||||||
|
*/
|
||||||
|
makeSite(
|
||||||
|
id: string | undefined,
|
||||||
|
siteUrl: string,
|
||||||
|
token?: string,
|
||||||
|
info?: CoreSiteInfo,
|
||||||
|
privateToken?: string,
|
||||||
|
config?: CoreSiteConfig,
|
||||||
|
loggedOut?: boolean,
|
||||||
|
): CoreSite {
|
||||||
|
return new CoreSite(id, siteUrl, token, info, privateToken, config, loggedOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CoreSitesFactory = makeSingleton(CoreSitesFactoryService);
|
|
@ -51,6 +51,7 @@ import {
|
||||||
import { CoreArray } from '../singletons/array';
|
import { CoreArray } from '../singletons/array';
|
||||||
import { CoreNetworkError } from '@classes/errors/network-error';
|
import { CoreNetworkError } from '@classes/errors/network-error';
|
||||||
import { CoreNavigationOptions } from './navigator';
|
import { CoreNavigationOptions } from './navigator';
|
||||||
|
import { CoreSitesFactory } from './sites-factory';
|
||||||
|
|
||||||
export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS');
|
export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS');
|
||||||
|
|
||||||
|
@ -220,7 +221,7 @@ export class CoreSitesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Site exists. Create a temporary site to check if local_mobile is installed.
|
// Site exists. Create a temporary site to check if local_mobile is installed.
|
||||||
const temporarySite = new CoreSite(undefined, siteUrl);
|
const temporarySite = CoreSitesFactory.makeSite(undefined, siteUrl);
|
||||||
let data: LocalMobileResponse;
|
let data: LocalMobileResponse;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -438,7 +439,7 @@ export class CoreSitesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a "candidate" site to fetch the site info.
|
// Create a "candidate" site to fetch the site info.
|
||||||
let candidateSite = new CoreSite(undefined, siteUrl, token, undefined, privateToken, undefined, undefined);
|
let candidateSite = CoreSitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken, undefined, undefined);
|
||||||
let isNewSite = true;
|
let isNewSite = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -706,20 +707,19 @@ export class CoreSitesProvider {
|
||||||
/**
|
/**
|
||||||
* Check the app for a site and show a download dialogs if necessary.
|
* Check the app for a site and show a download dialogs if necessary.
|
||||||
*
|
*
|
||||||
* @param response Data obtained during site check.
|
* @param config Config object of the site.
|
||||||
*/
|
*/
|
||||||
async checkApplication(response: CoreSiteCheckResponse): Promise<void> {
|
async checkApplication(config?: CoreSitePublicConfigResponse): Promise<void> {
|
||||||
await this.checkRequiredMinimumVersion(response.config);
|
await this.checkRequiredMinimumVersion(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the required minimum version of the app for a site and shows a download dialog.
|
* Check the required minimum version of the app for a site and shows a download dialog.
|
||||||
*
|
*
|
||||||
* @param config Config object of the site.
|
* @param config Config object of the site.
|
||||||
* @param siteId ID of the site to check. Current site id will be used otherwise.
|
|
||||||
* @return Resolved with if meets the requirements, rejected otherwise.
|
* @return Resolved with if meets the requirements, rejected otherwise.
|
||||||
*/
|
*/
|
||||||
async checkRequiredMinimumVersion(config?: CoreSitePublicConfigResponse, siteId?: string): Promise<void> {
|
protected async checkRequiredMinimumVersion(config?: CoreSitePublicConfigResponse): Promise<void> {
|
||||||
if (!config || !config.tool_mobile_minimumversion) {
|
if (!config || !config.tool_mobile_minimumversion) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -735,7 +735,7 @@ export class CoreSitesProvider {
|
||||||
default: config.tool_mobile_setuplink,
|
default: config.tool_mobile_setuplink,
|
||||||
};
|
};
|
||||||
|
|
||||||
siteId = siteId || this.getCurrentSiteId();
|
const siteId = this.getCurrentSiteId();
|
||||||
|
|
||||||
const downloadUrl = CoreApp.getAppStoreUrl(storesConfig);
|
const downloadUrl = CoreApp.getAppStoreUrl(storesConfig);
|
||||||
|
|
||||||
|
@ -837,7 +837,7 @@ export class CoreSitesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.checkRequiredMinimumVersion(config);
|
await this.checkApplication(config);
|
||||||
|
|
||||||
this.login(siteId);
|
this.login(siteId);
|
||||||
// Update site info. We don't block the UI.
|
// Update site info. We don't block the UI.
|
||||||
|
@ -1004,7 +1004,15 @@ export class CoreSitesProvider {
|
||||||
const info = entry.info ? <CoreSiteInfo> CoreTextUtils.parseJSON(entry.info) : undefined;
|
const info = entry.info ? <CoreSiteInfo> CoreTextUtils.parseJSON(entry.info) : undefined;
|
||||||
const config = entry.config ? <CoreSiteConfig> CoreTextUtils.parseJSON(entry.config) : undefined;
|
const config = entry.config ? <CoreSiteConfig> CoreTextUtils.parseJSON(entry.config) : undefined;
|
||||||
|
|
||||||
const site = new CoreSite(entry.id, entry.siteUrl, entry.token, info, entry.privateToken, config, entry.loggedOut == 1);
|
const site = CoreSitesFactory.makeSite(
|
||||||
|
entry.id,
|
||||||
|
entry.siteUrl,
|
||||||
|
entry.token,
|
||||||
|
info,
|
||||||
|
entry.privateToken,
|
||||||
|
config,
|
||||||
|
entry.loggedOut == 1,
|
||||||
|
);
|
||||||
site.setOAuthId(entry.oauthId || undefined);
|
site.setOAuthId(entry.oauthId || undefined);
|
||||||
|
|
||||||
return this.migrateSiteSchemas(site).then(() => {
|
return this.migrateSiteSchemas(site).then(() => {
|
||||||
|
@ -1165,28 +1173,27 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved when the user is logged out.
|
* @return Promise resolved when the user is logged out.
|
||||||
*/
|
*/
|
||||||
async logout(): Promise<void> {
|
async logout(): Promise<void> {
|
||||||
let siteId: string | undefined;
|
if (!this.currentSite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await this.appDB;
|
||||||
|
|
||||||
const promises: Promise<unknown>[] = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
|
const siteConfig = this.currentSite.getStoredConfig();
|
||||||
|
const siteId = this.currentSite.getId();
|
||||||
|
|
||||||
if (this.currentSite) {
|
this.currentSite = undefined;
|
||||||
const db = await this.appDB;
|
|
||||||
const siteConfig = this.currentSite.getStoredConfig();
|
|
||||||
siteId = this.currentSite.getId();
|
|
||||||
|
|
||||||
this.currentSite = undefined;
|
if (siteConfig && siteConfig.tool_mobile_forcelogout == '1') {
|
||||||
|
promises.push(this.setSiteLoggedOut(siteId, true));
|
||||||
if (siteConfig && siteConfig.tool_mobile_forcelogout == '1') {
|
|
||||||
promises.push(this.setSiteLoggedOut(siteId, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
promises.push(db.deleteRecords(CURRENT_SITE_TABLE_NAME, { id: 1 }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
promises.push(db.deleteRecords(CURRENT_SITE_TABLE_NAME, { id: 1 }));
|
||||||
await Promise.all(promises);
|
|
||||||
} finally {
|
await CoreUtils.ignoreErrors(Promise.all(promises));
|
||||||
CoreEvents.trigger(CoreEvents.LOGOUT, {}, siteId);
|
|
||||||
}
|
CoreEvents.trigger(CoreEvents.LOGOUT, {}, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1293,7 +1300,7 @@ export class CoreSitesProvider {
|
||||||
* @param siteid Site's ID.
|
* @param siteid Site's ID.
|
||||||
* @return A promise resolved when the site is updated.
|
* @return A promise resolved when the site is updated.
|
||||||
*/
|
*/
|
||||||
async updateSiteInfo(siteId: string): Promise<void> {
|
async updateSiteInfo(siteId?: string): Promise<void> {
|
||||||
const site = await this.getSite(siteId);
|
const site = await this.getSite(siteId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1430,7 +1437,7 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved with the public config.
|
* @return Promise resolved with the public config.
|
||||||
*/
|
*/
|
||||||
getSitePublicConfig(siteUrl: string): Promise<CoreSitePublicConfigResponse> {
|
getSitePublicConfig(siteUrl: string): Promise<CoreSitePublicConfigResponse> {
|
||||||
const temporarySite = new CoreSite(undefined, siteUrl);
|
const temporarySite = CoreSitesFactory.makeSite(undefined, siteUrl);
|
||||||
|
|
||||||
return temporarySite.getPublicConfig();
|
return temporarySite.getPublicConfig();
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ export class CoreCustomURLSchemesProvider {
|
||||||
|
|
||||||
data.siteUrl = result.siteUrl;
|
data.siteUrl = result.siteUrl;
|
||||||
|
|
||||||
await CoreSites.checkApplication(result);
|
await CoreSites.checkApplication(result.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CoreSites.newSite(
|
return CoreSites.newSite(
|
||||||
|
|
|
@ -1491,7 +1491,7 @@ export class CoreUtilsProvider {
|
||||||
debounce<T extends unknown[]>(fn: (...args: T) => unknown, delay: number): (...args: T) => void {
|
debounce<T extends unknown[]>(fn: (...args: T) => unknown, delay: number): (...args: T) => void {
|
||||||
let timeoutID: number;
|
let timeoutID: number;
|
||||||
|
|
||||||
const debounced = (...args: unknown[]): void => {
|
const debounced = (...args: T): void => {
|
||||||
clearTimeout(timeoutID);
|
clearTimeout(timeoutID);
|
||||||
|
|
||||||
timeoutID = window.setTimeout(() => fn.apply(null, args), delay);
|
timeoutID = window.setTimeout(() => fn.apply(null, args), delay);
|
||||||
|
|
|
@ -16,6 +16,16 @@ import moment from 'moment';
|
||||||
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
|
|
||||||
|
import { CoreTime } from './time';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to warn that logs are disabled, called only once.
|
||||||
|
*/
|
||||||
|
const warnLogsDisabled = CoreTime.once(() => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('Log is disabled in production app');
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log function type.
|
* Log function type.
|
||||||
*/
|
*/
|
||||||
|
@ -59,8 +69,7 @@ export class CoreLogger {
|
||||||
// Disable log on production and testing.
|
// Disable log on production and testing.
|
||||||
if (CoreConstants.BUILD.isProduction || CoreConstants.BUILD.isTesting) {
|
if (CoreConstants.BUILD.isProduction || CoreConstants.BUILD.isTesting) {
|
||||||
if (CoreConstants.BUILD.isProduction) {
|
if (CoreConstants.BUILD.isProduction) {
|
||||||
// eslint-disable-next-line no-console
|
warnLogsDisabled();
|
||||||
console.warn('Log is disabled in production app');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton with helper functions for math operations.
|
||||||
|
*/
|
||||||
|
export class CoreMath {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamp a value between a minimum and a maximum.
|
||||||
|
*
|
||||||
|
* @param value Original value.
|
||||||
|
* @param min Minimum value.
|
||||||
|
* @param max Maximum value.
|
||||||
|
* @return Clamped value.
|
||||||
|
*/
|
||||||
|
static clamp(value: number, min: number, max: number): number {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,17 @@ export type CoreObjectWithoutEmpty<T> = {
|
||||||
*/
|
*/
|
||||||
export class CoreObject {
|
export class CoreObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two objects have the same shape and the same leaf values.
|
||||||
|
*
|
||||||
|
* @param a First object.
|
||||||
|
* @param b Second object.
|
||||||
|
* @return Whether objects are equal.
|
||||||
|
*/
|
||||||
|
static deepEquals(a: unknown, b: unknown): boolean {
|
||||||
|
return JSON.stringify(a) === JSON.stringify(b);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the given object is empty.
|
* Check whether the given object is empty.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton with helper functions for time operations.
|
||||||
|
*/
|
||||||
|
export class CoreTime {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a function so that it is called only once.
|
||||||
|
*
|
||||||
|
* @param fn Function.
|
||||||
|
* @return Wrapper that will call the underlying function only once.
|
||||||
|
*/
|
||||||
|
static once<T extends unknown[]>(fn: (...args: T) => unknown): (...args: T) => void {
|
||||||
|
let called = false;
|
||||||
|
|
||||||
|
return (...args: T) => {
|
||||||
|
if (called) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
called = true;
|
||||||
|
fn.apply(null, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -161,3 +161,12 @@ $screen-breakpoints: (
|
||||||
) !default;
|
) !default;
|
||||||
|
|
||||||
$breakpoint-tablet: map-get($screen-breakpoints, tablet), !default;
|
$breakpoint-tablet: map-get($screen-breakpoints, tablet), !default;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Z-indexes.
|
||||||
|
*
|
||||||
|
* https://github.com/ionic-team/ionic-framework/blob/master/core/src/themes/ionic.globals.scss
|
||||||
|
*/
|
||||||
|
|
||||||
|
$z-index-overlay: 1001;
|
||||||
|
$z-index-overlay-wrapper: 10;
|
||||||
|
|
|
@ -37,6 +37,14 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.margin-bottom-sm { margin-bottom: 8px; }
|
||||||
|
.margin-bottom-md { margin-bottom: 12px; }
|
||||||
|
|
||||||
|
.font-bold { font-weight: bold; }
|
||||||
|
.font-italic { font-style: italic; }
|
||||||
|
.font-lg { font-size: 1.7rem; }
|
||||||
|
.font-sm { font-size: 1.2rem; }
|
||||||
|
|
||||||
// Correctly inherit ion-text-wrap onto labels.
|
// Correctly inherit ion-text-wrap onto labels.
|
||||||
ion-item.ion-text-wrap ion-label {
|
ion-item.ion-text-wrap ion-label {
|
||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
|
|
|
@ -122,7 +122,7 @@
|
||||||
ion-slide {
|
ion-slide {
|
||||||
--background: var(--core-tab-background);
|
--background: var(--core-tab-background);
|
||||||
--color: var(--core-tab-color);
|
--color: var(--core-tab-color);
|
||||||
--border-color: var(--core-tab-border-colo);
|
--border-color: var(--core-tab-border-color);
|
||||||
--color-active: var(--core-tab-color-active);
|
--color-active: var(--core-tab-color-active);
|
||||||
--border-color-active: var(--core-tab-border-color-active);
|
--border-color-active: var(--core-tab-border-color-active);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ declare module '@ionic/angular' {
|
||||||
|
|
||||||
export class NavController {
|
export class NavController {
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
navigateForward(url: string | UrlTree | any[], options?: NavigationOptions): Promise<boolean | null>;
|
navigateForward(url: string | UrlTree | any[], options?: NavigationOptions): Promise<boolean | null>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ information provided here is intended especially for developers.
|
||||||
=== 3.9.5 ===
|
=== 3.9.5 ===
|
||||||
|
|
||||||
- Several functions inside AddonNotificationsProvider have been modified to accept an "options" parameter instead of having several optional parameters.
|
- Several functions inside AddonNotificationsProvider have been modified to accept an "options" parameter instead of having several optional parameters.
|
||||||
|
- Schemas are now registered using Angular providers with the CORE_SITE_SCHEMAS injection token instead of CoreSitesProvider.registerSiteSchema.
|
||||||
|
|
||||||
=== 3.9.3 ===
|
=== 3.9.3 ===
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue