forked from EVOgeek/Vmeda.Online
		
	
						commit
						686f35d544
					
				| @ -3,6 +3,9 @@ | |||||||
| :host { | :host { | ||||||
|     .core-horizontal-scroll div.core-horizontal-scroll-item { |     .core-horizontal-scroll div.core-horizontal-scroll-item { | ||||||
|         @include horizontal_scroll_item(80%, 250px, 300px); |         @include horizontal_scroll_item(80%, 250px, 300px); | ||||||
|  |         ion-card { | ||||||
|  |             height: auto; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .core-course-module-handler { |     .core-course-module-handler { | ||||||
|  | |||||||
| @ -190,7 +190,7 @@ export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHan | |||||||
|     protected insertSpan(text: string, start: number, end: number): string { |     protected insertSpan(text: string, start: number, end: number): string { | ||||||
|         return CoreTextUtils.substrReplace( |         return CoreTextUtils.substrReplace( | ||||||
|             text, |             text, | ||||||
|             '<span class="nolink">' + text.substr(start, end - start + 1) + '</span>', |             '<span class="nolink">' + text.substring(start, end - start + 1) + '</span>', | ||||||
|             start, |             start, | ||||||
|             end - start + 1, |             end - start + 1, | ||||||
|         ); |         ); | ||||||
| @ -265,7 +265,7 @@ export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHan | |||||||
|      */ |      */ | ||||||
|     protected fixUseUrls(node: Element): void { |     protected fixUseUrls(node: Element): void { | ||||||
|         Array.from(node.querySelectorAll('use')).forEach((useElem) => { |         Array.from(node.querySelectorAll('use')).forEach((useElem) => { | ||||||
|             useElem.setAttribute('href', useElem.href.baseVal.substr(useElem.href.baseVal.indexOf('#'))); |             useElem.setAttribute('href', useElem.href.baseVal.substring(useElem.href.baseVal.indexOf('#'))); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -40,9 +40,11 @@ | |||||||
|                     [attr.aria-label]="(favourites.expanded ? 'core.collapse' : 'core.expand') | translate" |                     [attr.aria-label]="(favourites.expanded ? 'core.collapse' : 'core.expand') | translate" | ||||||
|                     [attr.aria-expanded]="favourites.expanded" aria-controls="addon-messages-groupconversations-favourite" role="heading" |                     [attr.aria-expanded]="favourites.expanded" aria-controls="addon-messages-groupconversations-favourite" role="heading" | ||||||
|                     detail="false"> |                     detail="false"> | ||||||
|                     <ion-icon *ngIf="!favourites.expanded" name="fas-caret-right" flip-rtl slot="start" aria-hidden="true"> |                     <ion-icon *ngIf="!favourites.expanded" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" | ||||||
|  |                         class="expandable-status-icon"> | ||||||
|                     </ion-icon> |                     </ion-icon> | ||||||
|                     <ion-icon *ngIf="favourites.expanded" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon> |                     <ion-icon *ngIf="favourites.expanded" name="fas-chevron-down" slot="start" aria-hidden="true" | ||||||
|  |                         class="expandable-status-icon"></ion-icon> | ||||||
|                     <ion-label> |                     <ion-label> | ||||||
|                         <h2>{{ 'core.favourites' | translate }} ({{ favourites.count }})</h2> |                         <h2>{{ 'core.favourites' | translate }} ({{ favourites.count }})</h2> | ||||||
|                     </ion-label> |                     </ion-label> | ||||||
| @ -74,8 +76,10 @@ | |||||||
|                 <ion-item button class="divider ion-text-wrap" (click)="toggle(group)" sticky="true" |                 <ion-item button class="divider ion-text-wrap" (click)="toggle(group)" sticky="true" | ||||||
|                     [attr.aria-label]="(group.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-expanded]="group.expanded" |                     [attr.aria-label]="(group.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-expanded]="group.expanded" | ||||||
|                     aria-controls="addon-messages-groupconversations-group" role="heading" detail="false"> |                     aria-controls="addon-messages-groupconversations-group" role="heading" detail="false"> | ||||||
|                     <ion-icon *ngIf="!group.expanded" name="fas-caret-right" flip-rtl slot="start" aria-hidden="true"></ion-icon> |                     <ion-icon *ngIf="!group.expanded" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" | ||||||
|                     <ion-icon *ngIf="group.expanded" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon> |                         class="expandable-status-icon"></ion-icon> | ||||||
|  |                     <ion-icon *ngIf="group.expanded" name="fas-chevron-down" slot="start" aria-hidden="true" class="expandable-status-icon"> | ||||||
|  |                     </ion-icon> | ||||||
|                     <ion-label> |                     <ion-label> | ||||||
|                         <h2>{{ 'addon.messages.groupconversations' | translate }} ({{ group.count }})</h2> |                         <h2>{{ 'addon.messages.groupconversations' | translate }} ({{ group.count }})</h2> | ||||||
|                     </ion-label> |                     </ion-label> | ||||||
| @ -107,9 +111,11 @@ | |||||||
|                     [attr.aria-label]="(individual.expanded ? 'core.collapse' : 'core.expand') | translate" |                     [attr.aria-label]="(individual.expanded ? 'core.collapse' : 'core.expand') | translate" | ||||||
|                     [attr.aria-expanded]="individual.expanded" aria-controls="addon-messages-groupconversations-individual" role="heading" |                     [attr.aria-expanded]="individual.expanded" aria-controls="addon-messages-groupconversations-individual" role="heading" | ||||||
|                     detail="false"> |                     detail="false"> | ||||||
|                     <ion-icon *ngIf="!individual.expanded" name="fas-caret-right" flip-rtl slot="start" aria-hidden="true"> |                     <ion-icon *ngIf="!individual.expanded" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" | ||||||
|  |                         class="expandable-status-icon"> | ||||||
|                     </ion-icon> |                     </ion-icon> | ||||||
|                     <ion-icon *ngIf="individual.expanded" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon> |                     <ion-icon *ngIf="individual.expanded" name="fas-chevron-down" slot="start" aria-hidden="true" | ||||||
|  |                         class="expandable-status-icon"></ion-icon> | ||||||
|                     <ion-label> |                     <ion-label> | ||||||
|                         <h2>{{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }})</h2> |                         <h2>{{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }})</h2> | ||||||
|                     </ion-label> |                     </ion-label> | ||||||
|  | |||||||
| @ -30,8 +30,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline"> |         [courseId]="courseId" [hasDataToSync]="hasOffline"> | ||||||
|         <ion-list inset="true" description *ngIf="assign && assign.introattachments && assign.introattachments.length"> |         <ion-list inset="true" description *ngIf="assign && assign.introattachments && assign.introattachments.length"> | ||||||
|             <core-file *ngFor="let file of assign.introattachments" [file]="file" [component]="component" [componentId]="componentId"> |             <core-file *ngFor="let file of assign.introattachments" [file]="file" [component]="component" [componentId]="componentId"> | ||||||
|             </core-file> |             </core-file> | ||||||
| @ -148,4 +148,5 @@ | |||||||
|     </addon-mod-assign-submission> |     </addon-mod-assign-submission> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"></core-course-module-navigation> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
|  | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -23,8 +23,8 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-page"> | <core-loading [hideUntil]="loaded" class="safe-area-page"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId"> |         [courseId]="courseId"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <ng-container *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)"> |     <ng-container *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)"> | ||||||
| @ -126,5 +126,5 @@ | |||||||
|     </ng-container> |     </ng-container> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -24,8 +24,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId"> |         [courseId]="courseId"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <ion-card class="core-warning-card" *ngIf="warning"> |     <ion-card class="core-warning-card" *ngIf="warning"> | ||||||
| @ -56,5 +56,5 @@ | |||||||
| 
 | 
 | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -27,8 +27,8 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-padding"> | <core-loading [hideUntil]="loaded" class="safe-area-padding"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId"> |         [courseId]="courseId"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <ion-card *ngIf="chatInfo" class="core-info-card"> |     <ion-card *ngIf="chatInfo" class="core-info-card"> | ||||||
| @ -48,5 +48,5 @@ | |||||||
|     </ng-container> |     </ng-container> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -53,14 +53,14 @@ export class AddonModChatHelperProvider { | |||||||
|         formattedMessage.message = formattedMessage.message.trim(); |         formattedMessage.message = formattedMessage.message.trim(); | ||||||
| 
 | 
 | ||||||
|         formattedMessage.showDate = this.showDate(message, prevMessage); |         formattedMessage.showDate = this.showDate(message, prevMessage); | ||||||
|         formattedMessage.beep = (message.message.substr(0, 5) == 'beep ' && message.message.substr(5).trim()) || undefined; |         formattedMessage.beep = (message.message.substring(0, 5) == 'beep ' && message.message.substring(5).trim()) || undefined; | ||||||
| 
 | 
 | ||||||
|         formattedMessage.special = !!formattedMessage.beep || (<AddonModChatSessionMessage> message).issystem || |         formattedMessage.special = !!formattedMessage.beep || (<AddonModChatSessionMessage> message).issystem || | ||||||
|             (<AddonModChatMessage> message).system; |             (<AddonModChatMessage> message).system; | ||||||
| 
 | 
 | ||||||
|         if (formattedMessage.message.substr(0, 4) == '/me ') { |         if (formattedMessage.message.substring(0, 4) == '/me ') { | ||||||
|             formattedMessage.special = true; |             formattedMessage.special = true; | ||||||
|             formattedMessage.message = formattedMessage.message.substr(4).trim(); |             formattedMessage.message = formattedMessage.message.substring(4).trim(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!formattedMessage.special && formattedMessage.message.match(patternTo)) { |         if (!formattedMessage.special && formattedMessage.message.match(patternTo)) { | ||||||
|  | |||||||
| @ -29,8 +29,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline"> |         [courseId]="courseId" [hasDataToSync]="hasOffline"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity availability messages --> |     <!-- Activity availability messages --> | ||||||
| @ -155,7 +155,7 @@ | |||||||
|     </ion-card> |     </ion-card> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
| 
 | 
 | ||||||
| <!-- Template to render a choice option label. --> | <!-- Template to render a choice option label. --> | ||||||
|  | |||||||
| @ -39,8 +39,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline || hasOfflineRatings"> |         [courseId]="courseId" [hasDataToSync]="hasOffline || hasOfflineRatings"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <ion-item class="ion-text-wrap" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)"> |     <ion-item class="ion-text-wrap" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)"> | ||||||
| @ -138,7 +138,7 @@ | |||||||
| 
 | 
 | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
| 
 | 
 | ||||||
| <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAdd"> | <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAdd"> | ||||||
|  | |||||||
| @ -52,7 +52,7 @@ export class AddonModDataFieldDateHandlerService implements AddonModDataFieldHan | |||||||
|         const enabledName = 'f_' + field.id + '_z'; |         const enabledName = 'f_' + field.id + '_z'; | ||||||
| 
 | 
 | ||||||
|         if (inputData[enabledName] && typeof inputData[fieldName] == 'string') { |         if (inputData[enabledName] && typeof inputData[fieldName] == 'string') { | ||||||
|             const date = inputData[fieldName].substr(0, 10).split('-'); |             const date = inputData[fieldName].substring(0, 10).split('-'); | ||||||
| 
 | 
 | ||||||
|             return [ |             return [ | ||||||
|                 { |                 { | ||||||
| @ -87,7 +87,7 @@ export class AddonModDataFieldDateHandlerService implements AddonModDataFieldHan | |||||||
|             return []; |             return []; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const date = inputData[fieldName].substr(0, 10).split('-'); |         const date = inputData[fieldName].substring(0, 10).split('-'); | ||||||
| 
 | 
 | ||||||
|         return [ |         return [ | ||||||
|             { |             { | ||||||
| @ -117,10 +117,10 @@ export class AddonModDataFieldDateHandlerService implements AddonModDataFieldHan | |||||||
|         originalFieldData: AddonModDataEntryField, |         originalFieldData: AddonModDataEntryField, | ||||||
|     ): boolean { |     ): boolean { | ||||||
|         const fieldName = 'f_' + field.id; |         const fieldName = 'f_' + field.id; | ||||||
|         const input = inputData[fieldName] && inputData[fieldName].substr(0, 10) || ''; |         const input = inputData[fieldName] && inputData[fieldName].substring(0, 10) || ''; | ||||||
| 
 | 
 | ||||||
|         const content = (originalFieldData && originalFieldData?.content && |         const content = (originalFieldData && originalFieldData?.content && | ||||||
|                 CoreTimeUtils.toDatetimeFormat(parseInt(originalFieldData.content, 10) * 1000).substr(0, 10)) || ''; |                 CoreTimeUtils.toDatetimeFormat(parseInt(originalFieldData.content, 10) * 1000).substring(0, 10)) || ''; | ||||||
| 
 | 
 | ||||||
|         return input != content; |         return input != content; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -29,8 +29,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline"> |         [courseId]="courseId" [hasDataToSync]="hasOffline"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <core-tabs [hideUntil]="tabsReady" [selectedIndex]="firstSelectedTab"> |     <core-tabs [hideUntil]="tabsReady" [selectedIndex]="firstSelectedTab"> | ||||||
| @ -55,7 +55,7 @@ | |||||||
|     </core-tabs> |     </core-tabs> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
| 
 | 
 | ||||||
| <ng-template #basicInfo> | <ng-template #basicInfo> | ||||||
|  | |||||||
| @ -26,8 +26,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info *ngIf="!subfolder" [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info *ngIf="!subfolder" [module]="module" [description]="description" [component]="component" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId"> |         [componentId]="componentId" [courseId]="courseId"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <ion-list *ngIf="contents && (contents.files.length + contents.folders.length > 0)"> |     <ion-list *ngIf="contents && (contents.files.length + contents.folders.length > 0)"> | ||||||
| @ -49,5 +49,5 @@ | |||||||
| 
 | 
 | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -53,10 +53,10 @@ export class AddonModFolderHelperProvider { | |||||||
|             let completePath = ''; |             let completePath = ''; | ||||||
| 
 | 
 | ||||||
|             // Remove first and last slash if needed.
 |             // Remove first and last slash if needed.
 | ||||||
|             if (path.substr(0, 1) === '/') { |             if (path.substring(0, 1) === '/') { | ||||||
|                 path = path.substr(1); |                 path = path.substring(1); | ||||||
|             } |             } | ||||||
|             if (path.substr(path.length - 1) === '/') { |             if (path.substring(path.length - 1) === '/') { | ||||||
|                 path = path.slice(0, -1); |                 path = path.slice(0, -1); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -38,10 +38,9 @@ | |||||||
| 
 | 
 | ||||||
|     <core-loading [hideUntil]="discussions && discussions.loaded"> |     <core-loading [hideUntil]="discussions && discussions.loaded"> | ||||||
|         <!-- Activity info. --> |         <!-- Activity info. --> | ||||||
|         <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" |         <core-course-module-info [module]="module" [description]="forum && forum.type != 'single' && description" [component]="component" | ||||||
|             [description]="forum && forum.type != 'single' && description" [component]="component" [componentId]="componentId" |             [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline || hasOfflineRatings"> | ||||||
|             [courseId]="courseId" [hasDataToSync]="hasOffline || hasOfflineRatings"> |             <ion-item lines="none" class="ion-text-wrap"> | ||||||
|             <ion-item> |  | ||||||
|                 <ion-label> |                 <ion-label> | ||||||
|                     {{descriptionNote}} |                     {{descriptionNote}} | ||||||
|                 </ion-label> |                 </ion-label> | ||||||
| @ -140,7 +139,8 @@ | |||||||
|         </ng-container> |         </ng-container> | ||||||
|     </core-loading> |     </core-loading> | ||||||
| 
 | 
 | ||||||
|     <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> |     <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" | ||||||
|  |         (completionChanged)="onCompletionChange()"> | ||||||
|     </core-course-module-navigation> |     </core-course-module-navigation> | ||||||
| 
 | 
 | ||||||
|     <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="forum && canAddDiscussion"> |     <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="forum && canAddDiscussion"> | ||||||
|  | |||||||
| @ -118,8 +118,10 @@ | |||||||
|             <ion-item button class="divider ion-text-wrap" (click)="toggleAdvanced()" detail="false" [attr.aria-expanded]="advanced" |             <ion-item button class="divider ion-text-wrap" (click)="toggleAdvanced()" detail="false" [attr.aria-expanded]="advanced" | ||||||
|                 [attr.aria-controls]="'addon-forum-reply-edit-form-advanced-' + uniqueId" |                 [attr.aria-controls]="'addon-forum-reply-edit-form-advanced-' + uniqueId" | ||||||
|                 [attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate"> |                 [attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate"> | ||||||
|                 <ion-icon *ngIf="!advanced" name="fas-caret-right" flip-rtl slot="start" aria-hidden="true"></ion-icon> |                 <ion-icon *ngIf="!advanced" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" | ||||||
|                 <ion-icon *ngIf="advanced" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon> |                     class="expandable-status-icon"></ion-icon> | ||||||
|  |                 <ion-icon *ngIf="advanced" name="fas-chevron-down" slot="start" aria-hidden="true" class="expandable-status-icon"> | ||||||
|  |                 </ion-icon> | ||||||
|                 <ion-label> |                 <ion-label> | ||||||
|                     <h2>{{ 'addon.mod_forum.advanced' | translate }}</h2> |                     <h2>{{ 'addon.mod_forum.advanced' | translate }}</h2> | ||||||
|                 </ion-label> |                 </ion-label> | ||||||
|  | |||||||
| @ -34,8 +34,10 @@ | |||||||
|             <ion-item button class="divider ion-text-wrap" (click)="toggleAdvanced()" detail="false" [attr.aria-expanded]="advanced" |             <ion-item button class="divider ion-text-wrap" (click)="toggleAdvanced()" detail="false" [attr.aria-expanded]="advanced" | ||||||
|                 [attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate" role="heading" |                 [attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate" role="heading" | ||||||
|                 aria-controls="addon-mod-forum-new-discussion-advanced"> |                 aria-controls="addon-mod-forum-new-discussion-advanced"> | ||||||
|                 <ion-icon *ngIf="!advanced" name="fas-caret-right" flip-rtl slot="start" aria-hidden="true"></ion-icon> |                 <ion-icon *ngIf="!advanced" name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" | ||||||
|                 <ion-icon *ngIf="advanced" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon> |                     class="expandable-status-icon"></ion-icon> | ||||||
|  |                 <ion-icon *ngIf="advanced" name="fas-chevron-down" slot="start" aria-hidden="true" class="expandable-status-icon"> | ||||||
|  |                 </ion-icon> | ||||||
|                 <ion-label> |                 <ion-label> | ||||||
|                     <h2>{{ 'addon.mod_forum.advanced' | translate }}</h2> |                     <h2>{{ 'addon.mod_forum.advanced' | translate }}</h2> | ||||||
|                 </ion-label> |                 </ion-label> | ||||||
|  | |||||||
| @ -50,8 +50,8 @@ | |||||||
| 
 | 
 | ||||||
|     <core-loading [hideUntil]="loaded"> |     <core-loading [hideUntil]="loaded"> | ||||||
|         <!-- Activity info. --> |         <!-- Activity info. --> | ||||||
|         <core-course-module-info *ngIf="!isSearch" [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |         <core-course-module-info *ngIf="!isSearch" [module]="module" [description]="description" [component]="component" | ||||||
|             [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline || hasOfflineRatings"> |             [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline || hasOfflineRatings"> | ||||||
|         </core-course-module-info> |         </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|         <ion-list *ngIf="!isSearch && entries && entries.offlineEntries.length > 0"> |         <ion-list *ngIf="!isSearch && entries && entries.offlineEntries.length > 0"> | ||||||
| @ -96,7 +96,8 @@ | |||||||
|         </core-infinite-loading> |         </core-infinite-loading> | ||||||
|     </core-loading> |     </core-loading> | ||||||
| 
 | 
 | ||||||
|     <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> |     <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" | ||||||
|  |         (completionChanged)="onCompletionChange()"> | ||||||
|     </core-course-module-navigation> |     </core-course-module-navigation> | ||||||
| 
 | 
 | ||||||
|     <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAdd"> |     <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAdd"> | ||||||
|  | |||||||
| @ -292,7 +292,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity | |||||||
|                     // Try to get the first letter without HTML tags.
 |                     // Try to get the first letter without HTML tags.
 | ||||||
|                     const noTags = CoreTextUtils.cleanTags(entry.concept); |                     const noTags = CoreTextUtils.cleanTags(entry.concept); | ||||||
| 
 | 
 | ||||||
|                     return (noTags || entry.concept).substr(0, 1).toUpperCase(); |                     return (noTags || entry.concept).substring(0, 1).toUpperCase(); | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|                 this.getDivider = getDivider; |                 this.getDivider = getDivider; | ||||||
|  | |||||||
| @ -36,8 +36,8 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-padding"> | <core-loading [hideUntil]="loaded" class="safe-area-padding"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline"> |         [courseId]="courseId" [hasDataToSync]="hasOffline"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <!-- Offline disabled. --> |     <!-- Offline disabled. --> | ||||||
| @ -85,5 +85,5 @@ | |||||||
|     </core-h5p-iframe> |     </core-h5p-iframe> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -29,7 +29,7 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-padding"> | <core-loading [hideUntil]="loaded" class="safe-area-padding"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()"> |     <core-course-module-info [module]="module"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <ion-card class="core-warning-card" *ngIf="warning"> |     <ion-card class="core-warning-card" *ngIf="warning"> | ||||||
| @ -46,5 +46,5 @@ | |||||||
|     </div> |     </div> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -155,7 +155,7 @@ export class AddonModImscpProvider { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const filePath = CoreTextUtils.concatenatePaths(item.filepath, item.filename); |             const filePath = CoreTextUtils.concatenatePaths(item.filepath, item.filename); | ||||||
|             const filePathAlt = filePath.charAt(0) === '/' ? filePath.substr(1) : '/' + filePath; |             const filePathAlt = filePath.charAt(0) === '/' ? filePath.substring(1) : '/' + filePath; | ||||||
| 
 | 
 | ||||||
|             // Check if it's main file.
 |             // Check if it's main file.
 | ||||||
|             return filePath === targetFilePath || filePathAlt === targetFilePath; |             return filePath === targetFilePath || filePathAlt === targetFilePath; | ||||||
|  | |||||||
| @ -33,8 +33,8 @@ | |||||||
|             <ng-template> |             <ng-template> | ||||||
| 
 | 
 | ||||||
|                 <!-- Activity info. --> |                 <!-- Activity info. --> | ||||||
|                 <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |                 <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|                     [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline"> |                     [courseId]="courseId" [hasDataToSync]="hasOffline"> | ||||||
|                 </core-course-module-info> |                 </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|                 <!-- Prevent access messages. Only show the first one. --> |                 <!-- Prevent access messages. Only show the first one. --> | ||||||
| @ -298,5 +298,5 @@ | |||||||
|     </core-tabs> |     </core-tabs> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -760,8 +760,8 @@ export class AddonModLessonProvider { | |||||||
|             let ignoreCase = ''; |             let ignoreCase = ''; | ||||||
| 
 | 
 | ||||||
|             if (useRegExp) { |             if (useRegExp) { | ||||||
|                 if (expectedAnswer.substr(-2) == '/i') { |                 if (expectedAnswer.substring(-2) == '/i') { | ||||||
|                     expectedAnswer = expectedAnswer.substr(0, expectedAnswer.length - 2); |                     expectedAnswer = expectedAnswer.substring(0, expectedAnswer.length - 2); | ||||||
|                     ignoreCase = 'i'; |                     ignoreCase = 'i'; | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
| @ -792,12 +792,12 @@ export class AddonModLessonProvider { | |||||||
|                         isMatch = true; |                         isMatch = true; | ||||||
|                     } |                     } | ||||||
|                 } else { // We are using regular expressions analysis.
 |                 } else { // We are using regular expressions analysis.
 | ||||||
|                     const startCode = expectedAnswer.substr(0, 2); |                     const startCode = expectedAnswer.substring(0, 2); | ||||||
| 
 | 
 | ||||||
|                     switch (startCode){ |                     switch (startCode){ | ||||||
|                         // 1- Check for absence of required string in studentAnswer (coded by initial '--').
 |                         // 1- Check for absence of required string in studentAnswer (coded by initial '--').
 | ||||||
|                         case '--': |                         case '--': | ||||||
|                             expectedAnswer = expectedAnswer.substr(2); |                             expectedAnswer = expectedAnswer.substring(2); | ||||||
|                             if (!studentAnswer.match(new RegExp('^' + expectedAnswer + '$', ignoreCase))) { |                             if (!studentAnswer.match(new RegExp('^' + expectedAnswer + '$', ignoreCase))) { | ||||||
|                                 isMatch = true; |                                 isMatch = true; | ||||||
|                             } |                             } | ||||||
| @ -805,7 +805,7 @@ export class AddonModLessonProvider { | |||||||
| 
 | 
 | ||||||
|                         // 2- Check for code for marking wrong strings (coded by initial '++').
 |                         // 2- Check for code for marking wrong strings (coded by initial '++').
 | ||||||
|                         case '++': { |                         case '++': { | ||||||
|                             expectedAnswer = expectedAnswer.substr(2); |                             expectedAnswer = expectedAnswer.substring(2); | ||||||
| 
 | 
 | ||||||
|                             // Check for one or several matches.
 |                             // Check for one or several matches.
 | ||||||
|                             const matches = studentAnswer.match(new RegExp(expectedAnswer, 'g' + ignoreCase)); |                             const matches = studentAnswer.match(new RegExp(expectedAnswer, 'g' + ignoreCase)); | ||||||
|  | |||||||
| @ -20,9 +20,8 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-padding"> | <core-loading [hideUntil]="loaded" class="safe-area-padding"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" |     <core-course-module-info [module]="module" [description]="lti && lti.showdescriptionlaunch && description" [component]="component" | ||||||
|         [description]="lti && lti.showdescriptionlaunch && description" [component]="component" [componentId]="componentId" |         [componentId]="componentId" [courseId]="courseId"> | ||||||
|         [courseId]="courseId"> |  | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <div class="ion-padding"> |     <div class="ion-padding"> | ||||||
| @ -33,5 +32,5 @@ | |||||||
|     </div> |     </div> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -26,8 +26,8 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-padding"> | <core-loading [hideUntil]="loaded" class="safe-area-padding"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="displayDescription && description" |     <core-course-module-info [module]="module" [description]="displayDescription && description" [component]="component" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId"> |         [componentId]="componentId" [courseId]="courseId"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <ion-card class="core-warning-card" *ngIf="warning"> |     <ion-card class="core-warning-card" *ngIf="warning"> | ||||||
| @ -49,5 +49,5 @@ | |||||||
| 
 | 
 | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -52,7 +52,7 @@ export class AddonModPageHelperProvider { | |||||||
|                 let key = content.filename; |                 let key = content.filename; | ||||||
|                 if (content.filepath !== '/') { |                 if (content.filepath !== '/') { | ||||||
|                     // Add the folders without the leading slash.
 |                     // Add the folders without the leading slash.
 | ||||||
|                     key = content.filepath.substr(1) + key; |                     key = content.filepath.substring(1) + key; | ||||||
|                 } |                 } | ||||||
|                 paths[CoreTextUtils.decodeURIComponent(key)] = url; |                 paths[CoreTextUtils.decodeURIComponent(key)] = url; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -29,9 +29,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId" |         [courseId]="courseId" [hasDataToSync]="buttonText && hasOffline && !showStatusSpinner"> | ||||||
|         [hasDataToSync]="buttonText && hasOffline && !showStatusSpinner"> |  | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <!-- Access rules description messages. --> |     <!-- Access rules description messages. --> | ||||||
| @ -227,5 +226,5 @@ | |||||||
|     </ion-card> |     </ion-card> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -54,7 +54,7 @@ export class AddonModQuizOfflineProvider { | |||||||
|             if (!questionsWithAnswers[slot]) { |             if (!questionsWithAnswers[slot]) { | ||||||
|                 questionsWithAnswers[slot] = { |                 questionsWithAnswers[slot] = { | ||||||
|                     answers: {}, |                     answers: {}, | ||||||
|                     prefix: name.substr(0, name.indexOf(nameWithoutPrefix)), |                     prefix: name.substring(0, name.indexOf(nameWithoutPrefix)), | ||||||
|                 }; |                 }; | ||||||
|             } |             } | ||||||
|             questionsWithAnswers[slot].answers[nameWithoutPrefix] = answers[name]; |             questionsWithAnswers[slot].answers[nameWithoutPrefix] = answers[name]; | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-padding"> | <core-loading [hideUntil]="loaded" class="safe-area-padding"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" [courseId]="courseId" (completionChanged)="onCompletionChange()" |     <core-course-module-info [module]="module" [courseId]="courseId" | ||||||
|         [description]="mode != 'iframe' && (mode != 'embedded' || displayDescription) && description" [component]="component" |         [description]="mode != 'iframe' && (mode != 'embedded' || displayDescription) && description" [component]="component" | ||||||
|         [componentId]="componentId"> |         [componentId]="componentId"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| @ -61,5 +61,5 @@ | |||||||
|     </ng-container> |     </ng-container> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -70,7 +70,7 @@ export class AddonModResourceHelperProvider { | |||||||
|         let mainFilePath = mainFile.filename; |         let mainFilePath = mainFile.filename; | ||||||
| 
 | 
 | ||||||
|         if (mainFile.filepath !== '/') { |         if (mainFile.filepath !== '/') { | ||||||
|             mainFilePath = mainFile.filepath.substr(1) + mainFilePath; |             mainFilePath = mainFile.filepath.substring(1) + mainFilePath; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|  | |||||||
| @ -201,7 +201,7 @@ export class AddonModScormDataModel12 { | |||||||
| 
 | 
 | ||||||
|         for (const element in this.currentUserData[this.scoId].userdata) { |         for (const element in this.currentUserData[this.scoId].userdata) { | ||||||
|             // Ommit for example the nav. elements and the session time element.
 |             // Ommit for example the nav. elements and the session time element.
 | ||||||
|             if (element.substr(0, 3) != 'cmi' || element == 'cmi.core.session_time') { |             if (element.substring(0, 3) != 'cmi' || element == 'cmi.core.session_time') { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -748,16 +748,16 @@ export class AddonModScormDataModel12 { | |||||||
|                     const childrenStr = '._children'; |                     const childrenStr = '._children'; | ||||||
|                     const countStr = '._count'; |                     const countStr = '._count'; | ||||||
| 
 | 
 | ||||||
|                     if (elementModel.substr(elementModel.length - childrenStr.length, elementModel.length) == childrenStr) { |                     if (elementModel.substring(elementModel.length - childrenStr.length, elementModel.length) == childrenStr) { | ||||||
|                         const parentModel = elementModel.substr(0, elementModel.length - childrenStr.length); |                         const parentModel = elementModel.substring(0, elementModel.length - childrenStr.length); | ||||||
| 
 | 
 | ||||||
|                         if (this.dataModel[this.scoId][parentModel] !== undefined) { |                         if (this.dataModel[this.scoId][parentModel] !== undefined) { | ||||||
|                             this.errorCode = '202'; |                             this.errorCode = '202'; | ||||||
|                         } else { |                         } else { | ||||||
|                             this.errorCode = '201'; |                             this.errorCode = '201'; | ||||||
|                         } |                         } | ||||||
|                     } else if (elementModel.substr(elementModel.length - countStr.length, elementModel.length) == countStr) { |                     } else if (elementModel.substring(elementModel.length - countStr.length, elementModel.length) == countStr) { | ||||||
|                         const parentModel = elementModel.substr(0, elementModel.length - countStr.length); |                         const parentModel = elementModel.substring(0, elementModel.length - countStr.length); | ||||||
| 
 | 
 | ||||||
|                         if (this.dataModel[this.scoId][parentModel] !== undefined) { |                         if (this.dataModel[this.scoId][parentModel] !== undefined) { | ||||||
|                             this.errorCode = '203'; |                             this.errorCode = '203'; | ||||||
|  | |||||||
| @ -29,8 +29,8 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-padding"> | <core-loading [hideUntil]="loaded" class="safe-area-padding"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="!errorMessage && hasOffline"> |         [courseId]="courseId" [hasDataToSync]="!errorMessage && hasOffline"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <!-- Warning message. --> |     <!-- Warning message. --> | ||||||
| @ -237,5 +237,5 @@ | |||||||
|     </ng-container> |     </ng-container> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -947,7 +947,7 @@ export class AddonModScormProvider { | |||||||
|         if (parameters) { |         if (parameters) { | ||||||
|             const connector = launchUrl.indexOf('?') > -1 ? '&' : '?'; |             const connector = launchUrl.indexOf('?') > -1 ? '&' : '?'; | ||||||
|             if (parameters.charAt(0) == '?') { |             if (parameters.charAt(0) == '?') { | ||||||
|                 parameters = parameters.substr(1); |                 parameters = parameters.substring(1); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             launchUrl += connector + parameters; |             launchUrl += connector + parameters; | ||||||
| @ -1315,7 +1315,7 @@ export class AddonModScormProvider { | |||||||
| 
 | 
 | ||||||
|         if (link.match(/^https?:\/\//i) && !CoreUrlUtils.isLocalFileUrl(link)) { |         if (link.match(/^https?:\/\//i) && !CoreUrlUtils.isLocalFileUrl(link)) { | ||||||
|             return true; |             return true; | ||||||
|         } else if (link.substr(0, 4) == 'www.') { |         } else if (link.substring(0, 4) == 'www.') { | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -30,9 +30,8 @@ | |||||||
| <core-loading [hideUntil]="loaded" class="safe-area-padding"> | <core-loading [hideUntil]="loaded" class="safe-area-padding"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" |     <core-course-module-info [module]="module" [description]="survey && !survey.surveydone && !hasOffline && description" | ||||||
|         [description]="survey && !survey.surveydone && !hasOffline && description" [component]="component" [componentId]="componentId" |         [component]="component" [componentId]="componentId" [courseId]="courseId" [hasDataToSync]="hasOffline"> | ||||||
|         [courseId]="courseId" [hasDataToSync]="hasOffline"> |  | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <!-- Survey already done --> |     <!-- Survey already done --> | ||||||
| @ -148,5 +147,5 @@ | |||||||
| 
 | 
 | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -16,8 +16,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="displayDescription && description" |     <core-course-module-info [module]="module" [description]="displayDescription && description" [component]="component" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId"> |         [componentId]="componentId" [courseId]="courseId"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <div *ngIf="shouldIframe || (shouldEmbed && isOther)" class="addon-mod_url-embedded-url"> |     <div *ngIf="shouldIframe || (shouldEmbed && isOther)" class="addon-mod_url-embedded-url"> | ||||||
| @ -53,5 +53,5 @@ | |||||||
|     </ion-list> |     </ion-list> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -68,7 +68,7 @@ export class AddonModUrlProvider { | |||||||
|         const download = ['application/zip', 'application/x-tar', 'application/g-zip', 'application/pdf', 'text/html']; |         const download = ['application/zip', 'application/x-tar', 'application/g-zip', 'application/pdf', 'text/html']; | ||||||
|         let mimetype = CoreMimetypeUtils.getMimeType(extension); |         let mimetype = CoreMimetypeUtils.getMimeType(extension); | ||||||
| 
 | 
 | ||||||
|         if (url.externalurl.indexOf('.php') != -1 || url.externalurl.substr(-1) === '/' || |         if (url.externalurl.indexOf('.php') != -1 || url.externalurl.substring(-1) === '/' || | ||||||
|                 (url.externalurl.indexOf('//') != -1 && url.externalurl.match(/\//g)?.length == 2)) { |                 (url.externalurl.indexOf('//') != -1 && url.externalurl.match(/\//g)?.length == 2)) { | ||||||
|             // Seems to be a web, use HTML mimetype.
 |             // Seems to be a web, use HTML mimetype.
 | ||||||
|             mimetype = 'text/html'; |             mimetype = 'text/html'; | ||||||
| @ -158,7 +158,7 @@ export class AddonModUrlProvider { | |||||||
|         const matches = url.match(/\//g); |         const matches = url.match(/\//g); | ||||||
|         const extension = CoreMimetypeUtils.getFileExtension(url); |         const extension = CoreMimetypeUtils.getFileExtension(url); | ||||||
| 
 | 
 | ||||||
|         if (!matches || matches.length < 3 || url.substr(-1) === '/' || extension == 'php') { |         if (!matches || matches.length < 3 || url.substring(-1) === '/' || extension == 'php') { | ||||||
|             // Use default icon.
 |             // Use default icon.
 | ||||||
|             return ''; |             return ''; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -48,9 +48,8 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [description]="description" |     <core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId" | ||||||
|         [component]="component" [componentId]="componentId" [courseId]="courseId"> |         [courseId]="courseId"> | ||||||
|         <h2 *ngIf="pageTitle" title>{{pageTitle}}</h2> |  | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <div *ngIf="pageIsOffline || hasOffline || pageWarning"> |     <div *ngIf="pageIsOffline || hasOffline || pageWarning"> | ||||||
| @ -74,6 +73,7 @@ | |||||||
|         </ion-card> |         </ion-card> | ||||||
|     </div> |     </div> | ||||||
|     <div class="ion-padding addon-mod_wiki-page-content"> |     <div class="ion-padding addon-mod_wiki-page-content"> | ||||||
|  |         <h2 *ngIf="pageTitle">{{pageTitle}}</h2> | ||||||
|         <article [ngClass]="{'addon-mod_wiki-noedit': !canEdit}"> |         <article [ngClass]="{'addon-mod_wiki-noedit': !canEdit}"> | ||||||
|             <core-format-text *ngIf="pageContent" [component]="component" [componentId]="componentId" [text]="pageContent" |             <core-format-text *ngIf="pageContent" [component]="component" [componentId]="componentId" [text]="pageContent" | ||||||
|                 contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> |                 contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||||
| @ -89,7 +89,7 @@ | |||||||
|     </div> |     </div> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
| 
 | 
 | ||||||
| <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canEdit"> | <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canEdit"> | ||||||
|  | |||||||
| @ -427,7 +427,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp | |||||||
|         const pageContents = await this.fetchPageContents(this.currentPage); |         const pageContents = await this.fetchPageContents(this.currentPage); | ||||||
| 
 | 
 | ||||||
|         if (pageContents) { |         if (pageContents) { | ||||||
|             this.dataRetrieved.emit(pageContents.title); |  | ||||||
|             this.setSelectedWiki(pageContents.subwikiid, pageContents.userid, pageContents.groupid); |             this.setSelectedWiki(pageContents.subwikiid, pageContents.userid, pageContents.groupid); | ||||||
| 
 | 
 | ||||||
|             this.pageTitle = pageContents.title; |             this.pageTitle = pageContents.title; | ||||||
|  | |||||||
| @ -8,10 +8,6 @@ | |||||||
|                 <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> |                 <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||||
|                 </core-format-text> |                 </core-format-text> | ||||||
|             </h1> |             </h1> | ||||||
|             <h2> |  | ||||||
|                 <core-format-text [text]="pageTitle" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> |  | ||||||
|                 </core-format-text> |  | ||||||
|             </h2> |  | ||||||
|         </ion-title> |         </ion-title> | ||||||
| 
 | 
 | ||||||
|         <ion-buttons slot="end"> |         <ion-buttons slot="end"> | ||||||
|  | |||||||
| @ -49,18 +49,6 @@ export class AddonModWikiIndexPage extends CoreCourseModuleMainActivityPage<Addo | |||||||
|         this.groupId = CoreNavigator.getRouteNumberParam('groupId'); |         this.groupId = CoreNavigator.getRouteNumberParam('groupId'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @inheritdoc |  | ||||||
|      */ |  | ||||||
|     updateData(data: { name: string } | string): void { |  | ||||||
|         if (typeof data == 'string') { |  | ||||||
|             // We received the title to display.
 |  | ||||||
|             this.pageTitle = data; |  | ||||||
|         } else { |  | ||||||
|             super.updateData(data); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * User entered the page. |      * User entered the page. | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ | |||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
| 
 | 
 | ||||||
|     <!-- Activity info. --> |     <!-- Activity info. --> | ||||||
|     <core-course-module-info [module]="module" (completionChanged)="onCompletionChange()" [hasDataToSync]="hasOffline"> |     <core-course-module-info [module]="module" [hasDataToSync]="hasOffline"> | ||||||
|     </core-course-module-info> |     </core-course-module-info> | ||||||
| 
 | 
 | ||||||
|     <ion-card *ngIf="phases"> |     <ion-card *ngIf="phases"> | ||||||
| @ -254,5 +254,5 @@ | |||||||
|     </div> |     </div> | ||||||
| </core-loading> | </core-loading> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"> | <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" (completionChanged)="onCompletionChange()"> | ||||||
| </core-course-module-navigation> | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -226,7 +226,7 @@ export class AddonQtypeCalculatedHandlerService implements CoreQuestionHandler { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const numberString = match[0]; |         const numberString = match[0]; | ||||||
|         const unit = unitsLeft ? answer.substr(0, answer.length - match[0].length) : answer.substr(match[0].length); |         const unit = unitsLeft ? answer.substring(0, answer.length - match[0].length) : answer.substring(match[0].length); | ||||||
| 
 | 
 | ||||||
|         // No need to calculate the multiplier.
 |         // No need to calculate the multiplier.
 | ||||||
|         return { answer: Number(numberString), unit }; |         return { answer: Number(numberString), unit }; | ||||||
|  | |||||||
| @ -58,7 +58,7 @@ export class CoreInterceptor implements HttpInterceptor { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return query.length ? query.substr(0, query.length - 1) : query; |         return query.length ? query.substring(0, query.length - 1) : query; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||||
|  | |||||||
| @ -600,7 +600,7 @@ export class SQLiteDB { | |||||||
|             sql = equal ? '= ?' : '<> ?'; |             sql = equal ? '= ?' : '<> ?'; | ||||||
|             params = Array.isArray(items) ? items : [items]; |             params = Array.isArray(items) ? items : [items]; | ||||||
|         } else { |         } else { | ||||||
|             sql = (equal ? '' : 'NOT ') + 'IN (' + ',?'.repeat(items.length).substr(1) + ')'; |             sql = (equal ? '' : 'NOT ') + 'IN (' + ',?'.repeat(items.length).substring(1) + ')'; | ||||||
|             params = items; |             params = items; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -801,7 +801,7 @@ export class SQLiteDB { | |||||||
| 
 | 
 | ||||||
|         const keys = Object.keys(data); |         const keys = Object.keys(data); | ||||||
|         const fields = keys.join(','); |         const fields = keys.join(','); | ||||||
|         const questionMarks = ',?'.repeat(keys.length).substr(1); |         const questionMarks = ',?'.repeat(keys.length).substring(1); | ||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|             sql: `INSERT OR REPLACE INTO ${table} (${fields}) VALUES (${questionMarks})`, |             sql: `INSERT OR REPLACE INTO ${table} (${fields}) VALUES (${questionMarks})`, | ||||||
| @ -1139,7 +1139,7 @@ export class SQLiteDB { | |||||||
|             if (params.length == 1) { |             if (params.length == 1) { | ||||||
|                 sql = sql + field + ' = ?'; |                 sql = sql + field + ' = ?'; | ||||||
|             } else { |             } else { | ||||||
|                 const questionMarks = ',?'.repeat(params.length).substr(1); |                 const questionMarks = ',?'.repeat(params.length).substring(1); | ||||||
|                 sql = sql + field + ' IN (' + questionMarks + ')'; |                 sql = sql + field + ' IN (' + questionMarks + ')'; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,23 +1,9 @@ | |||||||
| <ion-button | <ion-button fill="clear" color="dark" (click)="scroll('backward')" [hidden]="scrollPosition === 'hidden'" | ||||||
|     fill="clear" |     [disabled]="scrollPosition === 'start'" [attr.aria-label]="'core.scrollbackward' | translate" [attr.aria-controls]="targetId"> | ||||||
|     color="dark" |     <ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon> | ||||||
|     (click)="scroll('backward')" |  | ||||||
|     [hidden]="scrollPosition === 'hidden'" |  | ||||||
|     [disabled]="scrollPosition === 'start'" |  | ||||||
|     [attr.aria-label]="'core.scrollbackward' | translate" |  | ||||||
|     [attr.aria-controls]="targetId" |  | ||||||
| > |  | ||||||
|     <ion-icon name="fas-caret-left" slot="icon-only" aria-hidden="true"></ion-icon> |  | ||||||
| </ion-button> | </ion-button> | ||||||
| 
 | 
 | ||||||
| <ion-button | <ion-button fill="clear" color="dark" (click)="scroll('forward')" [hidden]="scrollPosition === 'hidden'" | ||||||
|     fill="clear" |     [disabled]="scrollPosition === 'end'" [attr.aria-label]="'core.scrollforward' | translate" [attr.aria-controls]="targetId"> | ||||||
|     color="dark" |     <ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon> | ||||||
|     (click)="scroll('forward')" |  | ||||||
|     [hidden]="scrollPosition === 'hidden'" |  | ||||||
|     [disabled]="scrollPosition === 'end'" |  | ||||||
|     [attr.aria-label]="'core.scrollforward' | translate" |  | ||||||
|     [attr.aria-controls]="targetId" |  | ||||||
| > |  | ||||||
|     <ion-icon name="fas-caret-right" slot="icon-only" aria-hidden="true"></ion-icon> |  | ||||||
| </ion-button> | </ion-button> | ||||||
|  | |||||||
| @ -52,16 +52,16 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges { | |||||||
|             switch (parts[0]) { |             switch (parts[0]) { | ||||||
|                 case 'far': |                 case 'far': | ||||||
|                     library = 'regular'; |                     library = 'regular'; | ||||||
|                     iconName = iconName.substr(4); |                     iconName = iconName.substring(4); | ||||||
|                     break; |                     break; | ||||||
|                 case 'fa': |                 case 'fa': | ||||||
|                 case 'fas': |                 case 'fas': | ||||||
|                     library = 'solid'; |                     library = 'solid'; | ||||||
|                     iconName = iconName.substr(parts[0].length + 1); |                     iconName = iconName.substring(parts[0].length + 1); | ||||||
|                     break; |                     break; | ||||||
|                 case 'fab': |                 case 'fab': | ||||||
|                     library = 'brands'; |                     library = 'brands'; | ||||||
|                     iconName = iconName.substr(4); |                     iconName = iconName.substring(4); | ||||||
|                     break; |                     break; | ||||||
|                 default: |                 default: | ||||||
|                     break; |                     break; | ||||||
|  | |||||||
| @ -140,7 +140,7 @@ export class CoreLinkDirective implements OnInit { | |||||||
| 
 | 
 | ||||||
|         if (href.charAt(0) == '#') { |         if (href.charAt(0) == '#') { | ||||||
|             // Look for id or name.
 |             // Look for id or name.
 | ||||||
|             href = href.substr(1); |             href = href.substring(1); | ||||||
|             CoreDomUtils.scrollToElementBySelector( |             CoreDomUtils.scrollToElementBySelector( | ||||||
|                 this.element.closest('ion-content'), |                 this.element.closest('ion-content'), | ||||||
|                 this.content, |                 this.content, | ||||||
| @ -170,7 +170,7 @@ export class CoreLinkDirective implements OnInit { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected async openLocalFile(path: string): Promise<void> { |     protected async openLocalFile(path: string): Promise<void> { | ||||||
|         const filename = path.substr(path.lastIndexOf('/') + 1); |         const filename = path.substring(path.lastIndexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|         if (!CoreFileHelper.isOpenableInApp({ filename })) { |         if (!CoreFileHelper.isOpenableInApp({ filename })) { | ||||||
|             try { |             try { | ||||||
|  | |||||||
| @ -95,7 +95,7 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { | |||||||
|         if (this.pattern) { |         if (this.pattern) { | ||||||
|             const position = url.search(this.pattern); |             const position = url.search(this.pattern); | ||||||
|             if (position > -1) { |             if (position > -1) { | ||||||
|                 return url.substr(0, position); |                 return url.substring(0, position); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
|     <core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance"> |     <core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance"> | ||||||
|     </core-mod-icon> |     </core-mod-icon> | ||||||
|     <ion-label> |     <ion-label> | ||||||
|  |         <p class="core-modulename" *ngIf="moduleNameTranslated">{{moduleNameTranslated}}</p> | ||||||
|         <h1> |         <h1> | ||||||
|             <core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId" |             <core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId" | ||||||
|                 [contextInstanceId]="module.id" [courseId]="courseId"> |                 [contextInstanceId]="module.id" [courseId]="courseId"> | ||||||
| @ -18,21 +19,12 @@ | |||||||
|     </ion-label> |     </ion-label> | ||||||
| </ion-item> | </ion-item> | ||||||
| <ng-content select="[description]"></ng-content> | <ng-content select="[description]"></ng-content> | ||||||
| <ion-item class="ion-text-wrap" lines="none" *ngIf="showCompletion && (module.dates?.length || | <!-- Activity dates. --> | ||||||
|         (module.completiondata && (module.completiondata.isautomatic || showManualCompletion) && module.uservisible))"> | <ion-item class="ion-text-wrap core-module-dates" lines="none" *ngIf="showCompletion && module.dates?.length"> | ||||||
|     <ion-label> |     <ion-label> | ||||||
|         <!-- Activity dates. --> |         <p *ngFor="let date of module.dates"> | ||||||
|         <div *ngIf="module.dates?.length" class="core-module-dates"> |             <strong>{{ date.label }}</strong> {{ date.timestamp * 1000 | coreFormatDate:'strftimedatetime' }} | ||||||
|             <p *ngFor="let date of module.dates"> |         </p> | ||||||
|                 <strong>{{ date.label }}</strong> {{ date.timestamp * 1000 | coreFormatDate:'strftimedatetime' }} |  | ||||||
|             </p> |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         <!-- Module completion. --> |  | ||||||
|         <core-course-module-completion *ngIf="module.completiondata && module.uservisible" [completion]="module.completiondata" |  | ||||||
|             [moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="true" |  | ||||||
|             [showManualCompletion]="showManualCompletion" (completionChanged)="completionChanged.emit($event)"> |  | ||||||
|         </core-course-module-completion> |  | ||||||
|     </ion-label> |     </ion-label> | ||||||
| </ion-item> | </ion-item> | ||||||
| <ng-content></ng-content> | <ng-content></ng-content> | ||||||
|  | |||||||
| @ -6,6 +6,14 @@ | |||||||
|     margin-bottom: 8px; |     margin-bottom: 8px; | ||||||
|     padding-bottom: 1px; // To allow margins inside. |     padding-bottom: 1px; // To allow margins inside. | ||||||
|     background-color: var(--contrast-background); |     background-color: var(--contrast-background); | ||||||
|      | 
 | ||||||
|     @include padding-horizontal(var(--ion-safe-area-left), var(--ion-safe-area-right)); |     @include padding-horizontal(var(--ion-safe-area-left), var(--ion-safe-area-right)); | ||||||
|  | 
 | ||||||
|  |     .core-modulename { | ||||||
|  |         text-transform: uppercase; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     core-mod-icon { | ||||||
|  |         align-self: flex-start; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,9 +12,9 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | import { Component, Input, OnInit } from '@angular/core'; | ||||||
| import { CoreCourse } from '@features/course/services/course'; | import { CoreCourse } from '@features/course/services/course'; | ||||||
| import { CoreCourseModuleData, CoreCourseModuleCompletionData } from '@features/course/services/course-helper'; | import { CoreCourseModuleData } from '@features/course/services/course-helper'; | ||||||
| import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| 
 | 
 | ||||||
| @ -36,7 +36,6 @@ import { CoreSites } from '@services/sites'; | |||||||
| export class CoreCourseModuleInfoComponent implements OnInit { | export class CoreCourseModuleInfoComponent implements OnInit { | ||||||
| 
 | 
 | ||||||
|     @Input() module!: CoreCourseModuleData; // The module to render.
 |     @Input() module!: CoreCourseModuleData; // The module to render.
 | ||||||
|     @Input() showManualCompletion = true; // Whether to show manual completion, true by default.
 |  | ||||||
|     @Input() courseId!: number; // The courseId the module belongs to.
 |     @Input() courseId!: number; // The courseId the module belongs to.
 | ||||||
| 
 | 
 | ||||||
|     @Input() component!: string; // Component for format text directive.
 |     @Input() component!: string; // Component for format text directive.
 | ||||||
| @ -47,8 +46,6 @@ export class CoreCourseModuleInfoComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|     @Input() hasDataToSync = false; // If the activity has any data to be synced.
 |     @Input() hasDataToSync = false; // If the activity has any data to be synced.
 | ||||||
| 
 | 
 | ||||||
|     @Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
 |  | ||||||
| 
 |  | ||||||
|     modicon = ''; |     modicon = ''; | ||||||
|     showCompletion = false; // Whether to show completion.
 |     showCompletion = false; // Whether to show completion.
 | ||||||
|     moduleNameTranslated = ''; |     moduleNameTranslated = ''; | ||||||
| @ -60,7 +57,6 @@ export class CoreCourseModuleInfoComponent implements OnInit { | |||||||
|         this.modicon = await CoreCourseModuleDelegate.getModuleIconSrc(this.module.modname, this.module.modicon, this.module); |         this.modicon = await CoreCourseModuleDelegate.getModuleIconSrc(this.module.modname, this.module.modicon, this.module); | ||||||
| 
 | 
 | ||||||
|         this.moduleNameTranslated = CoreCourse.translateModuleName(this.module.modname || ''); |         this.moduleNameTranslated = CoreCourse.translateModuleName(this.module.modname || ''); | ||||||
| 
 |  | ||||||
|         this.showCompletion = CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('3.11'); |         this.showCompletion = CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('3.11'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,6 +6,14 @@ | |||||||
|                 <ion-icon name="fas-arrow-left" slot="icon-only" aria-hidden="true"></ion-icon> |                 <ion-icon name="fas-arrow-left" slot="icon-only" aria-hidden="true"></ion-icon> | ||||||
|             </ion-button> |             </ion-button> | ||||||
|         </ion-col> |         </ion-col> | ||||||
|  |         <ion-col *ngIf="showCompletion && (currentModule.completiondata && | ||||||
|  |             (currentModule.completiondata.isautomatic || showManualCompletion) && currentModule.uservisible)"> | ||||||
|  |             <!-- Module completion. --> | ||||||
|  |             <core-course-module-completion [completion]="currentModule.completiondata" [moduleName]="currentModule.name" | ||||||
|  |                 [moduleId]="currentModule.id" [showCompletionConditions]="true" [showManualCompletion]="showManualCompletion" | ||||||
|  |                 (completionChanged)="completionChanged.emit($event)"> | ||||||
|  |             </core-course-module-completion> | ||||||
|  |         </ion-col> | ||||||
|         <ion-col size="auto"> |         <ion-col size="auto"> | ||||||
|             <ion-button fill="clear" class="core-course-next-module" *ngIf="nextModule" (click)="goToActivity(true)" |             <ion-button fill="clear" class="core-course-next-module" *ngIf="nextModule" (click)="goToActivity(true)" | ||||||
|                 [attr.aria-label]="'core.course.gotonextactivity' | translate"> |                 [attr.aria-label]="'core.course.gotonextactivity' | translate"> | ||||||
|  | |||||||
| @ -12,9 +12,9 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Component, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; | import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; | ||||||
| import { CoreCourse, CoreCourseProvider, CoreCourseWSSection } from '@features/course/services/course'; | import { CoreCourse, CoreCourseProvider, CoreCourseWSSection } from '@features/course/services/course'; | ||||||
| import { CoreCourseModuleData } from '@features/course/services/course-helper'; | import { CoreCourseModuleCompletionData, CoreCourseModuleData } from '@features/course/services/course-helper'; | ||||||
| import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||||
| import { IonContent } from '@ionic/angular'; | import { IonContent } from '@ionic/angular'; | ||||||
| import { ScrollDetail } from '@ionic/core'; | import { ScrollDetail } from '@ionic/core'; | ||||||
| @ -29,7 +29,7 @@ import { CoreMath } from '@singletons/math'; | |||||||
|  * Component to show a button to go to the next resource/activity. |  * Component to show a button to go to the next resource/activity. | ||||||
|  * |  * | ||||||
|  * Example usage: |  * Example usage: | ||||||
|  * <core-course-module-navigation [courseId]="courseId" [currentModuleId]="module.id"></core-course-module-navigation> |  * <core-course-module-navigation [courseId]="courseId" [currentModule]="module"></core-course-module-navigation> | ||||||
|  */ |  */ | ||||||
| @Component({ | @Component({ | ||||||
|     selector: 'core-course-module-navigation', |     selector: 'core-course-module-navigation', | ||||||
| @ -39,13 +39,17 @@ import { CoreMath } from '@singletons/math'; | |||||||
| export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { | export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { | ||||||
| 
 | 
 | ||||||
|     @Input() courseId!: number; // Course ID.
 |     @Input() courseId!: number; // Course ID.
 | ||||||
|     @Input() currentModuleId!: number; // Current module ID.
 |     @Input() currentModule!: CoreCourseModuleData; // Current module.
 | ||||||
|  |     @Input() showManualCompletion = true; // Whether to show manual completion, true by default.
 | ||||||
|  | 
 | ||||||
|  |     @Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
 | ||||||
| 
 | 
 | ||||||
|     nextModule?: CoreCourseModuleData; |     nextModule?: CoreCourseModuleData; | ||||||
|     previousModule?: CoreCourseModuleData; |     previousModule?: CoreCourseModuleData; | ||||||
|     nextModuleSection?: CoreCourseWSSection; |     nextModuleSection?: CoreCourseWSSection; | ||||||
|     previousModuleSection?: CoreCourseWSSection; |     previousModuleSection?: CoreCourseWSSection; | ||||||
|     loaded = false; |     loaded = false; | ||||||
|  |     showCompletion = false; // Whether to show completion.
 | ||||||
| 
 | 
 | ||||||
|     protected element: HTMLElement; |     protected element: HTMLElement; | ||||||
|     protected initialHeight = 0; |     protected initialHeight = 0; | ||||||
| @ -78,6 +82,8 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { | |||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     async ngOnInit(): Promise<void> { |     async ngOnInit(): Promise<void> { | ||||||
|  |         this.showCompletion = CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('3.11'); | ||||||
|  | 
 | ||||||
|         try { |         try { | ||||||
|             await this.setNextAndPreviousModules(CoreSitesReadingStrategy.PREFER_CACHE); |             await this.setNextAndPreviousModules(CoreSitesReadingStrategy.PREFER_CACHE); | ||||||
|         } finally { |         } finally { | ||||||
| @ -172,6 +178,7 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const preSets = CoreSites.getReadingStrategyPreSets(readingStrategy); |         const preSets = CoreSites.getReadingStrategyPreSets(readingStrategy); | ||||||
|  |         const currentModuleId = this.currentModule.id; | ||||||
| 
 | 
 | ||||||
|         const sections = await CoreCourse.getSections(this.courseId, false, true, preSets); |         const sections = await CoreCourse.getSections(this.courseId, false, true, preSets); | ||||||
| 
 | 
 | ||||||
| @ -184,7 +191,7 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { | |||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             currentModuleIndex = section.modules.findIndex((module: CoreCourseModuleData) => module.id == this.currentModuleId); |             currentModuleIndex = section.modules.findIndex((module: CoreCourseModuleData) => module.id == currentModuleId); | ||||||
| 
 | 
 | ||||||
|             return currentModuleIndex >= 0; |             return currentModuleIndex >= 0; | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -25,8 +25,7 @@ | |||||||
|     </ion-refresher> |     </ion-refresher> | ||||||
|     <core-loading [hideUntil]="loaded"> |     <core-loading [hideUntil]="loaded"> | ||||||
|         <core-course-module-info [module]="module" [courseId]="courseId" [description]="module.description" [component]="module.modname" |         <core-course-module-info [module]="module" [courseId]="courseId" [description]="module.description" [component]="module.modname" | ||||||
|             [componentId]="module.id" (completionChanged)="onCompletionChange()" [expandDescription]="true" |             [componentId]="module.id" [expandDescription]="true"> | ||||||
|             [showManualCompletion]="showManualCompletion"> |  | ||||||
| 
 | 
 | ||||||
|             <div class="safe-area-padding-horizontal ion-padding" *ngIf="module.handlerData?.extraBadge"> |             <div class="safe-area-padding-horizontal ion-padding" *ngIf="module.handlerData?.extraBadge"> | ||||||
|                 <ion-badge class="ion-text-wrap ion-text-start" [color]="module.handlerData?.extraBadgeColor"> |                 <ion-badge class="ion-text-wrap ion-text-start" [color]="module.handlerData?.extraBadgeColor"> | ||||||
| @ -61,5 +60,6 @@ | |||||||
|         </core-course-module-info> |         </core-course-module-info> | ||||||
|     </core-loading> |     </core-loading> | ||||||
| 
 | 
 | ||||||
|     <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"></core-course-module-navigation> |     <core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module" | ||||||
|  |         (completionChanged)="onCompletionChange()" [showManualCompletion]="showManualCompletion"></core-course-module-navigation> | ||||||
| </ion-content> | </ion-content> | ||||||
|  | |||||||
| @ -1,3 +1,10 @@ | |||||||
| :host ::ng-deep ion-item-divider { | :host ::ng-deep ion-item-divider { | ||||||
|     display: none !important; |     display: none !important; | ||||||
| } | } | ||||||
|  | :host ::ng-deep core-loading { | ||||||
|  |     --internal-loading-inline-min-height: calc(100vh - var(--core-header-toolbar-height)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | :host-context(ion-tabs.placement-bottom) ::ng-deep core-loading { | ||||||
|  |     --internal-loading-inline-min-height: calc(100vh - var(--core-header-toolbar-height) - var(--bottom-tabs-size) - 2px); | ||||||
|  | } | ||||||
|  | |||||||
| @ -198,7 +198,7 @@ export class FileTransferObjectMock extends FileTransferObject { | |||||||
|                 const headerString = headers[i]; |                 const headerString = headers[i]; | ||||||
|                 const separatorPos = headerString.indexOf(':'); |                 const separatorPos = headerString.indexOf(':'); | ||||||
|                 if (separatorPos != -1) { |                 if (separatorPos != -1) { | ||||||
|                     result[headerString.substr(0, separatorPos)] = headerString.substr(separatorPos + 1).trim(); |                     result[headerString.substring(0, separatorPos)] = headerString.substring(separatorPos + 1).trim(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -444,7 +444,7 @@ export class FileMock extends File { | |||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|             path: fullPath.substring(0, fullPath.lastIndexOf('/')), |             path: fullPath.substring(0, fullPath.lastIndexOf('/')), | ||||||
|             name: fullPath.substr(fullPath.lastIndexOf('/') + 1), |             name: fullPath.substring(fullPath.lastIndexOf('/') + 1), | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -65,7 +65,7 @@ export class ZipMock extends Zip { | |||||||
|         destination = destination.replace(/%20/g, ' '); |         destination = destination.replace(/%20/g, ' '); | ||||||
| 
 | 
 | ||||||
|         const sourceDir = source.substring(0, source.lastIndexOf('/')); |         const sourceDir = source.substring(0, source.lastIndexOf('/')); | ||||||
|         const sourceName = source.substr(source.lastIndexOf('/') + 1); |         const sourceName = source.substring(source.lastIndexOf('/') + 1); | ||||||
|         const zip = new JSZip(); |         const zip = new JSZip(); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
| @ -82,7 +82,7 @@ export class ZipMock extends Zip { | |||||||
| 
 | 
 | ||||||
|             // First of all, create the directory where the files will be unzipped.
 |             // First of all, create the directory where the files will be unzipped.
 | ||||||
|             const destParent = destination.substring(0, destination.lastIndexOf('/')); |             const destParent = destination.substring(0, destination.lastIndexOf('/')); | ||||||
|             const destFolderName = destination.substr(destination.lastIndexOf('/') + 1); |             const destFolderName = destination.substring(destination.lastIndexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|             await this.file.createDir(destParent, destFolderName, true); |             await this.file.createDir(destParent, destFolderName, true); | ||||||
| 
 | 
 | ||||||
| @ -95,7 +95,7 @@ export class ZipMock extends Zip { | |||||||
|                 if (!file.dir) { |                 if (!file.dir) { | ||||||
|                     // It's a file.
 |                     // It's a file.
 | ||||||
|                     const fileDir = name.substring(0, name.lastIndexOf('/')); |                     const fileDir = name.substring(0, name.lastIndexOf('/')); | ||||||
|                     const fileName = name.substr(name.lastIndexOf('/') + 1); |                     const fileName = name.substring(name.lastIndexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|                     if (fileDir) { |                     if (fileDir) { | ||||||
|                         // The file is in a subfolder, create it first.
 |                         // The file is in a subfolder, create it first.
 | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ | |||||||
|                                 </td> |                                 </td> | ||||||
|                                 <th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan"> |                                 <th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan"> | ||||||
|                                     <ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start" |                                     <ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start" | ||||||
|                                         [name]="row.expanded ? 'fas-caret-down' : 'fas-caret-right'"> |                                         [name]="row.expanded ? 'fas-chevron-down' : 'fas-chevron-right'" class="expandable-status-icon"> | ||||||
|                                     </ion-icon> |                                     </ion-icon> | ||||||
|                                     <ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt"> |                                     <ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt"> | ||||||
|                                     </ion-icon> |                                     </ion-icon> | ||||||
|  | |||||||
| @ -100,6 +100,11 @@ | |||||||
|             @include margin(null, null, null, 5px); |             @include margin(null, null, null, 5px); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         .expandable-status-icon { | ||||||
|  |             font-size: 14px; | ||||||
|  |             @include margin-horizontal(0, 2px); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .core-grades-table-feedback { |     .core-grades-table-feedback { | ||||||
|  | |||||||
| @ -154,7 +154,7 @@ export class CoreGradesHelperProvider { | |||||||
|         for (const name in item) { |         for (const name in item) { | ||||||
|             const index = name.indexOf('formatted'); |             const index = name.indexOf('formatted'); | ||||||
|             if (index > 0) { |             if (index > 0) { | ||||||
|                 item[name.substr(0, index)] = item[name]; |                 item[name.substring(0, index)] = item[name]; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -399,7 +399,7 @@ export class CoreGradesHelperProvider { | |||||||
|                 (row) => |                 (row) => | ||||||
|                     row.itemname && |                     row.itemname && | ||||||
|                     row.itemname.id && |                     row.itemname.id && | ||||||
|                     row.itemname.id.substr(0, 3) == 'row' && |                     row.itemname.id.substring(0, 3) == 'row' && | ||||||
|                     parseInt(row.itemname.id.split('_')[1], 10) == gradeId, |                     parseInt(row.itemname.id.split('_')[1], 10) == gradeId, | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -169,7 +169,7 @@ export class CoreH5PContentValidator { | |||||||
| 
 | 
 | ||||||
|         // Check if string is within allowed length.
 |         // Check if string is within allowed length.
 | ||||||
|         if (semantics.maxLength !== undefined) { |         if (semantics.maxLength !== undefined) { | ||||||
|             text = text.substr(0, semantics.maxLength); |             text = text.substring(0, semantics.maxLength); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return text; |         return text; | ||||||
| @ -357,8 +357,8 @@ export class CoreH5PContentValidator { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Remove temporary files suffix.
 |         // Remove temporary files suffix.
 | ||||||
|         if (file.path.substr(-4, 4) === '#tmp') { |         if (file.path.substring(-4, 4) === '#tmp') { | ||||||
|             file.path = file.path.substr(0, file.path.length - 4); |             file.path = file.path.substring(0, file.path.length - 4); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Make sure path and mime does not have any special chars
 |         // Make sure path and mime does not have any special chars
 | ||||||
| @ -670,7 +670,7 @@ export class CoreH5PContentValidator { | |||||||
| 
 | 
 | ||||||
|         const tag = tags[0]; |         const tag = tags[0]; | ||||||
| 
 | 
 | ||||||
|         if (tag.substr(0, 1) != '<') { |         if (tag.substring(0, 1) != '<') { | ||||||
|             // We matched a lone ">" character.
 |             // We matched a lone ">" character.
 | ||||||
|             return '>'; |             return '>'; | ||||||
|         } else if (tag.length == 1) { |         } else if (tag.length == 1) { | ||||||
| @ -746,7 +746,7 @@ export class CoreH5PContentValidator { | |||||||
|                     matches = attr.match(/^([-a-zA-Z]+)/); |                     matches = attr.match(/^([-a-zA-Z]+)/); | ||||||
|                     if (matches && matches.length > 1) { |                     if (matches && matches.length > 1) { | ||||||
|                         attrName = matches[1].toLowerCase(); |                         attrName = matches[1].toLowerCase(); | ||||||
|                         skip = attrName == 'style' || attrName.substr(0, 2) == 'on' || attrName.substr(0, 1) == '-' || |                         skip = attrName == 'style' || attrName.substring(0, 2) == 'on' || attrName.substring(0, 1) == '-' || | ||||||
|                                 attrName.length > 96; // Ignore long attributes to avoid unnecessary processing overhead.
 |                                 attrName.length > 96; // Ignore long attributes to avoid unnecessary processing overhead.
 | ||||||
|                         working = mode = 1; |                         working = mode = 1; | ||||||
|                         attr = attr.replace(/^[-a-zA-Z]+/, ''); |                         attr = attr.replace(/^[-a-zA-Z]+/, ''); | ||||||
| @ -883,7 +883,7 @@ export class CoreH5PContentValidator { | |||||||
| 
 | 
 | ||||||
|             if (colonPos > 0) { |             if (colonPos > 0) { | ||||||
|                 // We found a colon, possibly a protocol. Verify.
 |                 // We found a colon, possibly a protocol. Verify.
 | ||||||
|                 const protocol = uri.substr(0, colonPos); |                 const protocol = uri.substring(0, colonPos); | ||||||
|                 // If a colon is preceded by a slash, question mark or hash, it cannot possibly be part of the URL scheme.
 |                 // If a colon is preceded by a slash, question mark or hash, it cannot possibly be part of the URL scheme.
 | ||||||
|                 // This must be a relative URL, which inherits the (safe) protocol of the base document.
 |                 // This must be a relative URL, which inherits the (safe) protocol of the base document.
 | ||||||
|                 if (protocol.match(/[/?#]/)) { |                 if (protocol.match(/[/?#]/)) { | ||||||
| @ -891,7 +891,7 @@ export class CoreH5PContentValidator { | |||||||
|                 } |                 } | ||||||
|                 // Check if this is a disallowed protocol.
 |                 // Check if this is a disallowed protocol.
 | ||||||
|                 if (!allowedProtocols[protocol.toLowerCase()]) { |                 if (!allowedProtocols[protocol.toLowerCase()]) { | ||||||
|                     uri = uri.substr(colonPos + 1); |                     uri = uri.substring(colonPos + 1); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } while (before != uri); |         } while (before != uri); | ||||||
|  | |||||||
| @ -216,7 +216,7 @@ export class CoreH5PCore { | |||||||
| 
 | 
 | ||||||
|         // Prevent too long slug.
 |         // Prevent too long slug.
 | ||||||
|         if (newInput.length > 91) { |         if (newInput.length > 91) { | ||||||
|             newInput = newInput.substr(0, 92); |             newInput = newInput.substring(0, 92); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Prevent empty slug
 |         // Prevent empty slug
 | ||||||
|  | |||||||
| @ -14,23 +14,21 @@ | |||||||
| </ion-header> | </ion-header> | ||||||
| <ion-content> | <ion-content> | ||||||
|     <ion-list> |     <ion-list> | ||||||
|         <ion-item button class="core-user-profile-maininfo" *ngIf="siteInfo" lines="full" (click)="switchAccounts($event)" detail="true" |         <ion-item button class="core-usermenu-siteinfo ion-text-wrap" *ngIf="siteInfo" lines="full" detail="false" [href]="siteUrl" | ||||||
|             [attr.aria-label]="'core.mainmenu.switchaccount' | translate"> |             core-link auto-login="yes"> | ||||||
|             <core-user-avatar [user]="siteInfo" [userId]="siteInfo.userid" [linkProfile]="false" slot="start"></core-user-avatar> |  | ||||||
|             <ion-label> |             <ion-label> | ||||||
|                 <h2>{{ siteInfo.fullname }}</h2> |                 <p class="core-usermenu-sitename"> | ||||||
|                 <p class="core-usermenu-siteinfo core-usermenu-sitename"> |  | ||||||
|                     <core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true"> |                     <core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true"> | ||||||
|                     </core-format-text> |                     </core-format-text> | ||||||
|                 </p> |                 </p> | ||||||
|  |                 <a [href]="siteUrl" core-link auto-login="yes" class="core-usermenu-siteurl">{{ siteUrl }}</a> | ||||||
|             </ion-label> |             </ion-label> | ||||||
|         </ion-item> |         </ion-item> | ||||||
| 
 |         <ion-item button class="core-usermenu-handler ion-text-wrap" *ngIf="siteInfo" lines="full" (click)="openUserProfile($event)" | ||||||
|         <ion-item button class="ion-text-wrap core-usermenu-handler" (click)="openUserProfile($event)" |             detail="true" [attr.aria-label]="'core.user.profile' | translate"> | ||||||
|             [attr.aria-label]="'core.user.details' | translate" detail="true" lines="none"> |             <core-user-avatar [user]="siteInfo" [userId]="siteInfo.userid" [linkProfile]="false" slot="start"></core-user-avatar> | ||||||
|             <ion-icon name="fas-user" slot="start" aria-hidden="true"></ion-icon> |  | ||||||
|             <ion-label> |             <ion-label> | ||||||
|                 <p class="item-heading">{{ 'core.user.profile' | translate }}</p> |                 <h2>{{ siteInfo.fullname }}</h2> | ||||||
|             </ion-label> |             </ion-label> | ||||||
|         </ion-item> |         </ion-item> | ||||||
| 
 | 
 | ||||||
| @ -66,10 +64,18 @@ | |||||||
|         </ion-item> |         </ion-item> | ||||||
|     </ion-list> |     </ion-list> | ||||||
| </ion-content> | </ion-content> | ||||||
| <ion-footer class="ion-padding"> | <ion-footer> | ||||||
|     <ion-button (click)="logout($event)" expand="block" color="danger" [attr.aria-label]="'core.mainmenu.logout' | translate" |     <ion-item button lines="full" (click)="switchAccounts($event)" detail="true" class="ion-text-wrap"> | ||||||
|         class="ion-text-wrap"> |         <ion-icon name="fas-exchange-alt" slot="start" aria-hidden="true"></ion-icon> | ||||||
|         <ion-icon name="fas-sign-out-alt" slot="start" aria-hidden="true"></ion-icon> |         <ion-label> | ||||||
|         {{ 'core.mainmenu.logout' | translate }} |             <p class="item-heading">{{ 'core.mainmenu.switchaccount' | translate }}</p> | ||||||
|     </ion-button> |         </ion-label> | ||||||
|  |     </ion-item> | ||||||
|  |     <div class="ion-padding"> | ||||||
|  |         <ion-button (click)="logout($event)" expand="block" color="danger" [attr.aria-label]="'core.mainmenu.logout' | translate" | ||||||
|  |             class="ion-text-wrap"> | ||||||
|  |             <ion-icon name="fas-sign-out-alt" slot="start" aria-hidden="true"></ion-icon> | ||||||
|  |             {{ 'core.mainmenu.logout' | translate }} | ||||||
|  |         </ion-button> | ||||||
|  |     </div> | ||||||
| </ion-footer> | </ion-footer> | ||||||
|  | |||||||
| @ -3,16 +3,25 @@ | |||||||
| :host { | :host { | ||||||
|     .core-user-menu-preferences { |     .core-user-menu-preferences { | ||||||
|         --inner-border-width: 0; |         --inner-border-width: 0; | ||||||
|         --border-width: 1px 0 0 0;  |         --border-width: 1px 0 0 0; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .core-usermenu-siteinfo { | ||||||
|  |     --padding-top: 10px; | ||||||
|  |     --padding-bottom: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @if ($core-user-hide-siteinfo) { | @if ($core-user-hide-siteinfo) { | ||||||
|     .core-usermenu-siteinfo { |     .core-usermenu-siteinfo { | ||||||
|         display: none; |         display: none; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .core-usermenu-sitename { | ||||||
|  |     font-size: 16px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @if ($core-user-hide-sitename) { | @if ($core-user-hide-sitename) { | ||||||
|     .core-usermenu-sitename { |     .core-usermenu-sitename { | ||||||
|         display: none; |         display: none; | ||||||
| @ -24,3 +33,7 @@ | |||||||
|         display: none; |         display: none; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | ion-footer { | ||||||
|  |     background: var(--contrast-background); | ||||||
|  | } | ||||||
|  | |||||||
| @ -303,7 +303,7 @@ export class CoreQuestionHelperProvider { | |||||||
| 
 | 
 | ||||||
|                 // Remove start and end of the match, we only want the object.
 |                 // Remove start and end of the match, we only want the object.
 | ||||||
|                 initMatch = initMatch.replace('M.qtype_' + question.type + '.init_question(', ''); |                 initMatch = initMatch.replace('M.qtype_' + question.type + '.init_question(', ''); | ||||||
|                 initMatch = initMatch.substr(0, initMatch.length - 2); |                 initMatch = initMatch.substring(0, initMatch.length - 2); | ||||||
| 
 | 
 | ||||||
|                 // Try to convert it to an object and add it to the question.
 |                 // Try to convert it to an object and add it to the question.
 | ||||||
|                 question.initObjects = CoreTextUtils.parseJSON(initMatch, null); |                 question.initObjects = CoreTextUtils.parseJSON(initMatch, null); | ||||||
|  | |||||||
| @ -100,7 +100,7 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy { | |||||||
| 
 | 
 | ||||||
|         if (window.location && window.location.href) { |         if (window.location && window.location.href) { | ||||||
|             const url = window.location.href; |             const url = window.location.href; | ||||||
|             this.deviceInfo.locationHref = url.indexOf('#') > 0 ? url.substr(0, url.indexOf('#')) : url; |             this.deviceInfo.locationHref = url.indexOf('#') > 0 ? url.substring(0, url.indexOf('#')) : url; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (window.screen) { |         if (window.screen) { | ||||||
|  | |||||||
| @ -30,4 +30,5 @@ | |||||||
|     (onLoadingContent)="contentLoading()"> |     (onLoadingContent)="contentLoading()"> | ||||||
| </core-site-plugins-plugin-content> | </core-site-plugins-plugin-content> | ||||||
| 
 | 
 | ||||||
| <core-course-module-navigation *ngIf="module" [courseId]="courseId" [currentModuleId]="module.id"></core-course-module-navigation> | <core-course-module-navigation *ngIf="module" [courseId]="courseId" [currentModule]="module"> | ||||||
|  | </core-course-module-navigation> | ||||||
|  | |||||||
| @ -232,8 +232,8 @@ export class CoreFileProvider { | |||||||
|         } else { |         } else { | ||||||
|             // The file plugin doesn't allow creating more than 1 level at a time (e.g. tmp/folder).
 |             // The file plugin doesn't allow creating more than 1 level at a time (e.g. tmp/folder).
 | ||||||
|             // We need to create them 1 by 1.
 |             // We need to create them 1 by 1.
 | ||||||
|             const firstDir = path.substr(0, path.indexOf('/')); |             const firstDir = path.substring(0, path.indexOf('/')); | ||||||
|             const restOfPath = path.substr(path.indexOf('/') + 1); |             const restOfPath = path.substring(path.indexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|             this.logger.debug('Create dir ' + firstDir + ' in ' + base); |             this.logger.debug('Create dir ' + firstDir + ' in ' + base); | ||||||
| 
 | 
 | ||||||
| @ -692,7 +692,7 @@ export class CoreFileProvider { | |||||||
|      */ |      */ | ||||||
|     async removeExternalFile(fullPath: string): Promise<void> { |     async removeExternalFile(fullPath: string): Promise<void> { | ||||||
|         const directory = fullPath.substring(0, fullPath.lastIndexOf('/')); |         const directory = fullPath.substring(0, fullPath.lastIndexOf('/')); | ||||||
|         const filename = fullPath.substr(fullPath.lastIndexOf('/') + 1); |         const filename = fullPath.substring(fullPath.lastIndexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|         await File.removeFile(directory, filename); |         await File.removeFile(directory, filename); | ||||||
|     } |     } | ||||||
| @ -885,7 +885,7 @@ export class CoreFileProvider { | |||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         file.directory = path.substring(0, path.lastIndexOf('/')); |         file.directory = path.substring(0, path.lastIndexOf('/')); | ||||||
|         file.name = path.substr(path.lastIndexOf('/') + 1); |         file.name = path.substring(path.lastIndexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|         return file; |         return file; | ||||||
|     } |     } | ||||||
| @ -1035,7 +1035,7 @@ export class CoreFileProvider { | |||||||
|      */ |      */ | ||||||
|     removeStartingSlash(path: string): string { |     removeStartingSlash(path: string): string { | ||||||
|         if (path[0] == '/') { |         if (path[0] == '/') { | ||||||
|             return path.substr(1); |             return path.substring(1); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return path; |         return path; | ||||||
| @ -1232,7 +1232,7 @@ export class CoreFileProvider { | |||||||
|         const position = window.location.href.indexOf(window.location.pathname); |         const position = window.location.href.indexOf(window.location.pathname); | ||||||
| 
 | 
 | ||||||
|         if (position != -1) { |         if (position != -1) { | ||||||
|             return window.location.href.substr(0, position); |             return window.location.href.substring(0, position); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return window.location.href; |         return window.location.href; | ||||||
|  | |||||||
| @ -777,7 +777,7 @@ export class CoreFilepoolProvider { | |||||||
|                 // Calculate the path to the file.
 |                 // Calculate the path to the file.
 | ||||||
|                 path = file.filename || ''; |                 path = file.filename || ''; | ||||||
|                 if (file.filepath && file.filepath !== '/') { |                 if (file.filepath && file.filepath !== '/') { | ||||||
|                     path = file.filepath.substr(1) + path; |                     path = file.filepath.substring(1) + path; | ||||||
|                 } |                 } | ||||||
|                 path = CoreTextUtils.concatenatePaths(dirPath, path); |                 path = CoreTextUtils.concatenatePaths(dirPath, path); | ||||||
|             } |             } | ||||||
| @ -871,7 +871,7 @@ export class CoreFilepoolProvider { | |||||||
|                     // Calculate the path to the file.
 |                     // Calculate the path to the file.
 | ||||||
|                     path = file.filename || ''; |                     path = file.filename || ''; | ||||||
|                     if (file.filepath && file.filepath !== '/') { |                     if (file.filepath && file.filepath !== '/') { | ||||||
|                         path = file.filepath.substr(1) + path; |                         path = file.filepath.substring(1) + path; | ||||||
|                     } |                     } | ||||||
|                     path = CoreTextUtils.concatenatePaths(dirPath, path); |                     path = CoreTextUtils.concatenatePaths(dirPath, path); | ||||||
|                 } |                 } | ||||||
| @ -1837,7 +1837,7 @@ export class CoreFilepoolProvider { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const relativePath = url.substr(url.indexOf('/pluginfile.php') + 16); |         const relativePath = url.substring(url.indexOf('/pluginfile.php') + 16); | ||||||
|         const args = relativePath.split('/'); |         const args = relativePath.split('/'); | ||||||
| 
 | 
 | ||||||
|         if (args.length < 3) { |         if (args.length < 3) { | ||||||
| @ -2084,7 +2084,7 @@ export class CoreFilepoolProvider { | |||||||
|             // It's a pluginfile URL. Search for the 'file' param to extract the name.
 |             // It's a pluginfile URL. Search for the 'file' param to extract the name.
 | ||||||
|             const params = CoreUrlUtils.extractUrlParams(fileUrl); |             const params = CoreUrlUtils.extractUrlParams(fileUrl); | ||||||
|             if (params.file) { |             if (params.file) { | ||||||
|                 filename = params.file.substr(params.file.lastIndexOf('/') + 1); |                 filename = params.file.substring(params.file.lastIndexOf('/') + 1); | ||||||
|             } else { |             } else { | ||||||
|                 // 'file' param not found. Extract what's after the last '/' without params.
 |                 // 'file' param not found. Extract what's after the last '/' without params.
 | ||||||
|                 filename = CoreUrlUtils.getLastFileWithoutParams(fileUrl); |                 filename = CoreUrlUtils.getLastFileWithoutParams(fileUrl); | ||||||
| @ -2115,7 +2115,7 @@ export class CoreFilepoolProvider { | |||||||
|             // Remove the URL from the array.
 |             // Remove the URL from the array.
 | ||||||
|             hashes.shift(); |             hashes.shift(); | ||||||
| 
 | 
 | ||||||
|             filename = filename.substr(0, index); |             filename = filename.substring(0, index); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Remove the extension from the filename.
 |         // Remove the extension from the filename.
 | ||||||
|  | |||||||
| @ -256,7 +256,7 @@ export class CoreLangProvider { | |||||||
|             // Language code defined by locale has a dash, like en-US or es-ES. Check if it's supported.
 |             // Language code defined by locale has a dash, like en-US or es-ES. Check if it's supported.
 | ||||||
|             if (CoreConstants.CONFIG.languages && CoreConstants.CONFIG.languages[preferredLanguage] === undefined) { |             if (CoreConstants.CONFIG.languages && CoreConstants.CONFIG.languages[preferredLanguage] === undefined) { | ||||||
|                 // Code is NOT supported. Fallback to language without dash. E.g. 'en-US' would fallback to 'en'.
 |                 // Code is NOT supported. Fallback to language without dash. E.g. 'en-US' would fallback to 'en'.
 | ||||||
|                 preferredLanguage = preferredLanguage.substr(0, preferredLanguage.indexOf('-')); |                 preferredLanguage = preferredLanguage.substring(0, preferredLanguage.indexOf('-')); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -249,7 +249,7 @@ export class CoreCustomURLSchemesProvider { | |||||||
| 
 | 
 | ||||||
|         // Remove the params to get the site URL.
 |         // Remove the params to get the site URL.
 | ||||||
|         if (url.indexOf('?') != -1) { |         if (url.indexOf('?') != -1) { | ||||||
|             url = url.substr(0, url.indexOf('?')); |             url = url.substring(0, url.indexOf('?')); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!url.match(/https?:\/\//)) { |         if (!url.match(/https?:\/\//)) { | ||||||
|  | |||||||
| @ -443,7 +443,7 @@ export class CoreIframeUtilsProvider { | |||||||
|             } |             } | ||||||
|         } else if (CoreUrlUtils.isLocalFileUrl(url)) { |         } else if (CoreUrlUtils.isLocalFileUrl(url)) { | ||||||
|             // It's a local file.
 |             // It's a local file.
 | ||||||
|             const filename = url.substr(url.lastIndexOf('/') + 1); |             const filename = url.substring(url.lastIndexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|             if (!CoreFileHelper.isOpenableInApp({ filename })) { |             if (!CoreFileHelper.isOpenableInApp({ filename })) { | ||||||
|                 try { |                 try { | ||||||
| @ -522,7 +522,7 @@ export class CoreIframeUtilsProvider { | |||||||
|             // Opening links with _parent, _top or _blank can break the app. We'll open it in InAppBrowser.
 |             // Opening links with _parent, _top or _blank can break the app. We'll open it in InAppBrowser.
 | ||||||
|             event && event.preventDefault(); |             event && event.preventDefault(); | ||||||
| 
 | 
 | ||||||
|             const filename = link.href.substr(link.href.lastIndexOf('/') + 1); |             const filename = link.href.substring(link.href.lastIndexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|             if (!CoreFileHelper.isOpenableInApp({ filename })) { |             if (!CoreFileHelper.isOpenableInApp({ filename })) { | ||||||
|                 try { |                 try { | ||||||
|  | |||||||
| @ -84,13 +84,13 @@ export class CoreMimetypeUtilsProvider { | |||||||
|         // If the extension has parameters, remove them.
 |         // If the extension has parameters, remove them.
 | ||||||
|         let position = extension.indexOf('?'); |         let position = extension.indexOf('?'); | ||||||
|         if (position > -1) { |         if (position > -1) { | ||||||
|             extension = extension.substr(0, position); |             extension = extension.substring(0, position); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // If the extension has an anchor, remove it.
 |         // If the extension has an anchor, remove it.
 | ||||||
|         position = extension.indexOf('#'); |         position = extension.indexOf('#'); | ||||||
|         if (position > -1) { |         if (position > -1) { | ||||||
|             extension = extension.substr(0, position); |             extension = extension.substring(0, position); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Remove hash in extension if there's any (added by filepool).
 |         // Remove hash in extension if there's any (added by filepool).
 | ||||||
| @ -98,7 +98,7 @@ export class CoreMimetypeUtilsProvider { | |||||||
| 
 | 
 | ||||||
|         // Remove dot from the extension if found.
 |         // Remove dot from the extension if found.
 | ||||||
|         if (extension && extension[0] == '.') { |         if (extension && extension[0] == '.') { | ||||||
|             extension = extension.substr(1); |             extension = extension.substring(1); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return extension; |         return extension; | ||||||
| @ -311,12 +311,12 @@ export class CoreMimetypeUtilsProvider { | |||||||
|             // Remove params if any.
 |             // Remove params if any.
 | ||||||
|             position = candidate.indexOf('?'); |             position = candidate.indexOf('?'); | ||||||
|             if (position > -1) { |             if (position > -1) { | ||||||
|                 candidate = candidate.substr(0, position); |                 candidate = candidate.substring(0, position); | ||||||
|             } |             } | ||||||
|             // Remove anchor if any.
 |             // Remove anchor if any.
 | ||||||
|             position = candidate.indexOf('#'); |             position = candidate.indexOf('#'); | ||||||
|             if (position > -1) { |             if (position > -1) { | ||||||
|                 candidate = candidate.substr(0, position); |                 candidate = candidate.substring(0, position); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (EXTENSION_REGEX.test(candidate)) { |             if (EXTENSION_REGEX.test(candidate)) { | ||||||
| @ -346,7 +346,7 @@ export class CoreMimetypeUtilsProvider { | |||||||
|         let ext; |         let ext; | ||||||
| 
 | 
 | ||||||
|         if (dot > -1) { |         if (dot > -1) { | ||||||
|             ext = filename.substr(dot + 1).toLowerCase(); |             ext = filename.substring(dot + 1).toLowerCase(); | ||||||
|             ext = this.cleanExtension(ext); |             ext = this.cleanExtension(ext); | ||||||
| 
 | 
 | ||||||
|             // Check extension corresponds to a mimetype to know if it's valid.
 |             // Check extension corresponds to a mimetype to know if it's valid.
 | ||||||
| @ -589,9 +589,9 @@ export class CoreMimetypeUtilsProvider { | |||||||
| 
 | 
 | ||||||
|         if (position > -1) { |         if (position > -1) { | ||||||
|             // Check extension corresponds to a mimetype to know if it's valid.
 |             // Check extension corresponds to a mimetype to know if it's valid.
 | ||||||
|             extension = path.substr(position + 1).toLowerCase(); |             extension = path.substring(position + 1).toLowerCase(); | ||||||
|             if (this.getMimeType(extension) !== undefined) { |             if (this.getMimeType(extension) !== undefined) { | ||||||
|                 return path.substr(0, position); // Remove extension.
 |                 return path.substring(0, position); // Remove extension.
 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -279,7 +279,7 @@ export class CoreTextUtilsProvider { | |||||||
|         const firstCharRight = rightPath.charAt(0); |         const firstCharRight = rightPath.charAt(0); | ||||||
| 
 | 
 | ||||||
|         if (lastCharLeft === '/' && firstCharRight === '/') { |         if (lastCharLeft === '/' && firstCharRight === '/') { | ||||||
|             return leftPath + rightPath.substr(1); |             return leftPath + rightPath.substring(1); | ||||||
|         } else if (lastCharLeft !== '/' && firstCharRight !== '/') { |         } else if (lastCharLeft !== '/' && firstCharRight !== '/') { | ||||||
|             return leftPath + '/' + rightPath; |             return leftPath + '/' + rightPath; | ||||||
|         } else { |         } else { | ||||||
| @ -539,7 +539,7 @@ export class CoreTextUtilsProvider { | |||||||
|             const url = CoreFileHelper.getFileUrl(files[0]); |             const url = CoreFileHelper.getFileUrl(files[0]); | ||||||
| 
 | 
 | ||||||
|             // Remove text after last slash (encoded or not).
 |             // Remove text after last slash (encoded or not).
 | ||||||
|             return url?.substr(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F'))); |             return url?.substring(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F'))); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return undefined; |         return undefined; | ||||||
| @ -778,9 +778,9 @@ export class CoreTextUtilsProvider { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Get the filename from the URL.
 |             // Get the filename from the URL.
 | ||||||
|             let filename = url.substr(url.lastIndexOf('/') + 1); |             let filename = url.substring(url.lastIndexOf('/') + 1); | ||||||
|             if (filename.indexOf('?') != -1) { |             if (filename.indexOf('?') != -1) { | ||||||
|                 filename = filename.substr(0, filename.indexOf('?')); |                 filename = filename.substring(0, filename.indexOf('?')); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (pluginfileMap[filename]) { |             if (pluginfileMap[filename]) { | ||||||
| @ -904,12 +904,12 @@ export class CoreTextUtilsProvider { | |||||||
|      */ |      */ | ||||||
|     shortenText(text: string, length: number): string { |     shortenText(text: string, length: number): string { | ||||||
|         if (text.length > length) { |         if (text.length > length) { | ||||||
|             text = text.substr(0, length); |             text = text.substring(0, length); | ||||||
| 
 | 
 | ||||||
|             // Now, truncate at the last word boundary (if exists).
 |             // Now, truncate at the last word boundary (if exists).
 | ||||||
|             const lastWordPos = text.lastIndexOf(' '); |             const lastWordPos = text.lastIndexOf(' '); | ||||||
|             if (lastWordPos > 0) { |             if (lastWordPos > 0) { | ||||||
|                 text = text.substr(0, lastWordPos); |                 text = text.substring(0, lastWordPos); | ||||||
|             } |             } | ||||||
|             text += '…'; |             text += '…'; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -329,9 +329,9 @@ export class CoreUrlUtilsProvider { | |||||||
|      * @return Last file without params. |      * @return Last file without params. | ||||||
|      */ |      */ | ||||||
|     getLastFileWithoutParams(url: string): string { |     getLastFileWithoutParams(url: string): string { | ||||||
|         let filename = url.substr(url.lastIndexOf('/') + 1); |         let filename = url.substring(url.lastIndexOf('/') + 1); | ||||||
|         if (filename.indexOf('?') != -1) { |         if (filename.indexOf('?') != -1) { | ||||||
|             filename = filename.substr(0, filename.indexOf('?')); |             filename = filename.substring(0, filename.indexOf('?')); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return filename; |         return filename; | ||||||
|  | |||||||
| @ -1254,7 +1254,7 @@ export class CoreUtilsProvider { | |||||||
|         const mapped = {}; |         const mapped = {}; | ||||||
|         objects.forEach((item) => { |         objects.forEach((item) => { | ||||||
|             const keyValue = item[keyName] as string; |             const keyValue = item[keyName] as string; | ||||||
|             const key = prefixSubstr > 0 ? keyValue.substr(prefixSubstr) : keyValue; |             const key = prefixSubstr > 0 ? keyValue.substring(prefixSubstr) : keyValue; | ||||||
|             mapped[key] = item[valueName]; |             mapped[key] = item[valueName]; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -112,7 +112,7 @@ export class CoreColors { | |||||||
|      */ |      */ | ||||||
|     static hexToRGB(color: string): ColorComponents { |     static hexToRGB(color: string): ColorComponents { | ||||||
|         if (color.charAt(0) == '#') { |         if (color.charAt(0) == '#') { | ||||||
|             color = color.substr(1); |             color = color.substring(1); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (color.length === 3) { |         if (color.length === 3) { | ||||||
| @ -122,9 +122,9 @@ export class CoreColors { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|             red: parseInt(color.substr(0, 2), 16), |             red: parseInt(color.substring(0, 2), 16), | ||||||
|             green: parseInt(color.substr(2, 2), 16), |             green: parseInt(color.substring(2, 2), 16), | ||||||
|             blue: parseInt(color.substr(4, 2), 16), |             blue: parseInt(color.substring(4, 2), 16), | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -123,7 +123,7 @@ function readBytes (str, len, escapedString = false) { | |||||||
|     const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff |     const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff | ||||||
| 
 | 
 | ||||||
|     if (escapedString && chr === '\\') { |     if (escapedString && chr === '\\') { | ||||||
|       chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16)) |       chr = String.fromCharCode(parseInt(str.substring(c + 1, 2), 16)) | ||||||
|       escapedChars++ |       escapedChars++ | ||||||
| 
 | 
 | ||||||
|       // each escaped sequence is 3 characters. Go 2 chars ahead.
 |       // each escaped sequence is 3 characters. Go 2 chars ahead.
 | ||||||
| @ -167,7 +167,7 @@ function expectString (str) { | |||||||
| 
 | 
 | ||||||
|   const len = parseInt(byteLenMatch, 10) |   const len = parseInt(byteLenMatch, 10) | ||||||
| 
 | 
 | ||||||
|   str = str.substr(match.length) |   str = str.substring(match.length) | ||||||
| 
 | 
 | ||||||
|   let [ strMatch, bytes ] = readBytes(str, len) |   let [ strMatch, bytes ] = readBytes(str, len) | ||||||
| 
 | 
 | ||||||
| @ -175,7 +175,7 @@ function expectString (str) { | |||||||
|     throw SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`) |     throw SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   str = str.substr((strMatch as string).length) |   str = str.substring((strMatch as string).length) | ||||||
| 
 | 
 | ||||||
|   // strict parsing, match closing "; chars
 |   // strict parsing, match closing "; chars
 | ||||||
|   if (!str.startsWith('";')) { |   if (!str.startsWith('";')) { | ||||||
| @ -195,7 +195,7 @@ function expectEscapedString (str) { | |||||||
| 
 | 
 | ||||||
|   const len = parseInt(strLenMatch, 10) |   const len = parseInt(strLenMatch, 10) | ||||||
| 
 | 
 | ||||||
|   str = str.substr(match.length) |   str = str.substring(match.length) | ||||||
| 
 | 
 | ||||||
|   let [ strMatch, bytes, escapedChars ] = readBytes(str, len, true) |   let [ strMatch, bytes, escapedChars ] = readBytes(str, len, true) | ||||||
| 
 | 
 | ||||||
| @ -203,7 +203,7 @@ function expectEscapedString (str) { | |||||||
|     throw SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`) |     throw SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   str = str.substr((strMatch as string).length + (escapedChars as number) * 2) |   str = str.substring((strMatch as string).length + (escapedChars as number) * 2) | ||||||
| 
 | 
 | ||||||
|   // strict parsing, match closing "; chars
 |   // strict parsing, match closing "; chars
 | ||||||
|   if (!str.startsWith('";')) { |   if (!str.startsWith('";')) { | ||||||
| @ -249,15 +249,15 @@ function expectObject (str, cache) { | |||||||
|   const obj = {} |   const obj = {} | ||||||
|   cache([obj]) |   cache([obj]) | ||||||
| 
 | 
 | ||||||
|   str = str.substr(totalOffset) |   str = str.substring(totalOffset) | ||||||
| 
 | 
 | ||||||
|   for (let i = 0; i < propCount; i++) { |   for (let i = 0; i < propCount; i++) { | ||||||
|     const prop = expectKeyOrIndex(str) |     const prop = expectKeyOrIndex(str) | ||||||
|     str = str.substr(prop[1]) |     str = str.substring(prop[1]) | ||||||
|     totalOffset += prop[1] as number |     totalOffset += prop[1] as number | ||||||
| 
 | 
 | ||||||
|     const value = expectType(str, cache) |     const value = expectType(str, cache) | ||||||
|     str = str.substr(value[1]) |     str = str.substring(value[1]) | ||||||
|     totalOffset += value[1] |     totalOffset += value[1] | ||||||
| 
 | 
 | ||||||
|     obj[prop[0]] = value[0] |     obj[prop[0]] = value[0] | ||||||
| @ -299,7 +299,7 @@ function expectArray (str, cache) { | |||||||
|     throw SyntaxError('Expected array length annotation') |     throw SyntaxError('Expected array length annotation') | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   str = str.substr(arrayLiteralBeginMatch.length) |   str = str.substring(arrayLiteralBeginMatch.length) | ||||||
| 
 | 
 | ||||||
|   const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache) |   const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache) | ||||||
| 
 | 
 | ||||||
| @ -327,14 +327,14 @@ function expectArrayItems (str, expectedItems = 0, cache) { | |||||||
|       hasStringKeys = (typeof key[0] === 'string') |       hasStringKeys = (typeof key[0] === 'string') | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     str = str.substr(key[1]) |     str = str.substring(key[1]) | ||||||
|     totalOffset += key[1] |     totalOffset += key[1] | ||||||
| 
 | 
 | ||||||
|     // references are resolved immediately, so if duplicate key overwrites previous array index
 |     // references are resolved immediately, so if duplicate key overwrites previous array index
 | ||||||
|     // the old value is anyway resolved
 |     // the old value is anyway resolved
 | ||||||
|     // fixme: but next time the same reference should point to the new value
 |     // fixme: but next time the same reference should point to the new value
 | ||||||
|     item = expectType(str, cache) |     item = expectType(str, cache) | ||||||
|     str = str.substr(item[1]) |     str = str.substring(item[1]) | ||||||
|     totalOffset += item[1] |     totalOffset += item[1] | ||||||
| 
 | 
 | ||||||
|     items[key[0]] = item[0] |     items[key[0]] = item[0] | ||||||
| @ -428,7 +428,7 @@ function substr_replace (str, replace, start, length) { | |||||||
| 
 | 
 | ||||||
|   return [ |   return [ | ||||||
|     str.slice(0, start), |     str.slice(0, start), | ||||||
|     replace.substr(0, length), |     replace.substring(0, length), | ||||||
|     replace.slice(length), |     replace.slice(length), | ||||||
|     str.slice(start + length) |     str.slice(start + length) | ||||||
|   ].join('') |   ].join('') | ||||||
|  | |||||||
| @ -34,7 +34,7 @@ export class CoreText { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (text.slice(-1) == '/') { |         if (text.slice(-1) == '/') { | ||||||
|             return text.substr(0, text.length - 1); |             return text.substring(0, text.length - 1); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return text; |         return text; | ||||||
|  | |||||||
| @ -216,7 +216,7 @@ export class CoreUrl { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return url.substr(firstAnchorIndex); |         return url.substring(firstAnchorIndex); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -82,7 +82,7 @@ export class CoreWindow { | |||||||
|      */ |      */ | ||||||
|     static async open(url: string, name?: string): Promise<void> { |     static async open(url: string, name?: string): Promise<void> { | ||||||
|         if (CoreUrlUtils.isLocalFileUrl(url)) { |         if (CoreUrlUtils.isLocalFileUrl(url)) { | ||||||
|             const filename = url.substr(url.lastIndexOf('/') + 1); |             const filename = url.substring(url.lastIndexOf('/') + 1); | ||||||
| 
 | 
 | ||||||
|             if (!CoreFileHelper.isOpenableInApp({ filename })) { |             if (!CoreFileHelper.isOpenableInApp({ filename })) { | ||||||
|                 try { |                 try { | ||||||
|  | |||||||
| @ -209,7 +209,7 @@ | |||||||
|         --vertical-margin: 10px; |         --vertical-margin: 10px; | ||||||
|         --horizontal-margin: 10px; |         --horizontal-margin: 10px; | ||||||
| 
 | 
 | ||||||
|         width: calc(100% - var(--vertical-margin) - var(--vertical-margin)); |         width: calc(100% - var(--horizontal-margin) - var(--horizontal-margin)); | ||||||
|         height: calc(100% - var(--vertical-margin) - var(--vertical-margin)); |         height: calc(100% - var(--vertical-margin) - var(--vertical-margin)); | ||||||
|         margin: var(--vertical-margin) var(--horizontal-margin); |         margin: var(--vertical-margin) var(--horizontal-margin); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -554,7 +554,7 @@ body.core-iframe-fullscreen ion-router-outlet { | |||||||
|             display: block; |             display: block; | ||||||
|             height: 100% !important; |             height: 100% !important; | ||||||
|             width: auto; |             width: auto; | ||||||
|             min-width: #{$modal-lateral-width}; |             min-width: calc(#{$modal-lateral-width} - 16px); | ||||||
|             box-shadow: 0 28px 48px rgba(0, 0, 0, 0.4); |             box-shadow: 0 28px 48px rgba(0, 0, 0, 0.4); | ||||||
|         } |         } | ||||||
|         ion-backdrop { |         ion-backdrop { | ||||||
|  | |||||||
| @ -215,6 +215,10 @@ | |||||||
|         --color: var(--item-divider-color); |         --color: var(--item-divider-color); | ||||||
|         --min-height: var(--item-divider-min-height); |         --min-height: var(--item-divider-min-height); | ||||||
|         min-height: var(--min-height); |         min-height: var(--min-height); | ||||||
|  | 
 | ||||||
|  |         .expandable-status-icon { | ||||||
|  |             font-size: 18px; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     --spacer-background: var(--item-divider-background); |     --spacer-background: var(--item-divider-background); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user