MOBILE-4270 chore: Solve lots of linter warnings

main
Pau Ferrer Ocaña 2022-11-30 11:36:56 +01:00
parent ec86719833
commit c22424c93e
70 changed files with 689 additions and 539 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>[] = [];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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[] = [];

View File

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

View File

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

View File

@ -145,7 +145,7 @@ export function conditionalRoutes(routes: Routes, condition: () => boolean): Rou
return {
...newRoute,
matcher: buildConditionalUrlMatcher(matcher || path!, condition),
matcher: buildConditionalUrlMatcher(matcher || path || '', condition),
};
});
}

View File

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

View File

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

View File

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

View File

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

View 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),
};

View File

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

View File

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

View File

@ -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] = '';
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]*' +

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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