forked from EVOgeek/Vmeda.Online
		
	
						commit
						cc5c476019
					
				| @ -39,10 +39,10 @@ import { CoreLogger } from '@singletons/logger'; | ||||
| }) | ||||
| export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent { | ||||
| 
 | ||||
|     sort = new FormControl(); | ||||
|     sort = new FormControl(AddonBlockTimelineSort.ByDates); | ||||
|     sort$!: Observable<AddonBlockTimelineSort>; | ||||
|     sortOptions!: AddonBlockTimelineOption<AddonBlockTimelineSort>[]; | ||||
|     filter = new FormControl(); | ||||
|     filter = new FormControl(AddonBlockTimelineFilter.Next30Days); | ||||
|     filter$!: Observable<AddonBlockTimelineFilter>; | ||||
|     statusFilterOptions!: AddonBlockTimelineOption<AddonBlockTimelineFilter>[]; | ||||
|     dateFilterOptions!: AddonBlockTimelineOption<AddonBlockTimelineFilter>[]; | ||||
|  | ||||
| @ -78,9 +78,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { | ||||
| 
 | ||||
|     // Form variables.
 | ||||
|     form: FormGroup; | ||||
|     typeControl: FormControl; | ||||
|     groupControl: FormControl; | ||||
|     descriptionControl: FormControl; | ||||
|     typeControl: FormControl<AddonCalendarEventType | null>; | ||||
|     groupControl: FormControl<number | null>; | ||||
|     descriptionControl: FormControl<string>; | ||||
| 
 | ||||
|     // Reminders.
 | ||||
|     remindersEnabled = false; | ||||
| @ -103,9 +103,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { | ||||
|         this.form = new FormGroup({}); | ||||
| 
 | ||||
|         // Initialize form variables.
 | ||||
|         this.typeControl = this.fb.control('', Validators.required); | ||||
|         this.groupControl = this.fb.control(''); | ||||
|         this.descriptionControl = this.fb.control(''); | ||||
|         this.typeControl = this.fb.control(null, Validators.required); | ||||
|         this.groupControl = this.fb.control(null); | ||||
|         this.descriptionControl = this.fb.control('', { nonNullable: true }); | ||||
|         this.form.addControl('name', this.fb.control('', Validators.required)); | ||||
|         this.form.addControl('eventtype', this.typeControl); | ||||
|         this.form.addControl('categoryid', this.fb.control('')); | ||||
| @ -322,11 +322,11 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { | ||||
| 
 | ||||
|         this.form.controls.name.setValue(event.name); | ||||
|         this.form.controls.timestart.setValue(CoreTimeUtils.toDatetimeFormat(event.timestart * 1000)); | ||||
|         this.form.controls.eventtype.setValue(event.eventtype); | ||||
|         this.typeControl.setValue(event.eventtype as AddonCalendarEventType); | ||||
|         this.form.controls.categoryid.setValue(event.categoryid || ''); | ||||
|         this.form.controls.courseid.setValue(courseId || ''); | ||||
|         this.form.controls.groupcourseid.setValue(courseId || ''); | ||||
|         this.form.controls.groupid.setValue(event.groupid || ''); | ||||
|         this.groupControl.setValue(event.groupid || null); | ||||
|         this.form.controls.description.setValue(event.description); | ||||
|         this.form.controls.location.setValue(event.location); | ||||
| 
 | ||||
| @ -410,7 +410,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { | ||||
|         try { | ||||
|             await this.loadGroups(courseId); | ||||
| 
 | ||||
|             this.groupControl.setValue(''); | ||||
|             this.groupControl.setValue(null); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting data.'); | ||||
|         } | ||||
|  | ||||
| @ -34,7 +34,7 @@ import { AddonModAssignFeedbackPluginBaseComponent } from '@addons/mod/assign/cl | ||||
| }) | ||||
| export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedbackPluginBaseComponent implements OnInit { | ||||
| 
 | ||||
|     control?: FormControl; | ||||
|     control?: FormControl<string>; | ||||
|     component = AddonModAssignProvider.COMPONENT; | ||||
|     text = ''; | ||||
|     isSent = false; | ||||
| @ -76,7 +76,7 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb | ||||
|                     } | ||||
|                 }); | ||||
|             } else if (this.edit) { | ||||
|                 this.control = this.fb.control(this.text); | ||||
|                 this.control = this.fb.control(this.text, { nonNullable: true }); | ||||
|             } | ||||
|         } finally { | ||||
|             this.loaded = true; | ||||
|  | ||||
| @ -31,7 +31,7 @@ import { AddonModAssignSubmissionOnlineTextPluginData } from '../services/handle | ||||
| }) | ||||
| export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignSubmissionPluginBaseComponent implements OnInit { | ||||
| 
 | ||||
|     control?: FormControl; | ||||
|     control?: FormControl<string>; | ||||
|     words = 0; | ||||
|     component = AddonModAssignProvider.COMPONENT; | ||||
|     text = ''; | ||||
| @ -94,7 +94,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS | ||||
|                 }); | ||||
|             } else { | ||||
|                 // Create and add the control.
 | ||||
|                 this.control = this.fb.control(this.text); | ||||
|                 this.control = this.fb.control(this.text, { nonNullable: true }); | ||||
|             } | ||||
| 
 | ||||
|             // Calculate initial words.
 | ||||
|  | ||||
| @ -83,7 +83,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges | ||||
| 
 | ||||
|     @ViewChild('replyFormEl') formElement!: ElementRef; | ||||
| 
 | ||||
|     messageControl = new FormControl(); | ||||
|     messageControl = new FormControl<string | null>(null); | ||||
| 
 | ||||
|     uniqueId!: string; | ||||
|     defaultReplySubject!: string; | ||||
|  | ||||
| @ -70,7 +70,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea | ||||
|     @ViewChild(CoreEditorRichTextEditorComponent) messageEditor!: CoreEditorRichTextEditorComponent; | ||||
| 
 | ||||
|     component = AddonModForumProvider.COMPONENT; | ||||
|     messageControl = new FormControl(); | ||||
|     messageControl = new FormControl<string | null>(null); | ||||
|     groupsLoaded = false; | ||||
|     showGroups = false; | ||||
|     hasOffline = false; | ||||
|  | ||||
| @ -58,7 +58,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave { | ||||
|     courseId!: number; | ||||
|     loaded = false; | ||||
|     glossary?: AddonModGlossaryGlossary; | ||||
|     definitionControl = new FormControl(); | ||||
|     definitionControl = new FormControl<string | null>(null); | ||||
|     categories: AddonModGlossaryCategory[] = []; | ||||
|     showAliases = true; | ||||
|     editorExtraParams: Record<string, unknown> = {}; | ||||
|  | ||||
| @ -369,7 +369,7 @@ export class AddonModLessonHelperProvider { | ||||
|             }; | ||||
| 
 | ||||
|             // Init the control.
 | ||||
|             essayQuestion.control = this.formBuilder.control(''); | ||||
|             essayQuestion.control = this.formBuilder.control('', { nonNullable: true }); | ||||
|             questionForm.addControl(essayQuestion.textarea.name, essayQuestion.control); | ||||
|         } | ||||
| 
 | ||||
| @ -635,7 +635,7 @@ export type AddonModLessonInputQuestion = AddonModLessonQuestionBasicData & { | ||||
| export type AddonModLessonEssayQuestion = AddonModLessonQuestionBasicData & { | ||||
|     useranswer?: string; // User answer, for reviewing.
 | ||||
|     textarea?: AddonModLessonTextareaData; // Data for the textarea.
 | ||||
|     control?: FormControl; // Form control.
 | ||||
|     control?: FormControl<string>; // Form control.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -157,14 +157,14 @@ export class AddonModUrlProvider { | ||||
|         url = url || ''; | ||||
| 
 | ||||
|         const matches = url.match(/\//g); | ||||
|         const extension = CoreMimetypeUtils.getFileExtension(url); | ||||
|         const extension = CoreMimetypeUtils.guessExtensionFromUrl(url); | ||||
| 
 | ||||
|         if (!matches || matches.length < 3 || url.slice(-1) === '/' || extension == 'php') { | ||||
|             // Use default icon.
 | ||||
|             return ''; | ||||
|         } | ||||
| 
 | ||||
|         const icon = CoreMimetypeUtils.getFileIcon(url); | ||||
|         const icon = CoreMimetypeUtils.getExtensionIcon(extension ?? ''); | ||||
| 
 | ||||
|         // We do not want to return those icon types, the module icon is more appropriate.
 | ||||
|         if (icon === CoreMimetypeUtils.getFileIconForType('unknown') || | ||||
|  | ||||
| @ -47,7 +47,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|     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.
 | ||||
|     contentControl: FormControl<string>; // 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.
 | ||||
| @ -74,7 +74,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|     constructor( | ||||
|         protected formBuilder: FormBuilder, | ||||
|     ) { | ||||
|         this.contentControl = this.formBuilder.control(''); | ||||
|         this.contentControl = this.formBuilder.control('', { nonNullable: true }); | ||||
|         this.pageForm = this.formBuilder.group({}); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -73,7 +73,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe | ||||
|     assessmentStrategyLoaded = false; | ||||
|     notSupported = false; | ||||
|     feedbackText = ''; | ||||
|     feedbackControl = new FormControl(); | ||||
|     feedbackControl = new FormControl<string | null>(null); | ||||
|     overallFeedkback = false; | ||||
|     overallFeedkbackRequired = false; | ||||
|     component = ADDON_MOD_WORKSHOP_COMPONENT; | ||||
|  | ||||
| @ -32,7 +32,7 @@ import { CoreFileEntry } from '@services/file-helper'; | ||||
| }) | ||||
| export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent<AddonModQuizEssayQuestion> { | ||||
| 
 | ||||
|     formControl?: FormControl; | ||||
|     formControl?: FormControl<string | null>; | ||||
|     attachments?: CoreFileEntry[]; | ||||
|     uploadFilesSupported = false; | ||||
| 
 | ||||
| @ -52,7 +52,7 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent<AddonMod | ||||
| 
 | ||||
|         this.initEssayComponent(this.review); | ||||
| 
 | ||||
|         this.formControl = this.fb.control(this.question?.textarea?.text); | ||||
|         this.formControl = this.fb.control(this.question?.textarea?.text ?? null); | ||||
| 
 | ||||
|         if (this.question?.allowsAttachments && this.uploadFilesSupported && !this.review) { | ||||
|             this.loadAttachments(); | ||||
|  | ||||
| @ -27,20 +27,23 @@ import { CoreUtils } from '@services/utils/utils'; | ||||
|     templateUrl: 'addon-user-profile-field-checkbox.html', | ||||
|     styleUrls: ['./checkbox.scss'], | ||||
| }) | ||||
| export class AddonUserProfileFieldCheckboxComponent extends CoreUserProfileFieldBaseComponent { | ||||
| export class AddonUserProfileFieldCheckboxComponent extends CoreUserProfileFieldBaseComponent<boolean> { | ||||
| 
 | ||||
|     /** | ||||
|      * Create the Form control. | ||||
|      * | ||||
|      * @returns Form control. | ||||
|      */ | ||||
|     protected createFormControl(field: AuthEmailSignupProfileField): FormControl { | ||||
|     protected createFormControl(field: AuthEmailSignupProfileField): FormControl<boolean> { | ||||
|         const formData = { | ||||
|             value: CoreUtils.isTrueOrOne(field.defaultdata), | ||||
|             disabled: this.disabled, | ||||
|         }; | ||||
| 
 | ||||
|         return new FormControl(formData, this.required && !field.locked ? Validators.requiredTrue : null); | ||||
|         return new FormControl(formData, { | ||||
|             validators: this.required && !field.locked ? Validators.requiredTrue : null, | ||||
|             nonNullable: true, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -28,7 +28,7 @@ import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-p | ||||
|     selector: 'addon-user-profile-field-datetime', | ||||
|     templateUrl: 'addon-user-profile-field-datetime.html', | ||||
| }) | ||||
| export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent { | ||||
| export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent<string | undefined> { | ||||
| 
 | ||||
|     ionDateTimePresentation = 'date'; | ||||
|     min?: string; | ||||
| @ -84,13 +84,16 @@ export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileField | ||||
|      * | ||||
|      * @returns Form control. | ||||
|      */ | ||||
|     protected createFormControl(field: AuthEmailSignupProfileField): FormControl { | ||||
|     protected createFormControl(field: AuthEmailSignupProfileField): FormControl<string | undefined> { | ||||
|         const formData = { | ||||
|             value: field.defaultdata != '0' ? field.defaultdata : undefined, | ||||
|             disabled: this.disabled, | ||||
|         }; | ||||
| 
 | ||||
|         return new FormControl(formData, this.required && !field.locked ? Validators.required : null); | ||||
|         return new FormControl(formData, { | ||||
|             validators: this.required && !field.locked ? Validators.required : null, | ||||
|             nonNullable: true, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -11,9 +11,17 @@ | ||||
|         </ion-button> | ||||
|     </core-navbar-buttons> | ||||
| 
 | ||||
|     <iframe #iframe class="core-iframe" [attr.id]="id" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl" | ||||
|         [attr.allowfullscreen]="allowFullscreen ? 'allowfullscreen' : null" [class.core-iframe-loading]="loading"> | ||||
|     </iframe> | ||||
|     <!-- allowfullscreen cannot be set dynamically using attribute binding, define 2 iframe elements. --> | ||||
|     <ng-container *ngIf="allowFullscreen"> | ||||
|         <iframe #iframe class="core-iframe" [attr.id]="id" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl" | ||||
|             allowfullscreen="true" [class.core-iframe-loading]="loading"> | ||||
|         </iframe> | ||||
|     </ng-container> | ||||
|     <ng-container *ngIf="!allowFullscreen"> | ||||
|         <iframe #iframe class="core-iframe" [attr.id]="id" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl" | ||||
|             [class.core-iframe-loading]="loading"> | ||||
|         </iframe> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <ion-button *ngIf="!loading && displayHelp" expand="block" fill="clear" (click)="openIframeHelpModal()" aria-haspopup="dialog" | ||||
|         class="core-button-as-link core-iframe-help"> | ||||
|  | ||||
| @ -85,12 +85,6 @@ export class CoreIframeComponent implements OnChanges, OnDestroy { | ||||
| 
 | ||||
|         this.initialized = true; | ||||
| 
 | ||||
|         this.iframeWidth = (this.iframeWidth && CoreDomUtils.formatPixelsSize(this.iframeWidth)) || '100%'; | ||||
|         this.iframeHeight = (this.iframeHeight && CoreDomUtils.formatPixelsSize(this.iframeHeight)) || '100%'; | ||||
|         this.allowFullscreen = CoreUtils.isTrueOrOne(this.allowFullscreen); | ||||
|         this.showFullscreenOnToolbar = CoreUtils.isTrueOrOne(this.showFullscreenOnToolbar); | ||||
|         this.autoFullscreenOnRotate = CoreUtils.isTrueOrOne(this.autoFullscreenOnRotate); | ||||
| 
 | ||||
|         if (this.showFullscreenOnToolbar || this.autoFullscreenOnRotate) { | ||||
|             // Leave fullscreen when navigating.
 | ||||
|             this.navSubscription = Router.events | ||||
| @ -157,6 +151,22 @@ export class CoreIframeComponent implements OnChanges, OnDestroy { | ||||
|      * Detect changes on input properties. | ||||
|      */ | ||||
|     async ngOnChanges(changes: {[name: string]: SimpleChange }): Promise<void> { | ||||
|         if (changes.iframeWidth) { | ||||
|             this.iframeWidth = (this.iframeWidth && CoreDomUtils.formatPixelsSize(this.iframeWidth)) || '100%'; | ||||
|         } | ||||
|         if (changes.iframeHeight) { | ||||
|             this.iframeHeight = (this.iframeHeight && CoreDomUtils.formatPixelsSize(this.iframeHeight)) || '100%'; | ||||
|         } | ||||
|         if (changes.allowFullscreen) { | ||||
|             this.allowFullscreen = CoreUtils.isTrueOrOne(this.allowFullscreen); | ||||
|         } | ||||
|         if (changes.showFullscreenOnToolbar) { | ||||
|             this.showFullscreenOnToolbar = CoreUtils.isTrueOrOne(this.showFullscreenOnToolbar); | ||||
|         } | ||||
|         if (changes.autoFullscreenOnRotate) { | ||||
|             this.autoFullscreenOnRotate = CoreUtils.isTrueOrOne(this.autoFullscreenOnRotate); | ||||
|         } | ||||
| 
 | ||||
|         if (!changes.src) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @ -40,7 +40,7 @@ import { FormControl } from '@angular/forms'; | ||||
| }) | ||||
| export class CoreInputErrorsComponent implements OnInit, OnChanges { | ||||
| 
 | ||||
|     @Input() control?: FormControl; // Needed to be able to check the validity of the input.
 | ||||
|     @Input() control?: FormControl<unknown>; // Needed to be able to check the validity of the input.
 | ||||
|     @Input() errorMessages: Record<string, string> = {}; // Error messages to show. Keys must be the name of the error.
 | ||||
|     @Input() errorText = ''; // Set other non automatic errors.
 | ||||
|     errorKeys: string[] = []; | ||||
|  | ||||
| @ -47,8 +47,8 @@ export class CoreSitesListComponent<T extends CoreSiteBasicInfo> { | ||||
|     @Input() currentSiteClickable?: boolean; // If set, specify a different clickable value for current site.
 | ||||
|     @Output() onSiteClicked = new EventEmitter<T>(); | ||||
| 
 | ||||
|     @ContentChild('siteItem') siteItemTemplate?: TemplateRef<unknown>; | ||||
|     @ContentChild('siteLabel') siteLabelTemplate?: TemplateRef<unknown>; | ||||
|     @ContentChild('siteItem') siteItemTemplate?: TemplateRef<{site: T; isCurrentSite: boolean}>; | ||||
|     @ContentChild('siteLabel') siteLabelTemplate?: TemplateRef<{site: T; isCurrentSite: boolean}>; | ||||
| 
 | ||||
|     /** | ||||
|      * Check whether a site is clickable. | ||||
|  | ||||
| @ -67,7 +67,7 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe | ||||
|         }, 0); | ||||
|     } | ||||
| 
 | ||||
|     @ContentChild(TemplateRef) template?: TemplateRef<unknown>; // Template defined by the content.
 | ||||
|     @ContentChild(TemplateRef) template?: TemplateRef<{item: Item; active: boolean}>; // Template defined by the content.
 | ||||
| 
 | ||||
|     protected hostElement: HTMLElement; | ||||
|     protected unsubscribe?: () => void; | ||||
|  | ||||
| @ -68,7 +68,7 @@ export class CoreTabComponent implements OnInit, OnDestroy, CoreTabBase { | ||||
|     @Input() id = ''; // An ID to identify the tab.
 | ||||
|     @Output() ionSelect: EventEmitter<CoreTabComponent> = new EventEmitter<CoreTabComponent>(); | ||||
| 
 | ||||
|     @ContentChild(TemplateRef) template?: TemplateRef<unknown>; // Template defined by the content.
 | ||||
|     @ContentChild(TemplateRef) template?: TemplateRef<void>; // Template defined by the content.
 | ||||
| 
 | ||||
|     element: HTMLElement; // The core-tab element.
 | ||||
|     loaded = false; | ||||
|  | ||||
| @ -66,7 +66,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, | ||||
|     // @todo Implement ControlValueAccessor https://angular.io/api/forms/ControlValueAccessor.
 | ||||
| 
 | ||||
|     @Input() placeholder = ''; // Placeholder to set in textarea.
 | ||||
|     @Input() control?: FormControl; // Form control.
 | ||||
|     @Input() control?: FormControl<string | undefined | null>; // Form control.
 | ||||
|     @Input() name = 'core-rich-text-editor'; // Name to set to the textarea.
 | ||||
|     @Input() component?: string; // The component to link the files to.
 | ||||
|     @Input() componentId?: number; // An ID to use in conjunction with the component.
 | ||||
| @ -75,7 +75,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, | ||||
|     @Input() contextInstanceId?: number; // The instance ID related to the context.
 | ||||
|     @Input() elementId?: string; // An ID to set to the element.
 | ||||
|     @Input() draftExtraParams?: Record<string, unknown>; // Extra params to identify the draft.
 | ||||
|     @Output() contentChanged: EventEmitter<string>; | ||||
|     @Output() contentChanged: EventEmitter<string | undefined | null>; | ||||
| 
 | ||||
|     @ViewChild('editor') editor?: ElementRef; // WYSIWYG editor.
 | ||||
|     @ViewChild('textarea') textarea?: IonTextarea; // Textarea editor.
 | ||||
| @ -190,8 +190,8 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, | ||||
|         // Setup the editor.
 | ||||
|         this.editorElement = this.editor?.nativeElement as HTMLDivElement; | ||||
|         this.setContent(this.control?.value); | ||||
|         this.originalContent = this.control?.value; | ||||
|         this.lastDraft = this.control?.value; | ||||
|         this.originalContent = this.control?.value ?? undefined; | ||||
|         this.lastDraft = this.control?.value ?? ''; | ||||
| 
 | ||||
|         // Use paragraph on enter.
 | ||||
|         // eslint-disable-next-line deprecation/deprecation
 | ||||
| @ -261,19 +261,19 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, | ||||
| 
 | ||||
|             // Apply the new content.
 | ||||
|             this.setContent(newValue); | ||||
|             this.originalContent = newValue; | ||||
|             this.originalContent = newValue ?? undefined; | ||||
|             this.infoMessage = undefined; | ||||
| 
 | ||||
|             // Save a draft so the original content is saved.
 | ||||
|             this.lastDraft = newValue; | ||||
|             this.lastDraft = newValue ?? ''; | ||||
|             CoreEditorOffline.saveDraft( | ||||
|                 this.contextLevel || '', | ||||
|                 this.contextInstanceId || 0, | ||||
|                 this.elementId || '', | ||||
|                 this.draftExtraParams || {}, | ||||
|                 this.pageInstance, | ||||
|                 newValue, | ||||
|                 newValue, | ||||
|                 this.lastDraft, | ||||
|                 this.originalContent, | ||||
|             ); | ||||
|         }); | ||||
| 
 | ||||
| @ -579,7 +579,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, | ||||
|      * @param value text | ||||
|      * @returns If value is null only a white space. | ||||
|      */ | ||||
|     protected isNullOrWhiteSpace(value: string | null): boolean { | ||||
|     protected isNullOrWhiteSpace(value: string | null | undefined): boolean { | ||||
|         if (value == null || value === undefined) { | ||||
|             return true; | ||||
|         } | ||||
| @ -595,7 +595,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, | ||||
|      * | ||||
|      * @param value New content. | ||||
|      */ | ||||
|     protected setContent(value: string | null): void { | ||||
|     protected setContent(value: string | null | undefined): void { | ||||
|         if (!this.editorElement || !this.textarea) { | ||||
|             return; | ||||
|         } | ||||
| @ -974,7 +974,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const newText = this.control.value; | ||||
|             const newText = this.control.value ?? ''; | ||||
| 
 | ||||
|             if (this.lastDraft == newText) { | ||||
|                 // Text hasn't changed, nothing to save.
 | ||||
|  | ||||
| @ -69,8 +69,8 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
| 
 | ||||
|     // Data for age verification.
 | ||||
|     ageVerificationForm: FormGroup; | ||||
|     countryControl: FormControl; | ||||
|     signUpCountryControl?: FormControl; | ||||
|     countryControl: FormControl<string>; | ||||
|     signUpCountryControl?: FormControl<string>; | ||||
|     isMinor = false; // Whether the user is minor age.
 | ||||
|     ageDigitalConsentVerification?: boolean; // Whether the age verification is enabled.
 | ||||
|     supportName?: string; | ||||
| @ -93,7 +93,7 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
|         this.ageVerificationForm = this.fb.group({ | ||||
|             age: ['', Validators.required], | ||||
|         }); | ||||
|         this.countryControl = this.fb.control('', Validators.required); | ||||
|         this.countryControl = this.fb.control('', { validators: Validators.required, nonNullable: true }); | ||||
|         this.ageVerificationForm.addControl('country', this.countryControl); | ||||
| 
 | ||||
|         // Create the signupForm with the basic controls. More controls will be added later.
 | ||||
| @ -141,7 +141,7 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
|      */ | ||||
|     protected completeFormGroup(): void { | ||||
|         this.signupForm.addControl('city', this.fb.control(this.settings?.defaultcity || '')); | ||||
|         this.signUpCountryControl = this.fb.control(this.settings?.country || ''); | ||||
|         this.signUpCountryControl = this.fb.control(this.settings?.country || '', { nonNullable: true }); | ||||
|         this.signupForm.addControl('country', this.signUpCountryControl); | ||||
| 
 | ||||
|         // Add the name fields.
 | ||||
|  | ||||
| @ -24,7 +24,7 @@ import { CoreUserProfileField } from '@features/user/services/user'; | ||||
| @Component({ | ||||
|     template: '', | ||||
| }) | ||||
| export abstract class CoreUserProfileFieldBaseComponent implements OnInit { | ||||
| export abstract class CoreUserProfileFieldBaseComponent<T = string> implements OnInit { | ||||
| 
 | ||||
|     @Input() field?: AuthEmailSignupProfileField | CoreUserProfileField; // The profile field to be rendered.
 | ||||
|     @Input() signup = false; // True if editing the field in signup. Defaults to false.
 | ||||
| @ -36,7 +36,7 @@ export abstract class CoreUserProfileFieldBaseComponent implements OnInit { | ||||
|     @Input() contextInstanceId?: number; // The instance ID related to the context.
 | ||||
|     @Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters.
 | ||||
| 
 | ||||
|     control?: FormControl; | ||||
|     control?: FormControl<T>; | ||||
|     modelName = ''; | ||||
|     value?: string; | ||||
|     required?: boolean; | ||||
| @ -91,13 +91,16 @@ export abstract class CoreUserProfileFieldBaseComponent implements OnInit { | ||||
|      * | ||||
|      * @returns Form control. | ||||
|      */ | ||||
|     protected createFormControl(field: AuthEmailSignupProfileField): FormControl { | ||||
|     protected createFormControl(field: AuthEmailSignupProfileField): FormControl<T> { | ||||
|         const formData = { | ||||
|             value: field.defaultdata, | ||||
|             value: (field.defaultdata ?? '') as T, | ||||
|             disabled: this.disabled, | ||||
|         }; | ||||
| 
 | ||||
|         return new FormControl(formData, this.required && !field.locked ? Validators.required : null); | ||||
|         return new FormControl(formData, { | ||||
|             validators: this.required && !field.locked ? Validators.required : null, | ||||
|             nonNullable: true, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -312,17 +312,12 @@ export class CoreMimetypeUtilsProvider { | ||||
|      * @returns The lowercased extension without the dot, or undefined. | ||||
|      */ | ||||
|     guessExtensionFromUrl(fileUrl: string): string | undefined { | ||||
|         const split = CoreUrl.removeUrlAnchor(fileUrl).split('.'); | ||||
|         const parsed = CoreUrl.parse(fileUrl); | ||||
|         const split = parsed?.path?.split('.'); | ||||
|         let extension: string | undefined; | ||||
| 
 | ||||
|         if (split.length > 1) { | ||||
|             let candidate = split[split.length - 1].toLowerCase(); | ||||
|             // Remove params if any.
 | ||||
|             const position = candidate.indexOf('?'); | ||||
|             if (position > -1) { | ||||
|                 candidate = candidate.substring(0, position); | ||||
|             } | ||||
| 
 | ||||
|         if (split && split.length > 1) { | ||||
|             const candidate = split[split.length - 1].toLowerCase(); | ||||
|             if (EXTENSION_REGEX.test(candidate)) { | ||||
|                 extension = candidate; | ||||
|             } | ||||
|  | ||||
| @ -22,10 +22,10 @@ import { catchError, filter } from 'rxjs/operators'; | ||||
|  * @param control Form control. | ||||
|  * @returns Form control value observable. | ||||
|  */ | ||||
| export function formControlValue<T = unknown>(control: FormControl): Observable<T> { | ||||
| export function formControlValue<T = unknown>(control: FormControl<T | null>): Observable<T> { | ||||
|     return control.valueChanges.pipe( | ||||
|         startWithOnSubscribed(() => control.value), | ||||
|         filter(value => value !== null), | ||||
|         filter((value): value is T => value !== null), | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user