MOBILE-4270 chore: Solve lots of linter warnings
parent
ec86719833
commit
c22424c93e
|
@ -270,9 +270,10 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
|
|||
}
|
||||
|
||||
if (this.userId) {
|
||||
const userId = this.userId;
|
||||
// Get the member info. Invalidate first to make sure we get the latest status.
|
||||
promises.push(AddonMessages.invalidateMemberInfo(this.userId).then(async () => {
|
||||
this.otherMember = await AddonMessages.getMemberInfo(this.userId!);
|
||||
this.otherMember = await AddonMessages.getMemberInfo(userId);
|
||||
|
||||
if (!exists && this.otherMember) {
|
||||
this.conversationImage = this.otherMember.profileimageurl;
|
||||
|
@ -288,6 +289,8 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
|
|||
|
||||
} else {
|
||||
if (this.userId) {
|
||||
const userId = this.userId;
|
||||
|
||||
// Fake the user member info.
|
||||
promises.push(CoreUser.getProfile(this.userId).then(async (user) => {
|
||||
this.otherMember = {
|
||||
|
@ -305,8 +308,8 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
|
|||
canmessage: true,
|
||||
requirescontact: false,
|
||||
};
|
||||
this.otherMember.isblocked = await AddonMessages.isBlocked(this.userId!);
|
||||
this.otherMember.iscontact = await AddonMessages.isContact(this.userId!);
|
||||
this.otherMember.isblocked = await AddonMessages.isBlocked(userId);
|
||||
this.otherMember.iscontact = await AddonMessages.isContact(userId);
|
||||
this.blockIcon = this.otherMember.isblocked ? 'fas-user-check' : 'fas-user-lock';
|
||||
|
||||
return;
|
||||
|
|
|
@ -80,17 +80,17 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
AddonMessagesProvider.NEW_MESSAGE_EVENT,
|
||||
(data) => {
|
||||
if (data.userId && this.discussions) {
|
||||
const discussion = this.discussions.find((disc) => disc.message!.user == data.userId);
|
||||
const discussion = this.discussions.find((disc) => disc.message?.user === data.userId);
|
||||
|
||||
if (discussion === undefined) {
|
||||
this.loaded = false;
|
||||
this.refreshData().finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
} else {
|
||||
// An existing discussion has a new message, update the last message.
|
||||
discussion.message!.message = data.message;
|
||||
discussion.message!.timecreated = data.timecreated;
|
||||
} else if (discussion.message) {
|
||||
// An existing discussion has a new message, update the last message.
|
||||
discussion.message.message = data.message;
|
||||
discussion.message.timecreated = data.timecreated;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -102,10 +102,10 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
AddonMessagesProvider.READ_CHANGED_EVENT,
|
||||
(data) => {
|
||||
if (data.userId && this.discussions) {
|
||||
const discussion = this.discussions.find((disc) => disc.message!.user == data.userId);
|
||||
const discussion = this.discussions.find((disc) => disc.message?.user === data.userId);
|
||||
|
||||
if (discussion !== undefined) {
|
||||
// A discussion has been read reset counter.
|
||||
// A discussion has been read reset counter.
|
||||
discussion.unread = false;
|
||||
|
||||
// Conversations changed, invalidate them and refresh unread counts.
|
||||
|
@ -138,7 +138,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Component loaded.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.route.queryParams.subscribe(async (params) => {
|
||||
|
@ -150,9 +150,9 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
|
||||
await this.fetchData();
|
||||
|
||||
if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet) {
|
||||
if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet && this.discussions[0].message) {
|
||||
// Take first and load it.
|
||||
await this.gotoDiscussion(this.discussions[0].message!.user);
|
||||
await this.gotoDiscussion(this.discussions[0].message.user);
|
||||
}
|
||||
|
||||
// Treat deep link now that the conversation route has been loaded if needed.
|
||||
|
@ -287,7 +287,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.newMessagesObserver?.off();
|
||||
|
|
|
@ -77,15 +77,14 @@ export class AddonModChoiceProvider {
|
|||
choiceId: number,
|
||||
name: string,
|
||||
courseId: number,
|
||||
responses?: number[],
|
||||
responses: number[] = [],
|
||||
siteId?: string,
|
||||
): Promise<boolean> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
responses = responses || [];
|
||||
|
||||
// Convenience function to store a message to be synchronized later.
|
||||
const storeOffline = async (): Promise<boolean> => {
|
||||
await AddonModChoiceOffline.saveResponse(choiceId, name, courseId, responses!, true, siteId);
|
||||
await AddonModChoiceOffline.saveResponse(choiceId, name, courseId, responses, true, siteId);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
|
|
@ -62,7 +62,7 @@ export class AddonModForumHelperProvider {
|
|||
message: string,
|
||||
attachments?: CoreFileEntry[],
|
||||
options?: AddonModForumDiscussionOptions,
|
||||
groupIds?: number[],
|
||||
groupIds: number[] = [],
|
||||
timeCreated?: number,
|
||||
siteId?: string,
|
||||
): Promise<number[] | null> {
|
||||
|
@ -76,7 +76,7 @@ export class AddonModForumHelperProvider {
|
|||
// Convenience function to store a message to be synchronized later.
|
||||
const storeOffline = async (): Promise<void> => {
|
||||
// Multiple groups, the discussion is being posted to all groups.
|
||||
const groupId = groupIds!.length > 1 ? AddonModForumProvider.ALL_GROUPS : groupIds![0];
|
||||
const groupId = groupIds.length > 1 ? AddonModForumProvider.ALL_GROUPS : groupIds[0];
|
||||
|
||||
if (offlineAttachments && options) {
|
||||
options.attachmentsid = offlineAttachments;
|
||||
|
@ -182,7 +182,7 @@ export class AddonModForumHelperProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @returns Promise resolved with the object converted to Online.
|
||||
*/
|
||||
convertOfflineReplyToOnline(offlineReply: AddonModForumOfflineReply, siteId?: string): Promise<AddonModForumPost> {
|
||||
async convertOfflineReplyToOnline(offlineReply: AddonModForumOfflineReply, siteId?: string): Promise<AddonModForumPost> {
|
||||
const reply: AddonModForumPost = {
|
||||
id: -offlineReply.timecreated,
|
||||
discussionid: offlineReply.discussionid,
|
||||
|
@ -236,11 +236,11 @@ export class AddonModForumHelperProvider {
|
|||
),
|
||||
);
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
reply.attachment = reply.attachments!.length > 0 ? 1 : 0;
|
||||
await Promise.all(promises);
|
||||
|
||||
return reply;
|
||||
});
|
||||
reply.attachment = reply.attachments?.length ? 1 : 0;
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
import { CoreQuestionBehaviourDelegate, CoreQuestionQuestionWithAnswers } from '@features/question/services/behaviour-delegate';
|
||||
import { CoreQuestionAnswerDBRecord } from '@features/question/services/database/question';
|
||||
|
|
|
@ -99,7 +99,7 @@ export class AddonModQuizProvider {
|
|||
* @returns Grade to display.
|
||||
*/
|
||||
formatGrade(grade?: number | null, decimals?: number): string {
|
||||
if (grade === undefined || grade == -1 || grade === null || isNaN(grade)) {
|
||||
if (grade === undefined || grade === -1 || grade === null || isNaN(grade)) {
|
||||
return Translate.instant('addon.mod_quiz.notyetgraded');
|
||||
}
|
||||
|
||||
|
@ -1800,7 +1800,7 @@ export class AddonModQuizProvider {
|
|||
): string | undefined {
|
||||
let grade: number | undefined;
|
||||
|
||||
const rawGradeNum = typeof rawGrade == 'string' ? parseFloat(rawGrade) : rawGrade;
|
||||
const rawGradeNum = typeof rawGrade === 'string' ? parseFloat(rawGrade) : rawGrade;
|
||||
if (rawGradeNum !== undefined && rawGradeNum !== null && !isNaN(rawGradeNum)) {
|
||||
if (quiz.sumgrades && quiz.sumgrades >= 0.000005) {
|
||||
grade = rawGradeNum * (quiz.grade ?? 0) / quiz.sumgrades;
|
||||
|
|
|
@ -143,7 +143,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
|||
this.displayDescription = false;
|
||||
|
||||
this.warning = downloadResult.failed
|
||||
? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!)
|
||||
? this.getErrorDownloadingSomeFilesMessage(downloadResult.error ?? '')
|
||||
: '';
|
||||
|
||||
return;
|
||||
|
|
|
@ -61,8 +61,8 @@ export class AddonModResourcePrefetchHandlerService extends CoreCourseResourcePr
|
|||
async downloadOrPrefetch(module: CoreCourseModuleData, courseId: number, prefetch?: boolean): Promise<void> {
|
||||
let dirPath: string | undefined;
|
||||
|
||||
if (AddonModResourceHelper.isDisplayedInIframe(module)) {
|
||||
dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url!);
|
||||
if (AddonModResourceHelper.isDisplayedInIframe(module) && module.url !== undefined) {
|
||||
dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url);
|
||||
}
|
||||
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
|
|
@ -62,7 +62,7 @@ export class AddonModResourceHelperProvider {
|
|||
* @returns Promise resolved with the iframe src.
|
||||
*/
|
||||
async getIframeSrc(module: CoreCourseModuleData): Promise<string> {
|
||||
if (!module.contents?.length) {
|
||||
if (!module.contents?.length || module.url === undefined) {
|
||||
throw new CoreError('No contents available in module');
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ export class AddonModResourceHelperProvider {
|
|||
}
|
||||
|
||||
try {
|
||||
const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url!);
|
||||
const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url);
|
||||
|
||||
// This URL is going to be injected in an iframe, we need trustAsResourceUrl to make it work in a browser.
|
||||
return CorePath.concatenatePaths(dirPath, mainFilePath);
|
||||
|
|
|
@ -59,7 +59,10 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
moduleName = 'scorm';
|
||||
|
||||
scorm?: AddonModScormScorm; // The SCORM object.
|
||||
currentOrganization: Partial<AddonModScormOrganization> = {}; // Selected organization.
|
||||
currentOrganization: Partial<AddonModScormOrganization> & { identifier: string} = {
|
||||
identifier: '',
|
||||
}; // Selected organization.
|
||||
|
||||
startNewAttempt = false;
|
||||
errorMessage?: string; // Error message.
|
||||
syncTime?: string; // Last sync time.
|
||||
|
@ -70,7 +73,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
percentage?: string; // Download/unzip percentage.
|
||||
showPercentage = false; // Whether to show the percentage.
|
||||
progressMessage?: string; // Message about download/unzip.
|
||||
organizations?: AddonModScormOrganization[]; // List of organizations.
|
||||
organizations: AddonModScormOrganization[] = []; // List of organizations.
|
||||
loadingToc = false; // Whether the TOC is being loaded.
|
||||
toc?: AddonModScormTOCScoWithIcon[]; // Table of contents (structure).
|
||||
accessInfo?: AddonModScormGetScormAccessInformationWSResponse; // Access information.
|
||||
|
@ -128,7 +131,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
|
||||
try {
|
||||
await AddonModScormPrefetchHandler.download(this.module, this.courseId, undefined, (data) => {
|
||||
if (!data) {
|
||||
if (!data || !this.scorm) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -137,8 +140,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
|
||||
if (data.downloading) {
|
||||
// Downloading package.
|
||||
if (this.scorm!.packagesize && data.progress) {
|
||||
const percentageNumber = Number(data.progress.loaded / this.scorm!.packagesize) * 100;
|
||||
if (this.scorm.packagesize && data.progress) {
|
||||
const percentageNumber = Number(data.progress.loaded / this.scorm.packagesize) * 100;
|
||||
this.percentage = percentageNumber.toFixed(1);
|
||||
this.showPercentage = percentageNumber >= 0 && percentageNumber <= 100;
|
||||
}
|
||||
|
@ -198,7 +201,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
(
|
||||
this.accessInfo.canskipview && !this.accessInfo.canviewreport &&
|
||||
(this.scorm.skipview ?? 0) >= AddonModScormProvider.SKIPVIEW_FIRST &&
|
||||
(this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0)
|
||||
(this.scorm.skipview === AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt === 0)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -221,7 +224,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
this.lastAttempt = attempt.num;
|
||||
this.lastIsOffline = attempt.offline;
|
||||
|
||||
if (this.lastAttempt != this.attempts.lastAttempt.num) {
|
||||
if (this.lastAttempt !== this.attempts.lastAttempt.num) {
|
||||
this.attemptToContinue = this.lastAttempt;
|
||||
} else {
|
||||
this.attemptToContinue = undefined;
|
||||
|
@ -237,7 +240,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
this.gradeMethodReadable = AddonModScorm.getScormGradeMethod(scorm);
|
||||
this.attemptsLeft = AddonModScorm.countAttemptsLeft(scorm, this.attempts.lastAttempt.num);
|
||||
|
||||
if (scorm.forcenewattempt == AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS ||
|
||||
if (scorm.forcenewattempt === AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS ||
|
||||
(scorm.forcenewattempt && !this.incomplete)) {
|
||||
this.startNewAttempt = true;
|
||||
}
|
||||
|
@ -272,13 +275,9 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
protected async fetchStructure(scorm: AddonModScormScorm): Promise<void> {
|
||||
this.organizations = await AddonModScorm.getOrganizations(scorm.id, { cmId: this.module.id });
|
||||
|
||||
if (!this.currentOrganization.identifier) {
|
||||
if (this.currentOrganization.identifier === '' && this.organizations[0]?.identifier) {
|
||||
// Load first organization (if any).
|
||||
if (this.organizations.length) {
|
||||
this.currentOrganization.identifier = this.organizations[0].identifier;
|
||||
} else {
|
||||
this.currentOrganization.identifier = '';
|
||||
}
|
||||
this.currentOrganization.identifier = this.organizations[0].identifier;
|
||||
}
|
||||
|
||||
return this.loadOrganizationToc(scorm, this.currentOrganization.identifier);
|
||||
|
@ -297,7 +296,11 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
offline: boolean,
|
||||
attempts: Record<number, AddonModScormAttemptGrade>,
|
||||
): Promise<void> {
|
||||
const grade = await AddonModScorm.getAttemptGrade(this.scorm!, attempt, offline);
|
||||
if (!this.scorm) {
|
||||
return;
|
||||
}
|
||||
|
||||
const grade = await AddonModScorm.getAttemptGrade(this.scorm, attempt, offline);
|
||||
|
||||
attempts[attempt] = {
|
||||
num: attempt,
|
||||
|
@ -438,8 +441,12 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
* Load a organization's TOC.
|
||||
*/
|
||||
async loadOrganization(): Promise<void> {
|
||||
if (!this.scorm) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.loadOrganizationToc(this.scorm!, this.currentOrganization.identifier!);
|
||||
await this.loadOrganizationToc(this.scorm, this.currentOrganization.identifier);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
|
||||
}
|
||||
|
@ -453,7 +460,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async loadOrganizationToc(scorm: AddonModScormScorm, organizationId: string): Promise<void> {
|
||||
if (!scorm.displaycoursestructure) {
|
||||
if (!scorm.displaycoursestructure || this.lastAttempt === undefined) {
|
||||
// TOC is not displayed, no need to load it.
|
||||
return;
|
||||
}
|
||||
|
@ -461,18 +468,17 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
this.loadingToc = true;
|
||||
|
||||
try {
|
||||
this.toc = await AddonModScormHelper.getToc(scorm.id, this.lastAttempt!, this.incomplete, {
|
||||
this.toc = await AddonModScormHelper.getToc(scorm.id, this.lastAttempt, this.incomplete, {
|
||||
organization: organizationId,
|
||||
offline: this.lastIsOffline,
|
||||
cmId: this.module.id,
|
||||
});
|
||||
|
||||
// Search organization title.
|
||||
this.organizations!.forEach((org) => {
|
||||
if (org.identifier == organizationId) {
|
||||
this.currentOrganization.title = org.title;
|
||||
}
|
||||
});
|
||||
const organization = this.organizations.find((org) => org.identifier === organizationId);
|
||||
if (organization) {
|
||||
this.currentOrganization.title = organization.title;
|
||||
}
|
||||
} finally {
|
||||
this.loadingToc = false;
|
||||
}
|
||||
|
@ -486,20 +492,18 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
* @param scoId SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO.
|
||||
*/
|
||||
async open(event?: Event, preview: boolean = false, scoId?: number): Promise<void> {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
|
||||
if (this.downloading) {
|
||||
if (this.downloading || !this.scorm) {
|
||||
// Scope is being downloaded, abort.
|
||||
return;
|
||||
}
|
||||
|
||||
const isOutdated = this.currentStatus == CoreConstants.OUTDATED;
|
||||
const scorm = this.scorm!;
|
||||
const isOutdated = this.currentStatus === CoreConstants.OUTDATED;
|
||||
const scorm = this.scorm;
|
||||
|
||||
if (!isOutdated && this.currentStatus != CoreConstants.NOT_DOWNLOADED) {
|
||||
if (!isOutdated && this.currentStatus !== CoreConstants.NOT_DOWNLOADED) {
|
||||
// Already downloaded, open it.
|
||||
this.openScorm(scoId, preview);
|
||||
|
||||
|
@ -552,7 +556,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
this.dataSentObserver?.off();
|
||||
|
||||
this.dataSentObserver = CoreEvents.on(AddonModScormProvider.DATA_SENT_EVENT, (data) => {
|
||||
if (data.scormId === this.scorm!.id) {
|
||||
if (data.scormId === this.scorm?.id) {
|
||||
this.dataSent = true;
|
||||
|
||||
if (this.module.completiondata && CoreCourse.isIncompleteAutomaticCompletion(this.module.completiondata)) {
|
||||
|
@ -581,14 +585,14 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
*/
|
||||
protected async showStatus(status: string): Promise<void> {
|
||||
|
||||
if (status == CoreConstants.OUTDATED && this.scorm) {
|
||||
if (status === CoreConstants.OUTDATED && this.scorm) {
|
||||
// Only show the outdated message if the file should be downloaded.
|
||||
const download = await AddonModScorm.shouldDownloadMainFile(this.scorm, true);
|
||||
|
||||
this.statusMessage = download ? 'addon.mod_scorm.scormstatusoutdated' : '';
|
||||
} else if (status == CoreConstants.NOT_DOWNLOADED) {
|
||||
} else if (status === CoreConstants.NOT_DOWNLOADED) {
|
||||
this.statusMessage = 'addon.mod_scorm.scormstatusnotdownloaded';
|
||||
} else if (status == CoreConstants.DOWNLOADING) {
|
||||
} else if (status === CoreConstants.DOWNLOADING) {
|
||||
if (!this.downloading) {
|
||||
// It's being downloaded right now but the view isn't tracking it. "Restore" the download.
|
||||
this.downloadScormPackage();
|
||||
|
@ -604,15 +608,19 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
* @param retries Number of retries done.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async sync(retries = 0): Promise<AddonModScormSyncResult> {
|
||||
if (CoreSync.isBlocked(AddonModScormProvider.COMPONENT, this.scorm!.id) && retries < 5) {
|
||||
protected async sync(retries = 0): Promise<AddonModScormSyncResult | undefined> {
|
||||
if (!this.scorm) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CoreSync.isBlocked(AddonModScormProvider.COMPONENT, this.scorm.id) && retries < 5) {
|
||||
// Sync is currently blocked, this can happen when SCORM player is left. Retry in a bit.
|
||||
await CoreUtils.wait(400);
|
||||
|
||||
return this.sync(retries + 1);
|
||||
}
|
||||
|
||||
const result = await AddonModScormSync.syncScorm(this.scorm!);
|
||||
const result = await AddonModScormSync.syncScorm(this.scorm);
|
||||
|
||||
if (!result.updated && this.dataSent) {
|
||||
// The user sent data to server, but not in the sync process. Check if we need to fetch data.
|
||||
|
|
|
@ -130,17 +130,18 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
: await AddonModSurveyOffline.hasAnswers(this.survey.id);
|
||||
|
||||
if (!this.survey.surveydone && !this.hasOffline) {
|
||||
await this.fetchQuestions();
|
||||
await this.fetchQuestions(this.survey.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get survey questions.
|
||||
*
|
||||
* @param surveyId Survey Id.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async fetchQuestions(): Promise<void> {
|
||||
const questions = await AddonModSurvey.getQuestions(this.survey!.id, { cmId: this.module.id });
|
||||
protected async fetchQuestions(surveyId: number): Promise<void> {
|
||||
const questions = await AddonModSurvey.getQuestions(surveyId, { cmId: this.module.id });
|
||||
|
||||
this.questions = AddonModSurveyHelper.formatQuestions(questions);
|
||||
|
||||
|
@ -183,6 +184,10 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
* Save options selected.
|
||||
*/
|
||||
async submit(): Promise<void> {
|
||||
if (!this.survey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let modal: CoreIonLoadingElement | undefined;
|
||||
|
||||
try {
|
||||
|
@ -198,7 +203,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
});
|
||||
}
|
||||
|
||||
const online = await AddonModSurvey.submitAnswers(this.survey!.id, this.survey!.name, this.courseId, answers);
|
||||
const online = await AddonModSurvey.submitAnswers(this.survey.id, this.survey.name, this.courseId, answers);
|
||||
|
||||
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: this.moduleName });
|
||||
|
||||
|
@ -235,8 +240,12 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
*
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected sync(): Promise<AddonModSurveySyncResult> {
|
||||
return AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId);
|
||||
protected async sync(): Promise<AddonModSurveySyncResult | void> {
|
||||
if (!this.survey) {
|
||||
return;
|
||||
}
|
||||
|
||||
return AddonModSurveySync.syncSurvey(this.survey.id, this.currentUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -189,7 +189,11 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
|
|||
*/
|
||||
go(): void {
|
||||
this.logView();
|
||||
AddonModUrlHelper.open(this.url!);
|
||||
if (!this.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddonModUrlHelper.open(this.url);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
cmId?: number; // Course module ID.
|
||||
courseId?: number; // Course the wiki belongs to.
|
||||
title?: string; // Title to display.
|
||||
pageForm?: FormGroup; // The form group.
|
||||
contentControl?: FormControl; // The FormControl for the page content.
|
||||
pageForm: FormGroup; // The form group.
|
||||
contentControl: FormControl; // The FormControl for the page content.
|
||||
canEditTitle = false; // Whether title can be edited.
|
||||
loaded = false; // Whether the data has been loaded.
|
||||
component = AddonModWikiProvider.COMPONENT; // Component to link the files to.
|
||||
|
@ -71,7 +71,10 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
|
||||
constructor(
|
||||
protected formBuilder: FormBuilder,
|
||||
) { }
|
||||
) {
|
||||
this.contentControl = this.formBuilder.control('');
|
||||
this.pageForm = this.formBuilder.group({});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
|
@ -96,10 +99,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
this.blockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId);
|
||||
|
||||
// Create the form group and its controls.
|
||||
this.contentControl = this.formBuilder.control('');
|
||||
this.pageForm = this.formBuilder.group({
|
||||
title: pageTitle,
|
||||
});
|
||||
this.pageForm.addControl('title', this.formBuilder.control(pageTitle));
|
||||
this.pageForm.addControl('text', this.contentControl);
|
||||
|
||||
// Block the wiki so it cannot be synced.
|
||||
|
@ -121,7 +121,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
if (success && !this.isDestroyed) {
|
||||
// Block the subwiki now that we have blockId for sure.
|
||||
const newBlockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId);
|
||||
if (newBlockId != this.blockId) {
|
||||
if (newBlockId !== this.blockId) {
|
||||
CoreSync.unblockOperation(this.component, this.blockId);
|
||||
this.blockId = newBlockId;
|
||||
CoreSync.blockOperation(this.component, this.blockId);
|
||||
|
@ -143,7 +143,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
|
||||
try {
|
||||
// Wait for sync to be over (if any).
|
||||
const syncResult = await AddonModWikiSync.waitForSync(this.blockId!);
|
||||
const syncResult = this.blockId ? await AddonModWikiSync.waitForSync(this.blockId) : undefined;
|
||||
|
||||
if (this.pageId) {
|
||||
// Editing a page that already exists.
|
||||
|
@ -154,7 +154,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
// Get page contents to obtain title and editing permission
|
||||
const pageContents = await AddonModWiki.getPageContents(this.pageId, { cmId: this.cmId });
|
||||
|
||||
this.pageForm!.controls.title.setValue(pageContents.title); // Set the title in the form group.
|
||||
this.pageForm.controls.title.setValue(pageContents.title); // Set the title in the form group.
|
||||
this.wikiId = pageContents.wikiid;
|
||||
this.subwikiId = pageContents.subwikiid;
|
||||
this.title = Translate.instant('addon.mod_wiki.editingpage', { $a: pageContents.title });
|
||||
|
@ -177,7 +177,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
// Get the original page contents, treating file URLs if needed.
|
||||
const content = CoreTextUtils.replacePluginfileUrls(editContents.content || '', this.subwikiFiles);
|
||||
|
||||
this.contentControl!.setValue(content);
|
||||
this.contentControl.setValue(content);
|
||||
this.originalContent = content;
|
||||
this.version = editContents.version;
|
||||
|
||||
|
@ -188,7 +188,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
}, AddonModWikiProvider.RENEW_LOCK_TIME);
|
||||
}
|
||||
} else {
|
||||
const pageTitle = this.pageForm!.controls.title.value;
|
||||
const pageTitle = this.pageForm.controls.title.value;
|
||||
this.editing = false;
|
||||
canEdit = !!this.blockId; // If no blockId, the user cannot edit the page.
|
||||
|
||||
|
@ -222,7 +222,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
|
||||
if (page) {
|
||||
// Load offline content.
|
||||
this.contentControl!.setValue(page.cachedcontent);
|
||||
this.contentControl.setValue(page.cachedcontent);
|
||||
this.originalContent = page.cachedcontent;
|
||||
this.editOffline = true;
|
||||
} else {
|
||||
|
@ -286,13 +286,17 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @param title Page title.
|
||||
*/
|
||||
protected goToPage(title: string): void {
|
||||
if (!this.wikiId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Not the firstpage.
|
||||
AddonModWiki.setEditedPageData({
|
||||
cmId: this.cmId,
|
||||
courseId: this.courseId,
|
||||
pageId: this.pageId,
|
||||
pageTitle: title,
|
||||
wikiId: this.wikiId!,
|
||||
wikiId: this.wikiId,
|
||||
subwikiId: this.subwikiId,
|
||||
userId: this.userId,
|
||||
groupId: this.groupId,
|
||||
|
@ -307,7 +311,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Whether data has changed.
|
||||
*/
|
||||
protected hasDataChanged(): boolean {
|
||||
const values = this.pageForm!.value;
|
||||
const values = this.pageForm.value;
|
||||
|
||||
return !(this.originalContent == values.text || (!this.editing && !values.text && !values.title));
|
||||
}
|
||||
|
@ -348,7 +352,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async save(): Promise<void> {
|
||||
const values = this.pageForm!.value;
|
||||
const values = this.pageForm.value;
|
||||
const title = values.title;
|
||||
let text = values.text;
|
||||
|
||||
|
@ -358,14 +362,14 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
text = CoreTextUtils.formatHtmlLines(text);
|
||||
|
||||
try {
|
||||
if (this.editing) {
|
||||
if (this.editing && this.pageId) {
|
||||
// Edit existing page.
|
||||
await AddonModWiki.editPage(this.pageId!, text, this.section);
|
||||
await AddonModWiki.editPage(this.pageId, text, this.section);
|
||||
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, true, CoreSites.getCurrentSiteId());
|
||||
|
||||
// Invalidate page since it changed.
|
||||
await AddonModWiki.invalidatePage(this.pageId!);
|
||||
await AddonModWiki.invalidatePage(this.pageId);
|
||||
|
||||
return this.goToPage(title);
|
||||
}
|
||||
|
@ -451,7 +455,11 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
|
|||
* Renew lock and control versions.
|
||||
*/
|
||||
protected async renewLock(): Promise<void> {
|
||||
const response = await AddonModWiki.getPageForEditing(this.pageId!, this.section, true);
|
||||
if (!this.pageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await AddonModWiki.getPageForEditing(this.pageId, this.section, true);
|
||||
|
||||
if (response.version && this.version != response.version) {
|
||||
this.wrongVersionLock = true;
|
||||
|
|
|
@ -103,6 +103,10 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle
|
|||
|
||||
try {
|
||||
const route = CoreNavigator.getCurrentRoute({ pageComponent: AddonModWikiIndexPage });
|
||||
if (!route) {
|
||||
// Current view isn't wiki index.
|
||||
return;
|
||||
}
|
||||
const subwikiId = parseInt(params.swid, 10);
|
||||
const wikiId = parseInt(params.wid, 10);
|
||||
let path = AddonModWikiModuleHandlerService.PAGE_NAME;
|
||||
|
@ -112,7 +116,7 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle
|
|||
|
||||
if (isSameWiki) {
|
||||
// User is seeing the wiki, we can get the module from the wiki params.
|
||||
path = path + `/${route!.snapshot.params.courseId}/${route!.snapshot.params.cmId}/edit`;
|
||||
path = path + `/${route.snapshot.params.courseId}/${route.snapshot.params.cmId}/edit`;
|
||||
} else if (wikiId) {
|
||||
// The URL specifies which wiki it belongs to. Get the module.
|
||||
const module = await CoreCourse.getModuleBasicInfoByInstance(
|
||||
|
|
|
@ -162,6 +162,10 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
|
|||
cmId: this.workshop.coursemodule,
|
||||
});
|
||||
|
||||
if (!this.data.assessment.form) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.edit) {
|
||||
try {
|
||||
const offlineAssessment = await AddonModWorkshopOffline.getAssessment(this.workshop.id, this.assessmentId);
|
||||
|
@ -176,7 +180,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
|
|||
}
|
||||
|
||||
// Override assessment plugins values.
|
||||
this.data.assessment.form!.current = AddonModWorkshop.parseFields(
|
||||
this.data.assessment.form.current = AddonModWorkshop.parseFields(
|
||||
CoreUtils.objectToArrayOfObjects(offlineData, 'name', 'value'),
|
||||
);
|
||||
|
||||
|
@ -221,7 +225,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
|
|||
try {
|
||||
this.data.selectedValues = await AddonWorkshopAssessmentStrategyDelegate.getOriginalValues(
|
||||
this.strategy,
|
||||
this.data.assessment.form!,
|
||||
this.data.assessment.form,
|
||||
this.workshop.id,
|
||||
);
|
||||
} finally {
|
||||
|
@ -245,7 +249,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
|
|||
* @returns True if data has changed.
|
||||
*/
|
||||
hasDataChanged(): boolean {
|
||||
if (!this.assessmentStrategyLoaded) {
|
||||
if (!this.assessmentStrategyLoaded || !this.workshop.strategy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -269,7 +273,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
|
|||
}
|
||||
|
||||
return AddonWorkshopAssessmentStrategyDelegate.hasDataChanged(
|
||||
this.workshop.strategy!,
|
||||
this.workshop.strategy,
|
||||
this.originalData.selectedValues,
|
||||
this.data.selectedValues,
|
||||
);
|
||||
|
@ -281,6 +285,10 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
|
|||
* @returns Promise resolved when done, rejected if assessment could not be saved.
|
||||
*/
|
||||
async saveAssessment(): Promise<void> {
|
||||
if (!this.data.assessment?.form) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = CoreFileSession.getFiles(
|
||||
AddonModWorkshopProvider.COMPONENT,
|
||||
this.workshop.id + '_' + this.assessmentId,
|
||||
|
@ -328,7 +336,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
|
|||
this.workshop,
|
||||
this.data.selectedValues,
|
||||
text,
|
||||
this.data.assessment!.form!,
|
||||
this.data.assessment.form,
|
||||
attachmentsId,
|
||||
);
|
||||
} catch (errors) {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import { Component, Input, OnDestroy, OnInit, Optional } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component';
|
||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
||||
import { IonContent } from '@ionic/angular';
|
||||
|
@ -264,7 +265,12 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
* @returns Resolved when done.
|
||||
*/
|
||||
async gotoSubmissionsPage(page: number): Promise<void> {
|
||||
const report = await AddonModWorkshop.getGradesReport(this.workshop!.id, {
|
||||
if (!this.workshop) {
|
||||
return;
|
||||
}
|
||||
const workshop = this.workshop;
|
||||
|
||||
const report = await AddonModWorkshop.getGradesReport(workshop.id, {
|
||||
groupId: this.group,
|
||||
page,
|
||||
cmId: this.module.id,
|
||||
|
@ -284,7 +290,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
await Promise.all(grades.map(async (grade) => {
|
||||
const submission: AddonModWorkshopSubmissionDataWithOfflineData = {
|
||||
id: grade.submissionid,
|
||||
workshopid: this.workshop!.id,
|
||||
workshopid: workshop.id,
|
||||
example: false,
|
||||
authorid: grade.userid,
|
||||
timecreated: grade.submissionmodified,
|
||||
|
@ -303,7 +309,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
reviewerof: this.parseReviewer(grade.reviewerof),
|
||||
};
|
||||
|
||||
if (this.workshop!.phase == AddonModWorkshopPhase.PHASE_ASSESSMENT) {
|
||||
if (workshop.phase == AddonModWorkshopPhase.PHASE_ASSESSMENT) {
|
||||
submission.reviewedbydone = grade.reviewedby?.reduce((a, b) => a + (b.grade ? 1 : 0), 0) || 0;
|
||||
submission.reviewerofdone = grade.reviewerof?.reduce((a, b) => a + (b.grade ? 1 : 0), 0) || 0;
|
||||
submission.reviewedbycount = grade.reviewedby?.length || 0;
|
||||
|
@ -313,7 +319,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
const offlineData = await AddonModWorkshopHelper.applyOfflineData(submission, this.offlineSubmissions);
|
||||
|
||||
if (offlineData !== undefined) {
|
||||
this.grades!.push(offlineData);
|
||||
this.grades.push(offlineData);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -358,8 +364,12 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
* Go to submit page.
|
||||
*/
|
||||
gotoSubmit(): void {
|
||||
if (this.canSubmit && ((this.access!.creatingsubmissionallowed && !this.submission) ||
|
||||
(this.access!.modifyingsubmissionallowed && this.submission))) {
|
||||
if (!this.canSubmit || !this.access) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((this.access.creatingsubmissionallowed && !this.submission) ||
|
||||
(this.access.modifyingsubmissionallowed && this.submission)) {
|
||||
const params: Params = {
|
||||
module: this.module,
|
||||
access: this.access,
|
||||
|
@ -378,20 +388,22 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
* View Phase info.
|
||||
*/
|
||||
async viewPhaseInfo(): Promise<void> {
|
||||
if (this.phases) {
|
||||
const modalData = await CoreDomUtils.openModal<boolean>({
|
||||
component: AddonModWorkshopPhaseInfoComponent,
|
||||
componentProps: {
|
||||
phases: CoreUtils.objectToArray(this.phases),
|
||||
workshopPhase: this.workshop!.phase,
|
||||
externalUrl: this.module.url,
|
||||
showSubmit: this.showSubmit,
|
||||
},
|
||||
});
|
||||
if (!this.phases || !this.workshop) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modalData === true) {
|
||||
this.gotoSubmit();
|
||||
}
|
||||
const modalData = await CoreDomUtils.openModal<boolean>({
|
||||
component: AddonModWorkshopPhaseInfoComponent,
|
||||
componentProps: {
|
||||
phases: CoreUtils.objectToArray(this.phases),
|
||||
workshopPhase: this.workshop.phase,
|
||||
externalUrl: this.module.url,
|
||||
showSubmit: this.showSubmit,
|
||||
},
|
||||
});
|
||||
|
||||
if (modalData === true) {
|
||||
this.gotoSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -413,26 +425,32 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async setPhaseInfo(): Promise<void> {
|
||||
if (!this.phases || !this.workshop || !this.access) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.submission = undefined;
|
||||
this.canAssess = false;
|
||||
this.assessments = [];
|
||||
this.userGrades = undefined;
|
||||
this.publishedSubmissions = [];
|
||||
|
||||
const workshop = this.workshop;
|
||||
|
||||
this.canSubmit = AddonModWorkshopHelper.canSubmit(
|
||||
this.workshop!,
|
||||
this.access!,
|
||||
this.phases![AddonModWorkshopPhase.PHASE_SUBMISSION].tasks,
|
||||
this.workshop,
|
||||
this.access,
|
||||
this.phases[AddonModWorkshopPhase.PHASE_SUBMISSION].tasks,
|
||||
);
|
||||
|
||||
this.showSubmit = this.canSubmit &&
|
||||
((this.access!.creatingsubmissionallowed && !this.submission) ||
|
||||
(this.access!.modifyingsubmissionallowed && !!this.submission));
|
||||
((this.access.creatingsubmissionallowed && !this.submission) ||
|
||||
(this.access.modifyingsubmissionallowed && !!this.submission));
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
if (this.canSubmit) {
|
||||
promises.push(AddonModWorkshopHelper.getUserSubmission(this.workshop!.id, { cmId: this.module.id })
|
||||
promises.push(AddonModWorkshopHelper.getUserSubmission(this.workshop.id, { cmId: this.module.id })
|
||||
.then(async (submission) => {
|
||||
this.submission = await AddonModWorkshopHelper.applyOfflineData(submission, this.offlineSubmissions);
|
||||
|
||||
|
@ -440,27 +458,27 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
}));
|
||||
}
|
||||
|
||||
if (this.access!.canviewallsubmissions && this.workshop!.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) {
|
||||
if (this.access.canviewallsubmissions && this.workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) {
|
||||
promises.push(this.gotoSubmissionsPage(this.page));
|
||||
}
|
||||
|
||||
let assessPromise = Promise.resolve();
|
||||
|
||||
if (this.workshop!.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT) {
|
||||
this.canAssess = AddonModWorkshopHelper.canAssess(this.workshop!, this.access!);
|
||||
if (this.workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT) {
|
||||
this.canAssess = AddonModWorkshopHelper.canAssess(this.workshop, this.access);
|
||||
|
||||
if (this.canAssess) {
|
||||
assessPromise = AddonModWorkshopHelper.getReviewerAssessments(this.workshop!.id, {
|
||||
assessPromise = AddonModWorkshopHelper.getReviewerAssessments(this.workshop.id, {
|
||||
cmId: this.module.id,
|
||||
}).then(async (assessments) => {
|
||||
await Promise.all(assessments.map(async (assessment) => {
|
||||
assessment.strategy = this.workshop!.strategy;
|
||||
assessment.strategy = workshop.strategy;
|
||||
if (!this.hasOffline) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const offlineAssessment = await AddonModWorkshopOffline.getAssessment(this.workshop!.id, assessment.id);
|
||||
const offlineAssessment = await AddonModWorkshopOffline.getAssessment(workshop.id, assessment.id);
|
||||
|
||||
assessment.offline = true;
|
||||
assessment.timemodified = Math.floor(offlineAssessment.timemodified / 1000);
|
||||
|
@ -477,27 +495,23 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
}
|
||||
}
|
||||
|
||||
if (this.workshop!.phase == AddonModWorkshopPhase.PHASE_CLOSED) {
|
||||
promises.push(AddonModWorkshop.getGrades(this.workshop!.id, { cmId: this.module.id }).then((grades) => {
|
||||
if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED) {
|
||||
promises.push(AddonModWorkshop.getGrades(this.workshop.id, { cmId: this.module.id }).then((grades) => {
|
||||
this.userGrades = grades.submissionlongstrgrade || grades.assessmentlongstrgrade ? grades : undefined;
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
if (this.access!.canviewpublishedsubmissions) {
|
||||
if (this.access.canviewpublishedsubmissions) {
|
||||
promises.push(assessPromise.then(async () => {
|
||||
const submissions: AddonModWorkshopSubmissionDataWithOfflineData[] =
|
||||
await AddonModWorkshop.getSubmissions(this.workshop!.id, { cmId: this.module.id });
|
||||
await AddonModWorkshop.getSubmissions(workshop.id, { cmId: this.module.id });
|
||||
|
||||
this.publishedSubmissions = submissions.filter((submission) => {
|
||||
if (submission.published) {
|
||||
submission.reviewedby = [];
|
||||
|
||||
this.assessments.forEach((assessment) => {
|
||||
if (assessment.submissionid == submission.id) {
|
||||
submission.reviewedby!.push(AddonModWorkshopHelper.realGradeValue(this.workshop!, assessment));
|
||||
}
|
||||
});
|
||||
submission.reviewedby =
|
||||
this.assessments.filter((assessment) => assessment.submissionid === submission.id)
|
||||
.map((assessment => AddonModWorkshopHelper.realGradeValue(workshop, assessment)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -519,7 +533,11 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected sync(): Promise<AddonModWorkshopSyncResult> {
|
||||
return AddonModWorkshopSync.syncWorkshop(this.workshop!.id);
|
||||
if (!this.workshop) {
|
||||
throw new CoreError('Cannot sync without a workshop.');
|
||||
}
|
||||
|
||||
return AddonModWorkshopSync.syncWorkshop(this.workshop.id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -385,11 +385,14 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca
|
|||
);
|
||||
newSubmissionId = false;
|
||||
} else {
|
||||
if (!submissionId) {
|
||||
throw new CoreError('Submission cannot be updated without a submissionId');
|
||||
}
|
||||
// Try to send it to server.
|
||||
// Don't allow offline if there are attachments since they were uploaded fine.
|
||||
newSubmissionId = await AddonModWorkshop.updateSubmission(
|
||||
this.workshopId,
|
||||
submissionId!,
|
||||
submissionId,
|
||||
this.courseId,
|
||||
inputData.title,
|
||||
inputData.content,
|
||||
|
|
|
@ -254,7 +254,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
|
|||
|
||||
return;
|
||||
}));
|
||||
} else if (this.currentUserId == this.userId && this.assessmentId) {
|
||||
} else if (this.currentUserId === this.userId && this.assessmentId) {
|
||||
// Get new data, different that came from stateParams.
|
||||
promises.push(AddonModWorkshop.getAssessment(this.workshopId, this.assessmentId, {
|
||||
cmId: this.module.id,
|
||||
|
@ -268,7 +268,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
|
|||
|
||||
return;
|
||||
}));
|
||||
} else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.userId == this.currentUserId) {
|
||||
} else if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED && this.userId === this.currentUserId) {
|
||||
const assessments = await AddonModWorkshop.getSubmissionAssessments(this.workshopId, this.submissionId, {
|
||||
cmId: this.module.id,
|
||||
});
|
||||
|
@ -276,7 +276,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
|
|||
this.submissionInfo.reviewedby = assessments.map((assessment) => this.parseAssessment(assessment));
|
||||
}
|
||||
|
||||
if (this.canAddFeedback || this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED) {
|
||||
if (this.canAddFeedback || this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED) {
|
||||
this.evaluate = {
|
||||
published: this.submission.published,
|
||||
text: this.submission.feedbackauthor || '',
|
||||
|
@ -284,54 +284,13 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
|
|||
}
|
||||
|
||||
if (this.canAddFeedback) {
|
||||
|
||||
if (!this.isDestroyed) {
|
||||
// Block the workshop.
|
||||
CoreSync.blockOperation(this.component, this.workshopId);
|
||||
}
|
||||
|
||||
const defaultGrade = Translate.instant('addon.mod_workshop.notoverridden');
|
||||
|
||||
promises.push(CoreGradesHelper.makeGradesMenu(this.workshop.grade || 0, undefined, defaultGrade, -1)
|
||||
.then(async (grades) => {
|
||||
this.evaluationGrades = grades;
|
||||
|
||||
this.evaluate!.grade = {
|
||||
label: CoreGradesHelper.getGradeLabelFromValue(grades, this.submissionInfo.gradeover) ||
|
||||
defaultGrade,
|
||||
value: this.submissionInfo.gradeover || -1,
|
||||
};
|
||||
|
||||
try {
|
||||
const offlineSubmission =
|
||||
await AddonModWorkshopOffline.getEvaluateSubmission(this.workshopId, this.submissionId);
|
||||
|
||||
this.hasOffline = true;
|
||||
this.evaluate!.published = offlineSubmission.published;
|
||||
this.evaluate!.text = offlineSubmission.feedbacktext;
|
||||
this.evaluate!.grade = {
|
||||
label: CoreGradesHelper.getGradeLabelFromValue(
|
||||
grades,
|
||||
parseInt(offlineSubmission.gradeover, 10),
|
||||
) || defaultGrade,
|
||||
value: offlineSubmission.gradeover || -1,
|
||||
};
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
this.hasOffline = false;
|
||||
} finally {
|
||||
this.originalEvaluation.published = this.evaluate!.published;
|
||||
this.originalEvaluation.text = this.evaluate!.text;
|
||||
this.originalEvaluation.grade = this.evaluate!.grade.value;
|
||||
|
||||
this.feedbackForm.controls['published'].setValue(this.evaluate!.published);
|
||||
this.feedbackForm.controls['grade'].setValue(this.evaluate!.grade.value);
|
||||
this.feedbackForm.controls['text'].setValue(this.evaluate!.text);
|
||||
}
|
||||
|
||||
return;
|
||||
}));
|
||||
} else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.submission.gradeoverby &&
|
||||
promises.push(this.fillEvaluationsGrades());
|
||||
} else if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED && this.submission.gradeoverby &&
|
||||
this.evaluate && this.evaluate.text) {
|
||||
promises.push(CoreUser.getProfile(this.submission.gradeoverby, this.courseId, true).then((profile) => {
|
||||
this.evaluateByProfile = profile;
|
||||
|
@ -362,6 +321,49 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill evaluation grade info.
|
||||
*/
|
||||
protected async fillEvaluationsGrades(): Promise<void> {
|
||||
const defaultGrade = Translate.instant('addon.mod_workshop.notoverridden');
|
||||
|
||||
this.evaluationGrades = await CoreGradesHelper.makeGradesMenu(this.workshop.grade || 0, undefined, defaultGrade, -1);
|
||||
|
||||
if (!this.evaluate) {
|
||||
// Should not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
this.evaluate.grade = {
|
||||
label: CoreGradesHelper.getGradeLabelFromValue(this.evaluationGrades, this.submissionInfo.gradeover) || defaultGrade,
|
||||
value: this.submissionInfo.gradeover || -1,
|
||||
};
|
||||
|
||||
try {
|
||||
const offlineSubmission = await AddonModWorkshopOffline.getEvaluateSubmission(this.workshopId, this.submissionId);
|
||||
|
||||
this.hasOffline = true;
|
||||
this.evaluate.published = offlineSubmission.published;
|
||||
this.evaluate.text = offlineSubmission.feedbacktext;
|
||||
this.evaluate.grade = {
|
||||
label: CoreGradesHelper.getGradeLabelFromValue(this.evaluationGrades, parseInt(offlineSubmission.gradeover, 10)) ||
|
||||
defaultGrade,
|
||||
value: offlineSubmission.gradeover || -1,
|
||||
};
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
this.hasOffline = false;
|
||||
} finally {
|
||||
this.originalEvaluation.published = this.evaluate.published;
|
||||
this.originalEvaluation.text = this.evaluate.text;
|
||||
this.originalEvaluation.grade = this.evaluate.grade.value;
|
||||
|
||||
this.feedbackForm.controls['published'].setValue(this.evaluate.published);
|
||||
this.feedbackForm.controls['grade'].setValue(this.evaluate.grade.value);
|
||||
this.feedbackForm.controls['text'].setValue(this.evaluate.text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse assessment to be shown.
|
||||
*
|
||||
|
|
|
@ -71,7 +71,7 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr
|
|||
): Promise<{ workshop?: AddonModWorkshopData; groups: CoreGroup[]; files: CoreWSFile[]}> {
|
||||
let groups: CoreGroup[] = [];
|
||||
let files: CoreWSFile[] = [];
|
||||
let workshop: AddonModWorkshopData | undefined;
|
||||
let workshop: AddonModWorkshopData;
|
||||
let access: AddonModWorkshopGetWorkshopAccessInformationWSResponse | undefined;
|
||||
|
||||
const modOptions = {
|
||||
|
@ -79,11 +79,25 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr
|
|||
...options, // Include all options.
|
||||
};
|
||||
|
||||
try {
|
||||
const site = await CoreSites.getSite(options.siteId);
|
||||
const userId = site.getUserId();
|
||||
const workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options);
|
||||
const site = await CoreSites.getSite(options.siteId);
|
||||
options.siteId = options.siteId ?? site.getId();
|
||||
const userId = site.getUserId();
|
||||
|
||||
try {
|
||||
workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options);
|
||||
} catch (error) {
|
||||
if (options.omitFail) {
|
||||
// Any error, return the info we have.
|
||||
return {
|
||||
groups: [],
|
||||
files: [],
|
||||
};
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
files = this.getIntroFilesFromInstance(module, workshop);
|
||||
files = files.concat(workshop.instructauthorsfiles || []).concat(workshop.instructreviewersfiles || []);
|
||||
|
||||
|
@ -124,7 +138,7 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr
|
|||
await Promise.all(submissions.map(async (submission) => {
|
||||
files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []);
|
||||
|
||||
const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop!.id, submission.id, {
|
||||
const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop.id, submission.id, {
|
||||
cmId: module.id,
|
||||
});
|
||||
|
||||
|
@ -133,9 +147,9 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr
|
|||
.concat(assessment.feedbackcontentfiles);
|
||||
});
|
||||
|
||||
if (workshop!.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) {
|
||||
if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) {
|
||||
await Promise.all(assessments.map((assessment) =>
|
||||
AddonModWorkshopHelper.getReviewerAssessmentById(workshop!.id, assessment.id)));
|
||||
AddonModWorkshopHelper.getReviewerAssessmentById(workshop.id, assessment.id)));
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -267,7 +281,12 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr
|
|||
|
||||
// Prefetch the workshop data.
|
||||
const info = await this.getWorkshopInfoHelper(module, courseId, commonOptions);
|
||||
const workshop = info.workshop!;
|
||||
if (!info.workshop) {
|
||||
// It would throw an exception so it would not happen.
|
||||
return;
|
||||
}
|
||||
|
||||
const workshop = info.workshop;
|
||||
const promises: Promise<unknown>[] = [];
|
||||
const assessmentIds: number[] = [];
|
||||
|
||||
|
|
|
@ -181,7 +181,7 @@ export class AddonModWorkshopHelperProvider {
|
|||
assessment = await AddonModWorkshop.getAssessment(workshopId, assessmentId, options);
|
||||
} catch (error) {
|
||||
const assessments = await AddonModWorkshop.getReviewerAssessments(workshopId, options);
|
||||
assessment = assessments.find((assessment_1) => assessment_1.id == assessmentId);
|
||||
assessment = assessments.find((ass) => ass.id === assessmentId);
|
||||
|
||||
if (!assessment) {
|
||||
throw error;
|
||||
|
@ -266,9 +266,7 @@ export class AddonModWorkshopHelperProvider {
|
|||
* Upload or store some files for a submission, depending if the user is offline or not.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param submissionId If not editing, it will refer to timecreated.
|
||||
* @param files List of files.
|
||||
* @param editing If the submission is being edited or added otherwise.
|
||||
* @param offline True if files sould be stored for offline, false to upload them.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @returns Promise resolved if success.
|
||||
|
@ -451,30 +449,26 @@ export class AddonModWorkshopHelperProvider {
|
|||
* @returns Promise resolved with the files.
|
||||
*/
|
||||
async applyOfflineData(
|
||||
submission?: AddonModWorkshopSubmissionDataWithOfflineData,
|
||||
submission: AddonModWorkshopSubmissionDataWithOfflineData = {
|
||||
id: 0,
|
||||
workshopid: 0,
|
||||
title: '',
|
||||
content: '',
|
||||
timemodified: 0,
|
||||
example: false,
|
||||
authorid: 0,
|
||||
timecreated: 0,
|
||||
contenttrust: 0,
|
||||
attachment: 0,
|
||||
published: false,
|
||||
late: 0,
|
||||
},
|
||||
actions: AddonModWorkshopOfflineSubmission[] = [],
|
||||
): Promise<AddonModWorkshopSubmissionDataWithOfflineData | undefined> {
|
||||
if (actions.length == 0) {
|
||||
if (actions.length === 0) {
|
||||
return submission;
|
||||
}
|
||||
|
||||
if (submission === undefined) {
|
||||
submission = {
|
||||
id: 0,
|
||||
workshopid: 0,
|
||||
title: '',
|
||||
content: '',
|
||||
timemodified: 0,
|
||||
example: false,
|
||||
authorid: 0,
|
||||
timecreated: 0,
|
||||
contenttrust: 0,
|
||||
attachment: 0,
|
||||
published: false,
|
||||
late: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let attachmentsId: CoreFileUploaderStoreFilesResult | undefined;
|
||||
const workshopId = actions[0].workshopid;
|
||||
|
||||
|
@ -482,17 +476,17 @@ export class AddonModWorkshopHelperProvider {
|
|||
switch (action.action) {
|
||||
case AddonModWorkshopAction.ADD:
|
||||
case AddonModWorkshopAction.UPDATE:
|
||||
submission!.title = action.title;
|
||||
submission!.content = action.content;
|
||||
submission!.title = action.title;
|
||||
submission!.courseid = action.courseid;
|
||||
submission!.submissionmodified = action.timemodified / 1000;
|
||||
submission!.offline = true;
|
||||
submission.title = action.title;
|
||||
submission.content = action.content;
|
||||
submission.title = action.title;
|
||||
submission.courseid = action.courseid;
|
||||
submission.submissionmodified = action.timemodified / 1000;
|
||||
submission.offline = true;
|
||||
attachmentsId = action.attachmentsid as CoreFileUploaderStoreFilesResult;
|
||||
break;
|
||||
case AddonModWorkshopAction.DELETE:
|
||||
submission!.deleted = true;
|
||||
submission!.submissionmodified = action.timemodified / 1000;
|
||||
submission.deleted = true;
|
||||
submission.submissionmodified = action.timemodified / 1000;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
@ -534,7 +528,8 @@ export class AddonModWorkshopHelperProvider {
|
|||
}
|
||||
|
||||
const data =
|
||||
(await AddonWorkshopAssessmentStrategyDelegate.prepareAssessmentData(workshop.strategy!, selectedValues, form)) || {};
|
||||
(await AddonWorkshopAssessmentStrategyDelegate.prepareAssessmentData(workshop.strategy ?? '', selectedValues, form)) ||
|
||||
{};
|
||||
data.feedbackauthor = feedbackText;
|
||||
data.feedbackauthorattachmentsid = attachmentsId;
|
||||
data.nodims = form.dimenssionscount;
|
||||
|
@ -551,16 +546,16 @@ export class AddonModWorkshopHelperProvider {
|
|||
* @returns Real grade formatted.
|
||||
*/
|
||||
protected realGradeValueHelper(value?: number | string, max = 0, decimals = 0): string | undefined {
|
||||
if (typeof value == 'string') {
|
||||
if (typeof value === 'string') {
|
||||
// Already treated.
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value == null || value === undefined) {
|
||||
if (value === null || value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (max == 0) {
|
||||
if (max === 0) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
|
|
|
@ -610,13 +610,16 @@ export class AddonModWorkshopProvider {
|
|||
grades: AddonModWorkshopGradesData[],
|
||||
options: AddonModWorkshopGetGradesReportOptions = {},
|
||||
): Promise<AddonModWorkshopGradesData[]> {
|
||||
options.page = options.page ?? 0;
|
||||
options.perPage = options.perPage ?? AddonModWorkshopProvider.PER_PAGE;
|
||||
|
||||
const report = await this.getGradesReport(workshopId, options);
|
||||
|
||||
Array.prototype.push.apply(grades, report.grades);
|
||||
const canLoadMore = ((options.page! + 1) * options.perPage!) < report.totalcount;
|
||||
const canLoadMore = ((options.page + 1) * options.perPage) < report.totalcount;
|
||||
|
||||
if (canLoadMore) {
|
||||
options.page!++;
|
||||
options.page++;
|
||||
|
||||
return this.fetchGradeReportsRecursive(workshopId, grades, options);
|
||||
}
|
||||
|
@ -778,7 +781,11 @@ export class AddonModWorkshopProvider {
|
|||
// Other errors ocurring.
|
||||
CoreWS.throwOnFailedStatus(response, 'Add submission failed');
|
||||
|
||||
return response.submissionid!;
|
||||
if (!response.submissionid) {
|
||||
throw new CoreError('Add submission failed, no submission id was returned');
|
||||
}
|
||||
|
||||
return response.submissionid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -145,7 +145,7 @@ export function conditionalRoutes(routes: Routes, condition: () => boolean): Rou
|
|||
|
||||
return {
|
||||
...newRoute,
|
||||
matcher: buildConditionalUrlMatcher(matcher || path!, condition),
|
||||
matcher: buildConditionalUrlMatcher(matcher || path || '', condition),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -91,7 +91,12 @@ export class CoreQueueRunner {
|
|||
return;
|
||||
}
|
||||
|
||||
const item = this.orderedQueue.shift()!;
|
||||
const item = this.orderedQueue.shift();
|
||||
if (!item) {
|
||||
// No item found.
|
||||
return;
|
||||
}
|
||||
|
||||
this.numberRunning++;
|
||||
|
||||
try {
|
||||
|
|
|
@ -323,7 +323,7 @@ export class SQLiteDB {
|
|||
async close(): Promise<void> {
|
||||
await this.ready();
|
||||
|
||||
await this.db!.close();
|
||||
await this.db?.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -501,7 +501,7 @@ export class SQLiteDB {
|
|||
async execute(sql: string, params?: SQLiteDBRecordValue[]): Promise<any> {
|
||||
await this.ready();
|
||||
|
||||
return this.db!.executeSql(sql, params);
|
||||
return this.db?.executeSql(sql, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -516,7 +516,7 @@ export class SQLiteDB {
|
|||
async executeBatch(sqlStatements: (string | string[] | any)[]): Promise<void> {
|
||||
await this.ready();
|
||||
|
||||
await this.db!.sqlBatch(sqlStatements);
|
||||
await this.db?.sqlBatch(sqlStatements);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -544,7 +544,7 @@ export class SQLiteDB {
|
|||
* @returns List of params.
|
||||
*/
|
||||
protected formatDataToSQLParams(data: SQLiteDBRecordValues): SQLiteDBRecordValue[] {
|
||||
return Object.keys(data).map((key) => data[key]!);
|
||||
return Object.values(data).map((value) => value as SQLiteDBRecordValue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -958,7 +958,7 @@ export class SQLiteDB {
|
|||
async open(): Promise<void> {
|
||||
await this.ready();
|
||||
|
||||
await this.db!.open();
|
||||
await this.db?.open();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('CoreError', () => {
|
|||
// Arrange
|
||||
const message = Faker.lorem.sentence();
|
||||
|
||||
let error: CoreError | null = null;
|
||||
let error: CoreError;
|
||||
|
||||
// Act
|
||||
try {
|
||||
|
@ -37,10 +37,10 @@ describe('CoreError', () => {
|
|||
expect(error).not.toBeNull();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error).toBeInstanceOf(CoreError);
|
||||
expect(error!.name).toEqual('CoreError');
|
||||
expect(error!.message).toEqual(message);
|
||||
expect(error!.stack).not.toBeNull();
|
||||
expect(error!.stack).toContain(agnosticPath('src/core/classes/tests/error.test.ts'));
|
||||
expect(error.name).toEqual('CoreError');
|
||||
expect(error.message).toEqual(message);
|
||||
expect(error.stack).not.toBeNull();
|
||||
expect(error.stack).toContain(agnosticPath('src/core/classes/tests/error.test.ts'));
|
||||
});
|
||||
|
||||
it('can be subclassed', () => {
|
||||
|
@ -55,7 +55,7 @@ describe('CoreError', () => {
|
|||
|
||||
const message = Faker.lorem.sentence();
|
||||
|
||||
let error: CustomCoreError | null = null;
|
||||
let error: CustomCoreError;
|
||||
|
||||
// Act
|
||||
try {
|
||||
|
@ -69,10 +69,10 @@ describe('CoreError', () => {
|
|||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error).toBeInstanceOf(CoreError);
|
||||
expect(error).toBeInstanceOf(CustomCoreError);
|
||||
expect(error!.name).toEqual('CustomCoreError');
|
||||
expect(error!.message).toEqual(`Custom message: ${message}`);
|
||||
expect(error!.stack).not.toBeNull();
|
||||
expect(error!.stack).toContain(agnosticPath('src/core/classes/tests/error.test.ts'));
|
||||
expect(error.name).toEqual('CustomCoreError');
|
||||
expect(error.message).toEqual(`Custom message: ${message}`);
|
||||
expect(error.stack).not.toBeNull();
|
||||
expect(error.stack).toContain(agnosticPath('src/core/classes/tests/error.test.ts'));
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -46,7 +46,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
})
|
||||
export class CoreAttachmentsComponent implements OnInit {
|
||||
|
||||
@Input() files?: CoreFileEntry[]; // List of attachments. New attachments will be added to this array.
|
||||
@Input() files: CoreFileEntry[] = []; // List of attachments. New attachments will be added to this array.
|
||||
@Input() maxSize?: number; // Max size. -1 means unlimited, 0 means course/user max size, not defined means unknown.
|
||||
@Input() maxSubmissions?: number; // Max number of attachments. -1 means unlimited, not defined means unknown limit.
|
||||
@Input() component?: string; // Component the downloaded files will be linked to.
|
||||
|
@ -177,7 +177,7 @@ export class CoreAttachmentsComponent implements OnInit {
|
|||
* @param data The data received.
|
||||
*/
|
||||
renamed(index: number, data: { file: FileEntry }): void {
|
||||
this.files![index] = data.file;
|
||||
this.files[index] = data.file;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,11 +71,11 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges {
|
|||
generateLabels: (chart: Chart): ChartLegendLabelItem[] => {
|
||||
const data = chart.data;
|
||||
if (data.labels?.length) {
|
||||
const datasets = data.datasets![0];
|
||||
const datasets = data.datasets?.[0];
|
||||
|
||||
return data.labels.map((label, i) => ({
|
||||
text: label + ': ' + datasets.data![i],
|
||||
fillStyle: datasets.backgroundColor![i],
|
||||
return data.labels.map<ChartLegendLabelItem>((label, i) => ({
|
||||
text: label + ': ' + datasets?.data?.[i],
|
||||
fillStyle: datasets?.backgroundColor?.[i],
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -87,14 +87,18 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges {
|
|||
legend = Object.assign({}, this.legend);
|
||||
}
|
||||
|
||||
if (this.type == 'bar' && this.data.length >= 5) {
|
||||
if (this.type === 'bar' && this.data.length >= 5) {
|
||||
this.type = 'horizontalBar';
|
||||
}
|
||||
|
||||
// Format labels if needed.
|
||||
await this.formatLabels();
|
||||
|
||||
const context = this.canvas!.nativeElement.getContext('2d')!;
|
||||
const context = this.canvas?.nativeElement.getContext('2d');
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.chart = new Chart(context, {
|
||||
type: this.type,
|
||||
data: {
|
||||
|
@ -123,7 +127,11 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges {
|
|||
await this.formatLabels();
|
||||
}
|
||||
|
||||
this.chart.data.datasets![0] = {
|
||||
if (!this.chart.data.datasets) {
|
||||
this.chart.data.datasets = [];
|
||||
}
|
||||
|
||||
this.chart.data.datasets[0] = {
|
||||
data: this.data,
|
||||
backgroundColor: this.getRandomColors(this.data.length),
|
||||
};
|
||||
|
|
|
@ -149,24 +149,30 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
* @param isOpenButton Whether the open button was clicked.
|
||||
* @returns Promise resolved when file is opened.
|
||||
*/
|
||||
openFile(ev?: Event, isOpenButton = false): Promise<void> {
|
||||
async openFile(ev?: Event, isOpenButton = false): Promise<void> {
|
||||
ev?.preventDefault();
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (!this.file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options: CoreUtilsOpenFileOptions = {};
|
||||
if (isOpenButton) {
|
||||
// Use the non-default method.
|
||||
options.iOSOpenFileAction = this.defaultIsOpenWithPicker ? OpenFileAction.OPEN : OpenFileAction.OPEN_WITH;
|
||||
}
|
||||
|
||||
return CoreFileHelper.downloadAndOpenFile(this.file!, this.component, this.componentId, this.state, (event) => {
|
||||
if (event && 'calculating' in event && event.calculating) {
|
||||
// The process is calculating some data required for the download, show the spinner.
|
||||
this.isDownloading = true;
|
||||
}
|
||||
}, undefined, options).catch((error) => {
|
||||
try {
|
||||
return await CoreFileHelper.downloadAndOpenFile(this.file, this.component, this.componentId, this.state, (event) => {
|
||||
if (event && 'calculating' in event && event.calculating) {
|
||||
// The process is calculating some data required for the download, show the spinner.
|
||||
this.isDownloading = true;
|
||||
}
|
||||
}, undefined, options);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -264,7 +270,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.observer?.off();
|
||||
|
|
|
@ -30,7 +30,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
})
|
||||
export class CoreFilesComponent implements OnInit, DoCheck {
|
||||
|
||||
@Input() files?: CoreFileEntry[]; // List of files.
|
||||
@Input() files: CoreFileEntry[] = []; // List of files.
|
||||
@Input() component?: string; // Component the downloaded files will be linked to.
|
||||
@Input() componentId?: string | number; // Component ID.
|
||||
@Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded.
|
||||
|
@ -75,7 +75,7 @@ export class CoreFilesComponent implements OnInit, DoCheck {
|
|||
* Calculate contentText based on fils that can be rendered inline.
|
||||
*/
|
||||
protected renderInlineFiles(): void {
|
||||
this.contentText = this.files!.reduce((previous, file) => {
|
||||
this.contentText = this.files.reduce((previous, file) => {
|
||||
const text = CoreMimetypeUtils.getEmbeddedHtml(file);
|
||||
|
||||
return text ? previous + '<br>' + text : previous;
|
||||
|
|
|
@ -28,10 +28,10 @@ import { CorePath } from '@singletons/path';
|
|||
})
|
||||
export class CoreRecaptchaComponent implements OnInit {
|
||||
|
||||
@Input() model?: Record<string, string>; // The model where to store the recaptcha response.
|
||||
@Input() model: Record<string, string> = {}; // The model where to store the recaptcha response.
|
||||
@Input() publicKey?: string; // The site public key.
|
||||
@Input() modelValueName = 'recaptcharesponse'; // Name of the model property where to store the response.
|
||||
@Input() siteUrl?: string; // The site URL. If not defined, current site.
|
||||
@Input() siteUrl = ''; // The site URL. If not defined, current site.
|
||||
|
||||
expired = false;
|
||||
|
||||
|
@ -45,7 +45,7 @@ export class CoreRecaptchaComponent implements OnInit {
|
|||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.siteUrl = this.siteUrl || CoreSites.getCurrentSite()?.getURL();
|
||||
this.siteUrl = this.siteUrl || CoreSites.getRequiredCurrentSite().getURL();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +62,7 @@ export class CoreRecaptchaComponent implements OnInit {
|
|||
// Open the recaptcha challenge in an InAppBrowser.
|
||||
// The app used to use an iframe for this, but the app can no longer access the iframe to create the required callbacks.
|
||||
// The app cannot render the recaptcha directly because it has problems with the local protocols and domains.
|
||||
const src = CorePath.concatenatePaths(this.siteUrl!, 'webservice/recaptcha.php?lang=' + this.lang);
|
||||
const src = CorePath.concatenatePaths(this.siteUrl, 'webservice/recaptcha.php?lang=' + this.lang);
|
||||
|
||||
const inAppBrowserWindow = CoreUtils.openInApp(src);
|
||||
if (!inAppBrowserWindow) {
|
||||
|
@ -90,7 +90,7 @@ export class CoreRecaptchaComponent implements OnInit {
|
|||
this.expireRecaptchaAnswer();
|
||||
} else if (event.data.action == 'callback') {
|
||||
this.expired = false;
|
||||
this.model![this.modelValueName] = event.data.value;
|
||||
this.model[this.modelValueName] = event.data.value;
|
||||
|
||||
// Close the InAppBrowser now.
|
||||
inAppBrowserWindow.close();
|
||||
|
@ -105,7 +105,7 @@ export class CoreRecaptchaComponent implements OnInit {
|
|||
*/
|
||||
expireRecaptchaAnswer(): void {
|
||||
this.expired = true;
|
||||
this.model![this.modelValueName] = '';
|
||||
this.model[this.modelValueName] = '';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges {
|
|||
let iconName = this.name;
|
||||
let font = 'ionicons';
|
||||
const parts = iconName.split('-', 2);
|
||||
if (parts.length == 2) {
|
||||
if (parts.length === 2) {
|
||||
switch (parts[0]) {
|
||||
case 'far':
|
||||
library = 'regular';
|
||||
|
@ -82,7 +82,7 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges {
|
|||
}
|
||||
}
|
||||
|
||||
if (font == 'ionicons') {
|
||||
if (font === 'ionicons') {
|
||||
this.element.removeAttribute('src');
|
||||
this.logger.warn(`Ionic icon ${this.name} detected`);
|
||||
|
||||
|
@ -103,7 +103,7 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges {
|
|||
ngAfterViewInit(): void {
|
||||
if (!this.element.getAttribute('aria-label') &&
|
||||
!this.element.getAttribute('aria-labelledby') &&
|
||||
this.element.getAttribute('aria-hidden') != 'true') {
|
||||
this.element.getAttribute('aria-hidden') !== 'true') {
|
||||
this.logger.warn('Aria label not set on icon ' + this.name, this.element);
|
||||
|
||||
this.element.setAttribute('aria-hidden', 'true');
|
||||
|
@ -111,7 +111,7 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges {
|
|||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (!changes.name || !this.name) {
|
||||
|
|
|
@ -36,19 +36,23 @@ export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent implemen
|
|||
async ngOnInit(): Promise<void> {
|
||||
await super.ngOnInit();
|
||||
|
||||
this.fetchContentDefaultError = 'Error getting ' + this.block.contents?.title + ' data.';
|
||||
this.fetchContentDefaultError = `Error getting ${this.block.contents?.title} data.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the block page.
|
||||
*/
|
||||
gotoBlock(): void {
|
||||
if (!this.link) {
|
||||
return;
|
||||
}
|
||||
|
||||
const navOptions = this.navOptions || {};
|
||||
if (this.linkParams) {
|
||||
navOptions.params = this.linkParams;
|
||||
}
|
||||
|
||||
CoreNavigator.navigateToSitePath(this.link!, navOptions);
|
||||
CoreNavigator.navigateToSitePath(this.link, navOptions);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
try {
|
||||
|
@ -445,9 +445,13 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy {
|
|||
* @returns Promise resolved with modified comment when done.
|
||||
*/
|
||||
protected async loadCommentProfile(comment: CoreCommentsDataToDisplay): Promise<CoreCommentsDataToDisplay> {
|
||||
// Get the user profile image.
|
||||
if (!comment.userid) {
|
||||
return comment;
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await CoreUser.getProfile(comment.userid!, undefined, true);
|
||||
// Get the user profile image.
|
||||
const user = await CoreUser.getProfile(comment.userid, undefined, true);
|
||||
comment.profileimageurl = user.profileimageurl;
|
||||
comment.fullname = user.fullname;
|
||||
} catch {
|
||||
|
@ -599,7 +603,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.syncObserver?.off();
|
||||
|
|
|
@ -152,7 +152,7 @@ export class CoreCommentsProvider {
|
|||
this.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId),
|
||||
);
|
||||
|
||||
return commentsResponse![0];
|
||||
return commentsResponse[0];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,9 +166,9 @@ export class CoreCommentsProvider {
|
|||
async addCommentsOnline(
|
||||
comments: CoreCommentsCommentBasicData[],
|
||||
siteId?: string,
|
||||
): Promise<CoreCommentsAddCommentsWSResponse | undefined> {
|
||||
): Promise<CoreCommentsAddCommentsWSResponse> {
|
||||
if (!comments || !comments.length) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
@ -231,8 +231,12 @@ export class CoreCommentsProvider {
|
|||
|
||||
// Convenience function to store the action to be synchronized later.
|
||||
const storeOffline = async (): Promise<boolean> => {
|
||||
if (!comment.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await CoreCommentsOffline.deleteComment(
|
||||
comment.id!,
|
||||
comment.id,
|
||||
comment.contextlevel,
|
||||
comment.instanceid,
|
||||
comment.component,
|
||||
|
|
|
@ -67,7 +67,7 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
|
|||
courseId?: number,
|
||||
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
|
||||
courseId = Number(courseId || params.courseid || params.cid);
|
||||
const courseIdentifier = Number(courseId || params.courseid || params.cid);
|
||||
|
||||
return [{
|
||||
action: async (siteId): Promise<void> => {
|
||||
|
@ -79,14 +79,14 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
|
|||
CoreCourseHelper.navigateToModule(
|
||||
Number(params.id),
|
||||
{
|
||||
courseId,
|
||||
courseId: courseIdentifier,
|
||||
modName: this.useModNameToGetModule ? this.modName : undefined,
|
||||
siteId,
|
||||
},
|
||||
);
|
||||
} else if (this.canReview) {
|
||||
// Use the goToReview function.
|
||||
this.goToReview(url, params, courseId!, siteId);
|
||||
this.goToReview(url, params, courseIdentifier, siteId);
|
||||
} else {
|
||||
// Not current user and cannot review it in the app, open it in browser.
|
||||
site.openInBrowserWithAutoLogin(url);
|
||||
|
|
|
@ -76,6 +76,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
||||
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent<any>>;
|
||||
|
||||
// All the possible component classes.
|
||||
|
|
|
@ -58,16 +58,19 @@ export class CoreCourseDownloadModuleMainFileDirective implements OnInit {
|
|||
ev.stopPropagation();
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading();
|
||||
const courseId = typeof this.courseId == 'string' ? parseInt(this.courseId, 10) : this.courseId;
|
||||
const courseId = this.courseId ? Number(this.courseId) : undefined;
|
||||
|
||||
try {
|
||||
if (!this.module) {
|
||||
// Try to get the module from cache.
|
||||
this.moduleId = typeof this.moduleId == 'string' ? parseInt(this.moduleId, 10) : this.moduleId;
|
||||
this.module = await CoreCourse.getModule(this.moduleId!, courseId);
|
||||
const moduleId = Number(this.moduleId);
|
||||
if (!moduleId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.module = await CoreCourse.getModule(moduleId, courseId);
|
||||
}
|
||||
|
||||
const componentId = this.componentId || module.id;
|
||||
const componentId = this.componentId ? Number(this.componentId) : this.module.id;
|
||||
|
||||
await CoreCourseHelper.downloadModuleAndOpenFile(
|
||||
this.module,
|
||||
|
|
|
@ -448,7 +448,11 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
? (handler as CoreCourseOptionsMenuHandler).getMenuDisplayData
|
||||
: (handler as CoreCourseOptionsHandler).getDisplayData;
|
||||
|
||||
promises.push(Promise.resolve(getFunction!.call(handler, courseWithOptions)).then((data) => {
|
||||
if (!getFunction) {
|
||||
return;
|
||||
}
|
||||
|
||||
promises.push(Promise.resolve(getFunction.call(handler, courseWithOptions)).then((data) => {
|
||||
handlersToDisplay.push({
|
||||
data: data,
|
||||
priority: handler.priority || 0,
|
||||
|
@ -468,7 +472,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
handlersToDisplay.sort((
|
||||
a: CoreCourseOptionsHandlerToDisplay | CoreCourseOptionsMenuHandlerToDisplay,
|
||||
b: CoreCourseOptionsHandlerToDisplay | CoreCourseOptionsMenuHandlerToDisplay,
|
||||
) => b.priority! - a.priority!);
|
||||
) => (b.priority || 0) - (a.priority || 0));
|
||||
|
||||
return handlersToDisplay;
|
||||
}
|
||||
|
|
|
@ -1274,9 +1274,9 @@ export class CoreCourseProvider {
|
|||
if (!result.status) {
|
||||
if (result.warnings && result.warnings.length) {
|
||||
throw new CoreWSError(result.warnings[0]);
|
||||
} else {
|
||||
throw new CoreError('Cannot change completion.');
|
||||
}
|
||||
|
||||
throw new CoreError('Cannot change completion.');
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -119,24 +119,20 @@ export class CoreEmulatorCaptureHelperProvider {
|
|||
|
||||
if (mimetypes?.length) {
|
||||
// Search for a supported mimetype.
|
||||
for (let i = 0; i < mimetypes.length; i++) {
|
||||
const mimetype = mimetypes[i];
|
||||
result.mimetype = mimetypes.find((mimetype) => {
|
||||
const matches = mimetype.match(new RegExp('^' + type + '/'));
|
||||
|
||||
if (matches?.length && window.MediaRecorder.isTypeSupported(mimetype)) {
|
||||
result.mimetype = mimetype;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return matches?.length && window.MediaRecorder.isTypeSupported(mimetype);
|
||||
});
|
||||
}
|
||||
|
||||
if (result.mimetype) {
|
||||
// Found a supported mimetype in the mimetypes array, get the extension.
|
||||
result.extension = CoreMimetypeUtils.getExtension(result.mimetype);
|
||||
} else if (type == 'video') {
|
||||
} else if (type === 'video' && this.videoMimeType) {
|
||||
// No mimetype found, use default extension.
|
||||
result.mimetype = this.videoMimeType;
|
||||
result.extension = this.possibleVideoMimeTypes[result.mimetype!];
|
||||
result.extension = this.possibleVideoMimeTypes[result.mimetype];
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -76,7 +76,9 @@ export class FileTransferObjectMock extends FileTransferObject {
|
|||
abort(): void {
|
||||
if (this.xhr) {
|
||||
this.xhr.abort();
|
||||
this.reject!(new FileTransferErrorMock(FileTransferErrorMock.ABORT_ERR, this.source!, this.target!, 0, '', ''));
|
||||
this.reject?.(
|
||||
new FileTransferErrorMock(FileTransferErrorMock.ABORT_ERR, this.source || '', this.target || '', 0, '', ''),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ export class CoreFileUploaderDelegateService extends CoreDelegate<CoreFileUpload
|
|||
}
|
||||
|
||||
// Sort them by priority.
|
||||
handlers.sort((a, b) => a.priority! <= b.priority! ? 1 : -1);
|
||||
handlers.sort((a, b) => (a.priority || 0) <= (b.priority || 0) ? 1 : -1);
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
|
|
@ -806,8 +806,8 @@ export class CoreFileUploaderHelperProvider {
|
|||
}
|
||||
}
|
||||
|
||||
if (maxSize != -1 && size > maxSize) {
|
||||
throw this.createMaxBytesError(maxSize, file!.name);
|
||||
if (maxSize != -1 && size > maxSize && file) {
|
||||
throw this.createMaxBytesError(maxSize, file.name);
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
|
@ -849,12 +849,12 @@ export class CoreFileUploaderHelperProvider {
|
|||
stringKey: string,
|
||||
progress: ProgressEvent | CoreFileProgressEvent,
|
||||
): void {
|
||||
if (!progress || !progress.lengthComputable) {
|
||||
if (!progress || !progress.lengthComputable || progress.loaded === undefined || !progress.total) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the progress percentage.
|
||||
const perc = Math.min((progress.loaded! / progress.total!) * 100, 100);
|
||||
const perc = Math.min((progress.loaded / progress.total) * 100, 100);
|
||||
|
||||
if (isNaN(perc) || perc < 0) {
|
||||
return;
|
||||
|
|
|
@ -286,8 +286,8 @@ export class CoreFileUploaderProvider {
|
|||
|
||||
if (!stillInList) {
|
||||
filesToDelete.push({
|
||||
filepath: file.filepath!,
|
||||
filename: file.filename!,
|
||||
filepath: file.filepath || '',
|
||||
filename: file.filename || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -643,7 +643,7 @@ export class CoreFileUploaderProvider {
|
|||
filesToUpload.push(<FileEntry> file);
|
||||
} else {
|
||||
// It's an online file.
|
||||
usedNames[file.filename!.toLowerCase()] = file;
|
||||
usedNames[(file.filename || '').toLowerCase()] = file;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -681,7 +681,7 @@ export class CoreFileUploaderProvider {
|
|||
): Promise<number> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
let fileName: string | undefined;
|
||||
let fileName = '';
|
||||
let fileEntry: FileEntry | undefined;
|
||||
|
||||
const isOnline = !CoreUtils.isFileEntry(file);
|
||||
|
@ -692,7 +692,7 @@ export class CoreFileUploaderProvider {
|
|||
fileEntry = file;
|
||||
} else {
|
||||
// It's an online file. We need to download it and re-upload it.
|
||||
fileName = file.filename;
|
||||
fileName = file.filename || '';
|
||||
|
||||
const path = await CoreFilepool.downloadUrl(
|
||||
siteId,
|
||||
|
@ -710,9 +710,9 @@ export class CoreFileUploaderProvider {
|
|||
}
|
||||
|
||||
// Now upload the file.
|
||||
const extension = CoreMimetypeUtils.getFileExtension(fileName!);
|
||||
const extension = CoreMimetypeUtils.getFileExtension(fileName);
|
||||
const mimetype = extension ? CoreMimetypeUtils.getMimeType(extension) : undefined;
|
||||
const options = this.getFileUploadOptions(fileEntry.toURL(), fileName!, mimetype, isOnline, 'draft', itemId);
|
||||
const options = this.getFileUploadOptions(fileEntry.toURL(), fileName, mimetype, isOnline, 'draft', itemId);
|
||||
|
||||
const result = await this.uploadFile(fileEntry.toURL(), options, undefined, siteId);
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ export class CoreGradesReportLinkHandlerService extends CoreContentLinksHandlerB
|
|||
courseId?: number,
|
||||
data?: { cmid?: string },
|
||||
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
courseId = courseId || Number(params.id);
|
||||
const courseIdentifier = courseId || Number(params.id);
|
||||
data = data || {};
|
||||
|
||||
return [{
|
||||
|
@ -47,7 +47,7 @@ export class CoreGradesReportLinkHandlerService extends CoreContentLinksHandlerB
|
|||
const userId = params.userid ? parseInt(params.userid, 10) : undefined;
|
||||
const moduleId = data?.cmid && parseInt(data.cmid, 10) || undefined;
|
||||
|
||||
CoreGradesHelper.goToGrades(courseId!, userId, moduleId, siteId);
|
||||
CoreGradesHelper.goToGrades(courseIdentifier, userId, moduleId, siteId);
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export class CoreGradesUserLinkHandlerService extends CoreContentLinksHandlerBas
|
|||
courseId?: number,
|
||||
data?: { cmid?: string },
|
||||
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
courseId = courseId || Number(params.id);
|
||||
const courseIdentifier = courseId || Number(params.id);
|
||||
data = data || {};
|
||||
|
||||
return [{
|
||||
|
@ -47,7 +47,7 @@ export class CoreGradesUserLinkHandlerService extends CoreContentLinksHandlerBas
|
|||
const userId = params.user ? parseInt(params.user, 10) : undefined;
|
||||
const moduleId = data?.cmid && parseInt(data.cmid, 10) || undefined;
|
||||
|
||||
CoreGradesHelper.goToGrades(courseId!, userId, moduleId, siteId);
|
||||
CoreGradesHelper.goToGrades(courseIdentifier, userId, moduleId, siteId);
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -166,16 +166,16 @@ export class CoreH5PPlayer {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async deleteAllContentIndexesForSite(siteId?: string): Promise<void> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
const siteIdentifier = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
if (!siteId) {
|
||||
if (!siteIdentifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
const records = await this.h5pCore.h5pFramework.getAllContentData(siteId);
|
||||
const records = await this.h5pCore.h5pFramework.getAllContentData(siteIdentifier);
|
||||
|
||||
await Promise.all(records.map(async (record) => {
|
||||
await CoreUtils.ignoreErrors(this.h5pCore.h5pFS.deleteContentIndex(record.foldername, siteId!));
|
||||
await CoreUtils.ignoreErrors(this.h5pCore.h5pFS.deleteContentIndex(record.foldername, siteIdentifier));
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -217,12 +217,13 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
this.countryControl.setValue(this.settings.country || '');
|
||||
}
|
||||
|
||||
this.namefieldsErrors = {};
|
||||
const namefieldsErrors = {};
|
||||
if (this.settings.namefields) {
|
||||
this.settings.namefields.forEach((field) => {
|
||||
this.namefieldsErrors![field] = CoreLoginHelper.getErrorMessages('core.login.missing' + field);
|
||||
namefieldsErrors[field] = CoreLoginHelper.getErrorMessages('core.login.missing' + field);
|
||||
});
|
||||
}
|
||||
this.namefieldsErrors = namefieldsErrors;
|
||||
|
||||
this.countries = await CoreUtils.getCountryListSorted();
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreWSExternalWarning } from '@services/ws';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
@ -91,7 +90,7 @@ export class CoreLoginForgottenPasswordPage implements OnInit {
|
|||
}
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
const isMail = field == 'email';
|
||||
const isMail = field === 'email';
|
||||
|
||||
try {
|
||||
const response = await CoreLoginHelper.requestPasswordReset(
|
||||
|
@ -100,10 +99,14 @@ export class CoreLoginForgottenPasswordPage implements OnInit {
|
|||
isMail ? value : '',
|
||||
);
|
||||
|
||||
if (response.status == 'dataerror') {
|
||||
// Error in the data sent.
|
||||
this.showError(isMail, response.warnings!);
|
||||
} else if (response.status == 'emailpasswordconfirmnotsent' || response.status == 'emailpasswordconfirmnoemail') {
|
||||
if (response.status === 'dataerror') {
|
||||
// Show an error from the warnings.
|
||||
const warning = response.warnings?.find((warning) =>
|
||||
(warning.item === 'email' && isMail) || (warning.item === 'username' && !isMail));
|
||||
if (warning) {
|
||||
CoreDomUtils.showErrorModal(warning.message);
|
||||
}
|
||||
} else if (response.status === 'emailpasswordconfirmnotsent' || response.status === 'emailpasswordconfirmnoemail') {
|
||||
// Error, not found.
|
||||
CoreDomUtils.showErrorModal(response.notice);
|
||||
} else {
|
||||
|
@ -121,15 +124,4 @@ export class CoreLoginForgottenPasswordPage implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
// Show an error from the warnings.
|
||||
protected showError(isMail: boolean, warnings: CoreWSExternalWarning[]): void {
|
||||
for (let i = 0; i < warnings.length; i++) {
|
||||
const warning = warnings[i];
|
||||
if ((warning.item == 'email' && isMail) || (warning.item == 'username' && !isMail)) {
|
||||
CoreDomUtils.showErrorModal(warning.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -87,24 +87,24 @@ export class CoreLoginHelperProvider {
|
|||
|
||||
const result = await site.write<AgreeSitePolicyResult>('core_user_agree_site_policy', {});
|
||||
|
||||
if (!result.status) {
|
||||
// Error.
|
||||
if (result.warnings && result.warnings.length) {
|
||||
// Check if there is a warning 'alreadyagreed'.
|
||||
for (const i in result.warnings) {
|
||||
const warning = result.warnings[i];
|
||||
if (warning.warningcode == 'alreadyagreed') {
|
||||
// Policy already agreed, treat it as a success.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Another warning, reject.
|
||||
throw new CoreWSError(result.warnings[0]);
|
||||
} else {
|
||||
throw new CoreError('Cannot agree site policy');
|
||||
}
|
||||
if (result.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.warnings?.length) {
|
||||
throw new CoreError('Cannot agree site policy');
|
||||
}
|
||||
|
||||
// Check if there is a warning 'alreadyagreed'.
|
||||
const found = result.warnings.some((warning) => warning.warningcode === 'alreadyagreed');
|
||||
if (found) {
|
||||
// Policy already agreed, treat it as a success.
|
||||
return;
|
||||
}
|
||||
|
||||
// Another warning, reject.
|
||||
throw new CoreWSError(result.warnings[0]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1111,7 +1111,11 @@ export class CoreLoginHelperProvider {
|
|||
);
|
||||
|
||||
if (!result.status) {
|
||||
throw new CoreWSError(result.warnings![0]);
|
||||
if (result.warnings?.length) {
|
||||
throw new CoreWSError(result.warnings[0]);
|
||||
}
|
||||
|
||||
throw new CoreError('Error sending confirmation email');
|
||||
}
|
||||
|
||||
const message = Translate.instant('core.login.emailconfirmsentsuccess');
|
||||
|
@ -1178,16 +1182,16 @@ export class CoreLoginHelperProvider {
|
|||
treatUserTokenError(siteUrl: string, error: CoreWSError, username?: string, password?: string): void {
|
||||
switch (error.errorcode) {
|
||||
case 'forcepasswordchangenotice':
|
||||
this.openChangePassword(siteUrl, CoreTextUtils.getErrorMessageFromError(error)!);
|
||||
this.openChangePassword(siteUrl, CoreTextUtils.getErrorMessageFromError(error) ?? '');
|
||||
break;
|
||||
case 'usernotconfirmed':
|
||||
this.showNotConfirmedModal(siteUrl, undefined, username, password);
|
||||
break;
|
||||
case 'connecttomoodleapp':
|
||||
this.showMoodleAppNoticeModal(CoreTextUtils.getErrorMessageFromError(error)!);
|
||||
this.showMoodleAppNoticeModal(CoreTextUtils.getErrorMessageFromError(error) ?? '');
|
||||
break;
|
||||
case 'connecttoworkplaceapp':
|
||||
this.showWorkplaceNoticeModal(CoreTextUtils.getErrorMessageFromError(error)!);
|
||||
this.showWorkplaceNoticeModal(CoreTextUtils.getErrorMessageFromError(error) ?? '');
|
||||
break;
|
||||
case 'invalidlogin':
|
||||
this.showInvalidLoginModal(error);
|
||||
|
|
|
@ -85,12 +85,12 @@ export class CoreQuestionBaseComponent {
|
|||
*/
|
||||
protected treatCalculatedRadioUnits(questionEl: HTMLElement): boolean {
|
||||
// Check if the question has radio buttons for units.
|
||||
const radios = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="radio"]'));
|
||||
if (!radios.length) {
|
||||
const radios = Array.from(questionEl.querySelectorAll<HTMLInputElement>('input[type="radio"]'));
|
||||
if (!radios.length || !this.question) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const question = <AddonModQuizCalculatedQuestion> this.question!;
|
||||
const question: AddonModQuizCalculatedQuestion = this.question;
|
||||
question.options = [];
|
||||
|
||||
for (const i in radios) {
|
||||
|
@ -104,7 +104,7 @@ export class CoreQuestionBaseComponent {
|
|||
disabled: radioEl.disabled,
|
||||
};
|
||||
// Get the label with the question text.
|
||||
const label = <HTMLElement> questionEl.querySelector('label[for="' + option.id + '"]');
|
||||
const label = questionEl.querySelector<HTMLLabelElement>('label[for="' + option.id + '"]');
|
||||
|
||||
question.optionsName = option.name;
|
||||
|
||||
|
@ -129,7 +129,7 @@ export class CoreQuestionBaseComponent {
|
|||
if (question.parsedSettings && question.parsedSettings.unitsleft !== null) {
|
||||
question.optionsFirst = question.parsedSettings.unitsleft == '1';
|
||||
} else {
|
||||
const input = questionEl.querySelector('input[type="text"][name*=answer]');
|
||||
const input = questionEl.querySelector<HTMLInputElement>('input[type="text"][name*=answer]');
|
||||
question.optionsFirst =
|
||||
questionEl.innerHTML.indexOf(input?.outerHTML || '') > questionEl.innerHTML.indexOf(radios[0].outerHTML);
|
||||
}
|
||||
|
@ -145,14 +145,14 @@ export class CoreQuestionBaseComponent {
|
|||
*/
|
||||
protected treatCalculatedSelectUnits(questionEl: HTMLElement): boolean {
|
||||
// Check if the question has a select for units.
|
||||
const select = <HTMLSelectElement> questionEl.querySelector('select[name*=unit]');
|
||||
const select = questionEl.querySelector<HTMLSelectElement>('select[name*=unit]');
|
||||
const options = select && Array.from(select.querySelectorAll('option'));
|
||||
|
||||
if (!select || !options?.length) {
|
||||
if (!select || !options?.length || !this.question) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const question = <AddonModQuizCalculatedQuestion> this.question!;
|
||||
const question: AddonModQuizCalculatedQuestion = this.question;
|
||||
const selectModel: AddonModQuizQuestionSelect = {
|
||||
id: select.id,
|
||||
name: select.name,
|
||||
|
@ -190,7 +190,7 @@ export class CoreQuestionBaseComponent {
|
|||
}
|
||||
|
||||
// Get the accessibility label.
|
||||
const accessibilityLabel = questionEl.querySelector('label[for="' + select.id + '"]');
|
||||
const accessibilityLabel = questionEl.querySelector<HTMLLabelElement>('label[for="' + select.id + '"]');
|
||||
selectModel.accessibilityLabel = accessibilityLabel?.innerHTML;
|
||||
|
||||
question.select = selectModel;
|
||||
|
@ -199,7 +199,7 @@ export class CoreQuestionBaseComponent {
|
|||
if (question.parsedSettings && question.parsedSettings.unitsleft !== null) {
|
||||
question.selectFirst = question.parsedSettings.unitsleft == '1';
|
||||
} else {
|
||||
const input = questionEl.querySelector('input[type="text"][name*=answer]');
|
||||
const input = questionEl.querySelector<HTMLInputElement>('input[type="text"][name*=answer]');
|
||||
question.selectFirst =
|
||||
questionEl.innerHTML.indexOf(input?.outerHTML || '') > questionEl.innerHTML.indexOf(select.outerHTML);
|
||||
}
|
||||
|
@ -242,12 +242,12 @@ export class CoreQuestionBaseComponent {
|
|||
*/
|
||||
initEssayComponent(review?: boolean): void | HTMLElement {
|
||||
const questionEl = this.initComponent();
|
||||
if (!questionEl) {
|
||||
if (!questionEl || !this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
const question = <AddonModQuizEssayQuestion> this.question!;
|
||||
const answerDraftIdInput = <HTMLInputElement> questionEl.querySelector('input[name*="_answer:itemid"]');
|
||||
const question: AddonModQuizEssayQuestion = this.question;
|
||||
const answerDraftIdInput = questionEl.querySelector<HTMLInputElement>('input[name*="_answer:itemid"]');
|
||||
|
||||
if (question.parsedSettings) {
|
||||
question.allowsAttachments = question.parsedSettings.attachments != '0';
|
||||
|
@ -283,7 +283,7 @@ export class CoreQuestionBaseComponent {
|
|||
return questionEl;
|
||||
}
|
||||
|
||||
const textarea = <HTMLTextAreaElement> questionEl.querySelector('textarea[name*=_answer]');
|
||||
const textarea = questionEl.querySelector<HTMLTextAreaElement>('textarea[name*=_answer]');
|
||||
question.hasDraftFiles = question.allowsAnswerFiles && CoreQuestionHelper.hasDraftFileUrls(questionEl.innerHTML);
|
||||
|
||||
if (!textarea && (question.hasInlineText || !question.allowsAttachments)) {
|
||||
|
@ -297,12 +297,12 @@ export class CoreQuestionBaseComponent {
|
|||
}
|
||||
|
||||
if (textarea) {
|
||||
const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=answerformat]');
|
||||
const input = questionEl.querySelector<HTMLInputElement>('input[type="hidden"][name*=answerformat]');
|
||||
let content = CoreTextUtils.decodeHTML(textarea.innerHTML || '');
|
||||
|
||||
if (question.hasDraftFiles && question.responsefileareas) {
|
||||
content = CoreTextUtils.replaceDraftfileUrls(
|
||||
CoreSites.getCurrentSite()!.getURL(),
|
||||
CoreSites.getRequiredCurrentSite().getURL(),
|
||||
content,
|
||||
CoreQuestionHelper.getResponseFileAreaFiles(question, 'answer'),
|
||||
).text;
|
||||
|
@ -330,8 +330,8 @@ export class CoreQuestionBaseComponent {
|
|||
}
|
||||
|
||||
if (question.allowsAttachments) {
|
||||
const attachmentsInput = <HTMLInputElement> questionEl.querySelector('.attachments input[name*=_attachments]');
|
||||
const objectElement = <HTMLObjectElement> questionEl.querySelector('.attachments object');
|
||||
const attachmentsInput = questionEl.querySelector<HTMLInputElement>('.attachments input[name*=_attachments]');
|
||||
const objectElement = questionEl.querySelector<HTMLObjectElement>('.attachments object');
|
||||
const fileManagerUrl = objectElement && objectElement.data;
|
||||
|
||||
if (attachmentsInput) {
|
||||
|
@ -365,32 +365,40 @@ export class CoreQuestionBaseComponent {
|
|||
* @param questionEl Element with the question html.
|
||||
*/
|
||||
protected handleEssayPlagiarism(questionEl: HTMLElement): void {
|
||||
const question = <AddonModQuizEssayQuestion> this.question!;
|
||||
if (!this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
const question: AddonModQuizEssayQuestion = this.question;
|
||||
const answerPlagiarism = questionEl.querySelector<HTMLSpanElement>('.answer .core_plagiarism_links');
|
||||
if (answerPlagiarism) {
|
||||
question.answerPlagiarism = answerPlagiarism.innerHTML;
|
||||
}
|
||||
|
||||
if (!question.attachments?.length) {
|
||||
const attachments = question.attachments;
|
||||
if (!attachments?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attachmentsPlagiarisms = questionEl.querySelectorAll<HTMLSpanElement>('.attachments .core_plagiarism_links');
|
||||
question.attachmentsPlagiarisms = [];
|
||||
const attachmentsPlagiarisms = Array.from(
|
||||
questionEl.querySelectorAll<HTMLSpanElement>('.attachments .core_plagiarism_links'),
|
||||
);
|
||||
const questionAttachmentsPlagiarisms: string[] = [];
|
||||
|
||||
Array.from(attachmentsPlagiarisms).forEach((plagiarism) => {
|
||||
attachmentsPlagiarisms.forEach((plagiarism) => {
|
||||
// Search the URL of the attachment it affects.
|
||||
const attachmentUrl = plagiarism.parentElement?.querySelector('a')?.href;
|
||||
if (!attachmentUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = question.attachments!.findIndex((file) => CoreFileHelper.getFileUrl(file) == attachmentUrl);
|
||||
|
||||
const position = attachments.findIndex((file) => CoreFileHelper.getFileUrl(file) === attachmentUrl);
|
||||
if (position >= 0) {
|
||||
question.attachmentsPlagiarisms![position] = plagiarism.innerHTML;
|
||||
questionAttachmentsPlagiarisms[position] = plagiarism.innerHTML;
|
||||
}
|
||||
});
|
||||
|
||||
question.attachmentsPlagiarisms = questionAttachmentsPlagiarisms;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -440,15 +448,15 @@ export class CoreQuestionBaseComponent {
|
|||
*/
|
||||
initInputTextComponent(): void | HTMLElement {
|
||||
const questionEl = this.initComponent();
|
||||
if (!questionEl) {
|
||||
if (!questionEl || !this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the input element.
|
||||
const question = <AddonModQuizTextQuestion> this.question!;
|
||||
const input = <HTMLInputElement> questionEl.querySelector('input[type="text"][name*=answer]');
|
||||
const question: AddonModQuizTextQuestion = this.question;
|
||||
const input = questionEl.querySelector<HTMLInputElement>('input[type="text"][name*=answer]');
|
||||
if (!input) {
|
||||
this.logger.warn('Aborting because couldn\'t find input.', this.question!.slot);
|
||||
this.logger.warn('Aborting because couldn\'t find input.', this.question.slot);
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
@ -482,12 +490,13 @@ export class CoreQuestionBaseComponent {
|
|||
|
||||
if (question.input.isInline) {
|
||||
// Handle correct/incorrect classes and icons.
|
||||
const content = <HTMLElement> questionEl.querySelector('.qtext');
|
||||
const content = questionEl.querySelector<HTMLElement>('.qtext');
|
||||
if (content) {
|
||||
CoreQuestionHelper.replaceCorrectnessClasses(content);
|
||||
CoreQuestionHelper.treatCorrectnessIcons(content);
|
||||
|
||||
CoreQuestionHelper.replaceCorrectnessClasses(content);
|
||||
CoreQuestionHelper.treatCorrectnessIcons(content);
|
||||
|
||||
question.text = content.innerHTML;
|
||||
question.text = content.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
return questionEl;
|
||||
|
@ -500,13 +509,13 @@ export class CoreQuestionBaseComponent {
|
|||
*/
|
||||
initMatchComponent(): void | HTMLElement {
|
||||
const questionEl = this.initComponent();
|
||||
if (!questionEl) {
|
||||
if (!questionEl || !this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find rows.
|
||||
const question = <AddonModQuizMatchQuestion> this.question!;
|
||||
const rows = Array.from(questionEl.querySelectorAll('table.answer tr'));
|
||||
const question: AddonModQuizMatchQuestion = this.question;
|
||||
const rows = Array.from(questionEl.querySelectorAll<HTMLTableRowElement>('table.answer tr'));
|
||||
if (!rows || !rows.length) {
|
||||
this.logger.warn('Aborting because couldn\'t find any row.', question.slot);
|
||||
|
||||
|
@ -517,7 +526,7 @@ export class CoreQuestionBaseComponent {
|
|||
|
||||
for (const i in rows) {
|
||||
const row = rows[i];
|
||||
const columns = Array.from(row.querySelectorAll('td'));
|
||||
const columns = Array.from(row.querySelectorAll<HTMLTableCellElement>('td'));
|
||||
|
||||
if (!columns || columns.length < 2) {
|
||||
this.logger.warn('Aborting because couldn\'t the right columns.', question.slot);
|
||||
|
@ -526,8 +535,8 @@ export class CoreQuestionBaseComponent {
|
|||
}
|
||||
|
||||
// Get the select and the options.
|
||||
const select = columns[1].querySelector('select');
|
||||
const options = Array.from(columns[1].querySelectorAll('option'));
|
||||
const select = columns[1].querySelector<HTMLSelectElement>('select');
|
||||
const options = Array.from(columns[1].querySelectorAll<HTMLOptionElement>('option'));
|
||||
|
||||
if (!select || !options || !options.length) {
|
||||
this.logger.warn('Aborting because couldn\'t find select or options.', question.slot);
|
||||
|
@ -574,7 +583,7 @@ export class CoreQuestionBaseComponent {
|
|||
}
|
||||
|
||||
// Get the accessibility label.
|
||||
const accessibilityLabel = columns[1].querySelector('label.accesshide');
|
||||
const accessibilityLabel = columns[1].querySelector<HTMLLabelElement>('label.accesshide');
|
||||
rowModel.accessibilityLabel = accessibilityLabel?.innerHTML;
|
||||
|
||||
question.rows.push(rowModel);
|
||||
|
@ -592,20 +601,20 @@ export class CoreQuestionBaseComponent {
|
|||
*/
|
||||
initMultichoiceComponent(): void | HTMLElement {
|
||||
const questionEl = this.initComponent();
|
||||
if (!questionEl) {
|
||||
if (!questionEl || !this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the prompt.
|
||||
const question = <AddonModQuizMultichoiceQuestion> this.question!;
|
||||
const question: AddonModQuizMultichoiceQuestion = this.question;
|
||||
question.prompt = CoreDomUtils.getContentsOfElement(questionEl, '.prompt');
|
||||
|
||||
// Search radio buttons first (single choice).
|
||||
let options = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="radio"]'));
|
||||
let options = Array.from(questionEl.querySelectorAll<HTMLInputElement>('input[type="radio"]'));
|
||||
if (!options || !options.length) {
|
||||
// Radio buttons not found, it should be a multi answer. Search for checkbox.
|
||||
question.multi = true;
|
||||
options = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="checkbox"]'));
|
||||
options = Array.from(questionEl.querySelectorAll<HTMLInputElement>('input[type="checkbox"]'));
|
||||
|
||||
if (!options || !options.length) {
|
||||
// No checkbox found either. Abort.
|
||||
|
|
|
@ -304,14 +304,16 @@ export class CoreQuestionHelperProvider {
|
|||
// Search init_question functions for this type.
|
||||
const initMatches = scriptCode.match(new RegExp('M.qtype_' + question.type + '.init_question\\(.*?}\\);', 'mg'));
|
||||
if (initMatches) {
|
||||
let initMatch = initMatches.pop()!;
|
||||
let initMatch = initMatches.pop();
|
||||
|
||||
// Remove start and end of the match, we only want the object.
|
||||
initMatch = initMatch.replace('M.qtype_' + question.type + '.init_question(', '');
|
||||
initMatch = initMatch.substring(0, initMatch.length - 2);
|
||||
if (initMatch) {
|
||||
// Remove start and end of the match, we only want the object.
|
||||
initMatch = initMatch.replace('M.qtype_' + question.type + '.init_question(', '');
|
||||
initMatch = initMatch.substring(0, initMatch.length - 2);
|
||||
|
||||
// Try to convert it to an object and add it to the question.
|
||||
question.initObjects = CoreTextUtils.parseJSON(initMatch, null);
|
||||
// Try to convert it to an object and add it to the question.
|
||||
question.initObjects = CoreTextUtils.parseJSON(initMatch, null);
|
||||
}
|
||||
}
|
||||
|
||||
const amdRegExp = new RegExp('require\\(\\[["\']qtype_' + question.type + '/question["\']\\],[^f]*' +
|
||||
|
|
|
@ -60,13 +60,13 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnChanges(): void {
|
||||
this.aggregateObserver?.off();
|
||||
delete this.aggregateObserver;
|
||||
|
||||
this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid == this.itemId);
|
||||
this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid === this.itemId);
|
||||
if (!this.item) {
|
||||
return;
|
||||
}
|
||||
|
@ -99,11 +99,11 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy {
|
|||
this.aggregateObserver =
|
||||
CoreEvents.on(CoreRatingProvider.AGGREGATE_CHANGED_EVENT, (data) => {
|
||||
if (this.item &&
|
||||
data.contextLevel == this.contextLevel &&
|
||||
data.instanceId == this.instanceId &&
|
||||
data.component == this.ratingInfo.component &&
|
||||
data.ratingArea == this.ratingInfo.ratingarea &&
|
||||
data.itemId == this.itemId) {
|
||||
data.contextLevel === this.contextLevel &&
|
||||
data.instanceId === this.instanceId &&
|
||||
data.component === this.ratingInfo.component &&
|
||||
data.ratingArea === this.ratingInfo.ratingarea &&
|
||||
data.itemId === this.itemId) {
|
||||
this.item.aggregatestr = data.aggregate;
|
||||
this.item.count = data.count;
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy {
|
|||
* Open the individual ratings page.
|
||||
*/
|
||||
async openRatings(): Promise<void> {
|
||||
if (!this.ratingInfo.canviewall || !this.item!.count || this.disabled) {
|
||||
if (!this.ratingInfo.canviewall || !this.item?.count || this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ export class CoreRatingRateComponent implements OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnChanges(): Promise<void> {
|
||||
this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid == this.itemId);
|
||||
|
@ -125,6 +125,10 @@ export class CoreRatingRateComponent implements OnChanges, OnDestroy {
|
|||
* Send or save the user rating when changed.
|
||||
*/
|
||||
async userRatingChanged(): Promise<void> {
|
||||
if (this.rating === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
|
||||
try {
|
||||
|
@ -137,7 +141,7 @@ export class CoreRatingRateComponent implements OnChanges, OnDestroy {
|
|||
this.itemSetId,
|
||||
this.courseId,
|
||||
this.scaleId,
|
||||
this.rating!,
|
||||
this.rating,
|
||||
this.userId,
|
||||
this.aggregateMethod,
|
||||
);
|
||||
|
|
|
@ -58,10 +58,13 @@ export class CoreSearchHistoryProvider {
|
|||
if (items.length > CoreSearchHistoryProvider.HISTORY_LIMIT) {
|
||||
// Over the limit. Remove the last.
|
||||
const lastItem = items.pop();
|
||||
if (!lastItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchItem = {
|
||||
searcharea: lastItem!.searcharea,
|
||||
searchedtext: lastItem!.searchedtext,
|
||||
searcharea: lastItem.searcharea,
|
||||
searchedtext: lastItem.searchedtext,
|
||||
};
|
||||
|
||||
await db.deleteRecords(SEARCH_HISTORY_TABLE_NAME, searchItem);
|
||||
|
|
|
@ -43,7 +43,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy {
|
|||
@Output() onFilePicked = new EventEmitter<FileEntry>();
|
||||
|
||||
filesLoaded = false;
|
||||
files?: (FileEntry | DirectoryEntry)[];
|
||||
files: (FileEntry | DirectoryEntry)[] = [];
|
||||
|
||||
protected shareObserver?: CoreEventObserver;
|
||||
|
||||
|
@ -57,7 +57,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy {
|
|||
|
||||
// Listen for new files shared with the app.
|
||||
this.shareObserver = CoreEvents.on(CoreEvents.FILE_SHARED, (data) => {
|
||||
if (data.siteId == this.siteId) {
|
||||
if (data.siteId === this.siteId) {
|
||||
// File was stored in current site, refresh the list.
|
||||
this.filesLoaded = false;
|
||||
this.loadFiles().finally(() => {
|
||||
|
@ -94,7 +94,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy {
|
|||
* @param index Position of the file.
|
||||
*/
|
||||
fileDeleted(index: number): void {
|
||||
this.files!.splice(index, 1);
|
||||
this.files.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +104,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy {
|
|||
* @param data Data containing the new FileEntry.
|
||||
*/
|
||||
fileRenamed(index: number, data: { file: FileEntry }): void {
|
||||
this.files![index] = data.file;
|
||||
this.files[index] = data.file;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,7 +161,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.shareObserver?.off();
|
||||
|
|
|
@ -19,7 +19,6 @@ import { CoreFile } from '@services/file';
|
|||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Page to display the list of sites to choose one to store a shared file.
|
||||
|
@ -42,19 +41,20 @@ export class CoreSharedFilesChooseSitePage implements OnInit {
|
|||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.filePath = CoreNavigator.getRouteParam('filePath');
|
||||
this.isInbox = !!CoreNavigator.getRouteBooleanParam('isInbox');
|
||||
|
||||
if (!this.filePath) {
|
||||
CoreDomUtils.showErrorModal('Error reading file.');
|
||||
await CoreUtils.nextTick();
|
||||
try {
|
||||
this.filePath = CoreNavigator.getRequiredRouteParam('filePath');
|
||||
this.isInbox = !!CoreNavigator.getRouteBooleanParam('isInbox');
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error reading file.');
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.filePath);
|
||||
this.fileName = fileAndDir.name;
|
||||
if (this.filePath) {
|
||||
const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.filePath);
|
||||
this.fileName = fileAndDir.name;
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
|
@ -75,7 +75,11 @@ export class CoreSharedFilesChooseSitePage implements OnInit {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async loadFile(): Promise<void> {
|
||||
this.fileEntry = await CoreFile.getExternalFile(this.filePath!);
|
||||
if (!this.filePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fileEntry = await CoreFile.getExternalFile(this.filePath);
|
||||
this.fileName = this.fileEntry.name;
|
||||
}
|
||||
|
||||
|
@ -94,10 +98,14 @@ export class CoreSharedFilesChooseSitePage implements OnInit {
|
|||
* @param siteId Site ID.
|
||||
*/
|
||||
async storeInSite(siteId: string): Promise<void> {
|
||||
if (!this.fileEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loaded = false;
|
||||
|
||||
try {
|
||||
await CoreSharedFilesHelper.storeSharedFileInSite(this.fileEntry!, siteId, this.isInbox);
|
||||
await CoreSharedFilesHelper.storeSharedFileInSite(this.fileEntry, siteId, this.isInbox);
|
||||
|
||||
CoreNavigator.back();
|
||||
} finally {
|
||||
|
|
|
@ -50,7 +50,7 @@ export class CoreSitePluginsUserProfileFieldHandler extends CoreSitePluginsBaseH
|
|||
const name = 'profile_field_' + field.shortname;
|
||||
|
||||
return {
|
||||
type: 'type' in field ? field.type : field.datatype!,
|
||||
type: ('type' in field ? field.type : field.datatype) || '',
|
||||
name: name,
|
||||
value: formValues[name],
|
||||
};
|
||||
|
|
|
@ -185,7 +185,7 @@ export class CoreSitePluginsHelperProvider {
|
|||
);
|
||||
|
||||
files?.forEach((file) => {
|
||||
if (file.url != url) {
|
||||
if (file.url !== url) {
|
||||
// It's not the current file, delete it.
|
||||
CoreUtils.ignoreErrors(CoreFilepool.removeFileByUrl(site.getId(), file.url));
|
||||
}
|
||||
|
@ -489,32 +489,32 @@ export class CoreSitePluginsHelperProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
if (cssCode) {
|
||||
if (cssCode && handlerSchema.styles?.url) {
|
||||
// Load the styles.
|
||||
this.loadStyles(plugin, handlerName, handlerSchema.styles!.url!, cssCode, handlerSchema.styles!.version, siteId);
|
||||
this.loadStyles(plugin, handlerName, handlerSchema.styles.url, cssCode, handlerSchema.styles.version, siteId);
|
||||
}
|
||||
|
||||
let uniqueName: string | undefined;
|
||||
|
||||
switch (handlerSchema.delegate) {
|
||||
case 'CoreMainMenuDelegate':
|
||||
uniqueName = await this.registerMainMenuHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
uniqueName = this.registerMainMenuHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
break;
|
||||
|
||||
case 'CoreCourseModuleDelegate':
|
||||
uniqueName = await this.registerModuleHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
uniqueName = this.registerModuleHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
break;
|
||||
|
||||
case 'CoreUserDelegate':
|
||||
uniqueName = await this.registerUserProfileHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
uniqueName = this.registerUserProfileHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
break;
|
||||
|
||||
case 'CoreCourseOptionsDelegate':
|
||||
uniqueName = await this.registerCourseOptionHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
uniqueName = this.registerCourseOptionHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
break;
|
||||
|
||||
case 'CoreCourseFormatDelegate':
|
||||
uniqueName = await this.registerCourseFormatHandler(plugin, handlerName, handlerSchema);
|
||||
uniqueName = this.registerCourseFormatHandler(plugin, handlerName, handlerSchema);
|
||||
break;
|
||||
|
||||
case 'CoreUserProfileFieldDelegate':
|
||||
|
@ -522,7 +522,7 @@ export class CoreSitePluginsHelperProvider {
|
|||
break;
|
||||
|
||||
case 'CoreSettingsDelegate':
|
||||
uniqueName = await this.registerSettingsHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
uniqueName = this.registerSettingsHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
break;
|
||||
|
||||
case 'CoreQuestionDelegate':
|
||||
|
@ -534,11 +534,11 @@ export class CoreSitePluginsHelperProvider {
|
|||
break;
|
||||
|
||||
case 'CoreBlockDelegate':
|
||||
uniqueName = await this.registerBlockHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
uniqueName = this.registerBlockHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
break;
|
||||
|
||||
case 'AddonMessageOutputDelegate':
|
||||
uniqueName = await this.registerMessageOutputHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
uniqueName = this.registerMessageOutputHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
break;
|
||||
|
||||
case 'AddonModQuizAccessRuleDelegate':
|
||||
|
@ -558,7 +558,7 @@ export class CoreSitePluginsHelperProvider {
|
|||
break;
|
||||
|
||||
case 'CoreMainMenuHomeDelegate':
|
||||
uniqueName = await this.registerMainMenuHomeHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
uniqueName = this.registerMainMenuHomeHandler(plugin, handlerName, handlerSchema, initResult);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -101,7 +101,7 @@ export class CoreStylesService {
|
|||
this.styleHandlers.push(styleHandler);
|
||||
|
||||
// Sort them by priority, greatest go last because style loaded last it's more important.
|
||||
this.styleHandlers = this.styleHandlers.sort((a, b) => a.priority! >= b.priority! ? 1 : -1);
|
||||
this.styleHandlers = this.styleHandlers.sort((a, b) => a.priority >= b.priority ? 1 : -1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,7 +221,7 @@ export class CoreStylesService {
|
|||
const hash = <string>Md5.hashAsciiStr(contents);
|
||||
|
||||
// Update the styles only if they have changed.
|
||||
if (this.stylesEls[siteId!][handler.name] === hash) {
|
||||
if (this.stylesEls[siteId][handler.name] === hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -353,21 +353,21 @@ export class CoreStylesService {
|
|||
* @returns Promise resolved when styles are loaded.
|
||||
*/
|
||||
protected async load(siteId?: string, disabled = false): Promise<void> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
const siteIdentifier = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
if (!siteId || !this.stylesEls[siteId]) {
|
||||
if (!siteIdentifier || !this.stylesEls[siteIdentifier]) {
|
||||
throw new CoreError('Cannot load styles, site not found: ${siteId}');
|
||||
}
|
||||
|
||||
this.logger.debug('Load site', siteId, disabled);
|
||||
this.logger.debug('Load site', siteIdentifier, disabled);
|
||||
|
||||
// Enable or disable the styles.
|
||||
for (const sourceName in this.stylesEls[siteId]) {
|
||||
this.disableStyleElementByName(siteId, sourceName, disabled);
|
||||
for (const sourceName in this.stylesEls[siteIdentifier]) {
|
||||
this.disableStyleElementByName(siteIdentifier, sourceName, disabled);
|
||||
}
|
||||
|
||||
await CoreUtils.allPromises(this.styleHandlers.map(async (handler) => {
|
||||
await this.setStyle(siteId!, handler, !!disabled);
|
||||
await this.setStyle(siteIdentifier, handler, disabled);
|
||||
}));
|
||||
|
||||
if (!disabled) {
|
||||
|
|
|
@ -53,7 +53,7 @@ export class CoreTagIndexAreaPage implements OnInit {
|
|||
) { }
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.route.queryParams.subscribe(async () => {
|
||||
|
@ -80,7 +80,9 @@ export class CoreTagIndexAreaPage implements OnInit {
|
|||
await this.fetchData(true);
|
||||
}
|
||||
|
||||
this.areaComponent = await CoreTagAreaDelegate.getComponent(this.componentName!, this.itemType!);
|
||||
if (this.componentName && this.itemType) {
|
||||
this.areaComponent = await CoreTagAreaDelegate.getComponent(this.componentName, this.itemType);
|
||||
}
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
}
|
||||
|
|
|
@ -91,7 +91,10 @@ export class CoreUserSyncProvider extends CoreSyncBaseProvider<string[]> {
|
|||
await CoreUser.setUserPreference(preference.name, preference.value, siteId);
|
||||
} catch (error) {
|
||||
if (CoreUtils.isWebServiceError(error)) {
|
||||
warnings.push(CoreTextUtils.getErrorMessageFromError(error)!);
|
||||
const warning = CoreTextUtils.getErrorMessageFromError(error);
|
||||
if (warning) {
|
||||
warnings.push(warning);
|
||||
}
|
||||
} else {
|
||||
// Couldn't connect to server, reject.
|
||||
throw error;
|
||||
|
|
|
@ -20,6 +20,7 @@ export default async function(): Promise<void> {
|
|||
const lastUrls: Record<string, number> = {};
|
||||
|
||||
// Handle app launched with a certain URL (custom URL scheme).
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(<any> window).handleOpenURL = (url: string): void => {
|
||||
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||
NgZone.run(() => {
|
||||
|
|
|
@ -54,7 +54,7 @@ export class CoreCronDelegateService {
|
|||
async initializeDatabase(): Promise<void> {
|
||||
try {
|
||||
await CoreApp.createTablesFromSchema(APP_SCHEMA);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
|
||||
|
@ -144,21 +144,24 @@ export class CoreCronDelegateService {
|
|||
* @param siteId Site ID. If not defined, all sites.
|
||||
* @returns Promise resolved when the handler finishes or reaches max time, rejected if it fails.
|
||||
*/
|
||||
protected executeHandler(name: string, force?: boolean, siteId?: string): Promise<void> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
this.logger.debug('Executing handler: ' + name);
|
||||
protected async executeHandler(name: string, force?: boolean, siteId?: string): Promise<void> {
|
||||
this.logger.debug('Executing handler: ' + name);
|
||||
|
||||
try {
|
||||
// Wrap the call in Promise.resolve to make sure it's a promise.
|
||||
Promise.resolve(this.handlers[name].execute!(siteId, force)).then(resolve).catch(reject).finally(() => {
|
||||
clearTimeout(cancelTimeout);
|
||||
});
|
||||
const promise = Promise.resolve(this.handlers[name].execute?.(siteId, force));
|
||||
|
||||
const cancelTimeout = setTimeout(() => {
|
||||
await CoreUtils.timeoutPromise(promise, CoreCronDelegateService.MAX_TIME_PROCESS);
|
||||
} catch (error) {
|
||||
if (error.timeout) {
|
||||
// The handler took too long. Resolve because we don't want to retry soon.
|
||||
this.logger.debug(`Resolving execution of handler '${name}' because it took too long.`);
|
||||
resolve();
|
||||
}, CoreCronDelegateService.MAX_TIME_PROCESS);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -210,20 +213,21 @@ export class CoreCronDelegateService {
|
|||
* @returns Handler's interval.
|
||||
*/
|
||||
protected getHandlerInterval(name: string): number {
|
||||
if (!this.handlers[name] || !this.handlers[name].getInterval) {
|
||||
if (this.handlers[name] === undefined) {
|
||||
// Invalid, return default.
|
||||
return CoreCronDelegateService.DEFAULT_INTERVAL;
|
||||
}
|
||||
|
||||
// Don't allow intervals lower than the minimum.
|
||||
const minInterval = CoreCronDelegateService.MIN_INTERVAL;
|
||||
const handlerInterval = this.handlers[name].getInterval!();
|
||||
const handlerInterval = this.handlers[name].getInterval?.();
|
||||
|
||||
if (!handlerInterval) {
|
||||
return CoreCronDelegateService.DEFAULT_INTERVAL;
|
||||
} else {
|
||||
return Math.max(minInterval, handlerInterval);
|
||||
}
|
||||
|
||||
const minInterval = CoreCronDelegateService.MIN_INTERVAL;
|
||||
|
||||
return Math.max(minInterval, handlerInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -251,7 +255,7 @@ export class CoreCronDelegateService {
|
|||
const time = Number(entry.value);
|
||||
|
||||
return isNaN(time) ? 0 : time;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return 0; // Not set, return 0.
|
||||
}
|
||||
}
|
||||
|
@ -263,12 +267,7 @@ export class CoreCronDelegateService {
|
|||
* @returns True if handler uses network or not defined, false otherwise.
|
||||
*/
|
||||
protected handlerUsesNetwork(name: string): boolean {
|
||||
if (!this.handlers[name] || !this.handlers[name].usesNetwork) {
|
||||
// Invalid, return default.
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.handlers[name].usesNetwork!();
|
||||
return this.handlers[name]?.usesNetwork?.() ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,12 +307,7 @@ export class CoreCronDelegateService {
|
|||
* @returns True if handler is a sync process and can be manually executed or not defined, false otherwise.
|
||||
*/
|
||||
protected isHandlerManualSync(name: string): boolean {
|
||||
if (!this.handlers[name] || !this.handlers[name].canManualSync) {
|
||||
// Invalid, return default.
|
||||
return this.isHandlerSync(name);
|
||||
}
|
||||
|
||||
return this.handlers[name].canManualSync!();
|
||||
return this.handlers[name]?.canManualSync?.() ?? this.isHandlerSync(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -323,12 +317,7 @@ export class CoreCronDelegateService {
|
|||
* @returns True if handler is a sync process or not defined, false otherwise.
|
||||
*/
|
||||
protected isHandlerSync(name: string): boolean {
|
||||
if (!this.handlers[name] || !this.handlers[name].isSync) {
|
||||
// Invalid, return default.
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.handlers[name].isSync!();
|
||||
return this.handlers[name]?.isSync?.() ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -111,8 +111,8 @@ export class CoreLocalNotificationsProvider {
|
|||
});
|
||||
|
||||
CoreEvents.on(CoreEvents.SITE_DELETED, (site) => {
|
||||
if (site) {
|
||||
this.cancelSiteNotifications(site.id!);
|
||||
if (site?.id) {
|
||||
this.cancelSiteNotifications(site.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -364,15 +364,16 @@ export class CoreLocalNotificationsProvider {
|
|||
}
|
||||
|
||||
return stored.at === triggered;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
const notificationId = notification.id || 0;
|
||||
if (useQueue) {
|
||||
const queueId = 'isTriggered-' + notification.id;
|
||||
const queueId = 'isTriggered-' + notificationId;
|
||||
|
||||
return this.queueRunner.run(queueId, () => LocalNotifications.isTriggered(notification.id!), {
|
||||
return this.queueRunner.run(queueId, () => LocalNotifications.isTriggered(notificationId), {
|
||||
allowRepeated: true,
|
||||
});
|
||||
} else {
|
||||
return LocalNotifications.isTriggered(notification.id || 0);
|
||||
return LocalNotifications.isTriggered(notificationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ export class CorePluginFileDelegateService extends CoreDelegate<CorePluginFileHa
|
|||
* @returns List of URLs.
|
||||
*/
|
||||
getDownloadableFilesFromHTML(container: HTMLElement): string[] {
|
||||
let files = <string[]>[];
|
||||
let files: string[] = [];
|
||||
|
||||
for (const component in this.enabledHandlers) {
|
||||
const handler = this.enabledHandlers[component];
|
||||
|
@ -134,19 +134,19 @@ export class CorePluginFileDelegateService extends CoreDelegate<CorePluginFileHa
|
|||
* @returns Promise resolved with file size and a boolean to indicate if it is the total size or only partial.
|
||||
*/
|
||||
async getFilesDownloadSize(files: CoreWSFile[], siteId?: string): Promise<CoreFileSizeSum> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
const siteIdentifier = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
const filteredFiles = <CoreWSFile[]>[];
|
||||
const filteredFiles: CoreWSFile[] = [];
|
||||
|
||||
await Promise.all(files.map(async (file) => {
|
||||
const state = await CoreFilepool.getFileStateByUrl(siteId!, CoreFileHelper.getFileUrl(file), file.timemodified);
|
||||
const state = await CoreFilepool.getFileStateByUrl(siteIdentifier, CoreFileHelper.getFileUrl(file), file.timemodified);
|
||||
|
||||
if (state != CoreConstants.DOWNLOADED && state != CoreConstants.NOT_DOWNLOADABLE) {
|
||||
if (state !== CoreConstants.DOWNLOADED && state !== CoreConstants.NOT_DOWNLOADABLE) {
|
||||
filteredFiles.push(file);
|
||||
}
|
||||
}));
|
||||
|
||||
return this.getFilesSize(filteredFiles, siteId);
|
||||
return this.getFilesSize(filteredFiles, siteIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,7 +157,7 @@ export class CorePluginFileDelegateService extends CoreDelegate<CorePluginFileHa
|
|||
* @returns Promise resolved with file size and a boolean to indicate if it is the total size or only partial.
|
||||
*/
|
||||
async getFilesSize(files: CoreWSFile[], siteId?: string): Promise<CoreFileSizeSum> {
|
||||
const result = {
|
||||
const result: CoreFileSizeSum = {
|
||||
size: 0,
|
||||
total: true,
|
||||
};
|
||||
|
|
|
@ -1443,7 +1443,7 @@ export class CoreDomUtilsProvider {
|
|||
}
|
||||
|
||||
return this.showErrorModal(
|
||||
typeof errorMessage == 'string' && errorMessage ? error! : defaultError,
|
||||
typeof errorMessage == 'string' && errorMessage && error ? error : defaultError,
|
||||
needsTranslate,
|
||||
autocloseTime,
|
||||
);
|
||||
|
|
|
@ -87,7 +87,7 @@ export class CoreIframeUtilsProvider {
|
|||
});
|
||||
|
||||
return true;
|
||||
} else if (element.classList.contains('core-iframe-offline-disabled')) {
|
||||
} else if (element.classList.contains('core-iframe-offline-disabled') && element.parentElement) {
|
||||
// Reload the frame.
|
||||
if ('src' in element) {
|
||||
// eslint-disable-next-line no-self-assign
|
||||
|
@ -98,7 +98,7 @@ export class CoreIframeUtilsProvider {
|
|||
}
|
||||
|
||||
// Remove the warning and show the iframe
|
||||
CoreDomUtils.removeElement(element.parentElement!, 'div.core-iframe-offline-warning');
|
||||
CoreDomUtils.removeElement(element.parentElement, 'div.core-iframe-offline-warning');
|
||||
element.classList.remove('core-iframe-offline-disabled');
|
||||
|
||||
if (isSubframe) {
|
||||
|
@ -207,14 +207,13 @@ export class CoreIframeUtilsProvider {
|
|||
const finalUrl = await currentSite.getAutoLoginUrl(url, false);
|
||||
|
||||
// Resolve the promise once the iframe is loaded, or after a certain time.
|
||||
let unblocked = false;
|
||||
const unblock = () => {
|
||||
if (unblocked) {
|
||||
if (!this.waitAutoLoginDefer) {
|
||||
// Not blocked.
|
||||
return;
|
||||
}
|
||||
|
||||
unblocked = true;
|
||||
this.waitAutoLoginDefer!.resolve();
|
||||
this.waitAutoLoginDefer.resolve();
|
||||
delete this.waitAutoLoginDefer;
|
||||
};
|
||||
|
||||
|
|
|
@ -807,7 +807,10 @@ export class CoreTextUtilsProvider {
|
|||
// Index the pluginfile URLs by file name.
|
||||
const pluginfileMap: {[name: string]: string} = {};
|
||||
files.forEach((file) => {
|
||||
pluginfileMap[file.filename!] = CoreFileHelper.getFileUrl(file);
|
||||
if (!file.filename) {
|
||||
return;
|
||||
}
|
||||
pluginfileMap[file.filename] = CoreFileHelper.getFileUrl(file);
|
||||
});
|
||||
|
||||
// Replace each draftfile with the corresponding pluginfile URL.
|
||||
|
@ -872,9 +875,13 @@ export class CoreTextUtilsProvider {
|
|||
const draftfileUrlRegexPrefix = this.escapeForRegex(draftfileUrl) + '/[^/]+/[^/]+/[^/]+/[^/]+/';
|
||||
|
||||
files.forEach((file) => {
|
||||
if (!file.filename) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search the draftfile URL in the original text.
|
||||
const matches = originalText.match(
|
||||
new RegExp(draftfileUrlRegexPrefix + this.escapeForRegex(file.filename!) + '[^\'" ]*', 'i'),
|
||||
new RegExp(draftfileUrlRegexPrefix + this.escapeForRegex(file.filename) + '[^\'" ]*', 'i'),
|
||||
);
|
||||
|
||||
if (!matches || !matches[0]) {
|
||||
|
|
|
@ -70,7 +70,7 @@ $colors: (
|
|||
// The minimum dimensions at which your layout will change,
|
||||
// adapting to different screen sizes, for use in media queries
|
||||
$screen-breakpoints: (
|
||||
xs: 0,
|
||||
xs: 0px,
|
||||
sm: 576px,
|
||||
md: 768px,
|
||||
lg: 992px,
|
||||
|
|
Loading…
Reference in New Issue