commit
fb1096381e
|
@ -156,7 +156,7 @@
|
|||
<h2>{{ 'addon.badges.relatedbages' | translate}}</h2>
|
||||
</ion-item-divider>
|
||||
<ion-item text-wrap *ngFor="let relatedBadge of badge.relatedbadges">
|
||||
<h2><{{ relatedBadge.name }}</h2>
|
||||
<h2>{{ relatedBadge.name }}</h2>
|
||||
</ion-item>
|
||||
<ion-item text-wrap *ngIf="badge.relatedbadges.length == 0">
|
||||
<h2>{{ 'addon.badges.norelated' | translate}}</h2>
|
||||
|
|
|
@ -91,6 +91,12 @@ export class AddonBadgesProvider {
|
|||
// In 3.7, competencies was renamed to alignment. Rename the property in 3.6 too.
|
||||
response.badges.forEach((badge) => {
|
||||
badge.alignment = badge.alignment || badge.competencies;
|
||||
|
||||
// Check that the alignment is valid, they were broken in 3.7.
|
||||
if (badge.alignment && badge.alignment[0] && typeof badge.alignment[0].targetname == 'undefined') {
|
||||
// If any badge lacks targetname it means they are affected by the Moodle bug, don't display them.
|
||||
delete badge.alignment;
|
||||
}
|
||||
});
|
||||
|
||||
return response.badges;
|
||||
|
@ -175,20 +181,20 @@ export type AddonBadgesUserBadge = {
|
|||
alignment?: { // @since 3.7. Calculated by the app for 3.6 sites. Badge alignments.
|
||||
id?: number; // Alignment id.
|
||||
badgeid?: number; // Badge id.
|
||||
targetName?: string; // Target name.
|
||||
targetUrl?: string; // Target URL.
|
||||
targetDescription?: string; // Target description.
|
||||
targetFramework?: string; // Target framework.
|
||||
targetCode?: string; // Target code.
|
||||
targetname?: string; // Target name.
|
||||
targeturl?: string; // Target URL.
|
||||
targetdescription?: string; // Target description.
|
||||
targetframework?: string; // Target framework.
|
||||
targetcode?: string; // Target code.
|
||||
}[];
|
||||
competencies?: { // @deprecated from 3.7. @since 3.6. In 3.7 it was renamed to alignment.
|
||||
id?: number; // Alignment id.
|
||||
badgeid?: number; // Badge id.
|
||||
targetName?: string; // Target name.
|
||||
targetUrl?: string; // Target URL.
|
||||
targetDescription?: string; // Target description.
|
||||
targetFramework?: string; // Target framework.
|
||||
targetCode?: string; // Target code.
|
||||
targetname?: string; // Target name.
|
||||
targeturl?: string; // Target URL.
|
||||
targetdescription?: string; // Target description.
|
||||
targetframework?: string; // Target framework.
|
||||
targetcode?: string; // Target code.
|
||||
}[];
|
||||
relatedbadges?: { // @since 3.6. Related badges.
|
||||
id: number; // Badge id.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
<ion-item *ngIf="showMyEntriesToggle">
|
||||
<ion-label>{{ 'addon.blog.showonlyyourentries' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)">></ion-toggle>
|
||||
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)"></ion-toggle>
|
||||
</ion-item>
|
||||
<core-empty-box *ngIf="entries && entries.length == 0" icon="fa-newspaper-o" [message]="'addon.blog.noentriesyet' | translate"></core-empty-box>
|
||||
<ng-container *ngFor="let entry of entries">
|
||||
|
|
|
@ -119,6 +119,25 @@ export class AddonModAssignProvider {
|
|||
return assign.submissiondrafts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix some submission status params.
|
||||
*
|
||||
* @param site Site to use.
|
||||
* @param userId User Id (empty for current user).
|
||||
* @param groupId Group Id (empty for all participants).
|
||||
* @param isBlind If blind marking is enabled or not.
|
||||
* @return Object with fixed params.
|
||||
*/
|
||||
protected fixSubmissionStatusParams(site: CoreSite, userId?: number, groupId?: number, isBlind?: boolean)
|
||||
: {userId: number, groupId: number, isBlind: boolean} {
|
||||
|
||||
return {
|
||||
isBlind: !userId ? false : !!isBlind,
|
||||
groupId: site.isVersionGreaterEqualThan('3.5') ? groupId || 0 : 0,
|
||||
userId: userId || site.getUserId(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an assignment by course module ID.
|
||||
*
|
||||
|
@ -502,24 +521,23 @@ export class AddonModAssignProvider {
|
|||
getSubmissionStatus(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true,
|
||||
ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignGetSubmissionStatusResult> {
|
||||
|
||||
userId = userId || 0;
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
groupId = site.isVersionGreaterEqualThan('3.5') ? groupId || 0 : 0;
|
||||
const fixedParams = this.fixSubmissionStatusParams(site, userId, groupId, isBlind);
|
||||
|
||||
const params = {
|
||||
assignid: assignId,
|
||||
userid: userId
|
||||
userid: fixedParams.userId
|
||||
},
|
||||
preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getSubmissionStatusCacheKey(assignId, userId, groupId, isBlind),
|
||||
cacheKey: this.getSubmissionStatusCacheKey(assignId, fixedParams.userId, fixedParams.groupId,
|
||||
fixedParams.isBlind),
|
||||
getCacheUsingCacheKey: true, // We use the cache key to take isBlind into account.
|
||||
filter: filter,
|
||||
rewriteurls: filter
|
||||
};
|
||||
|
||||
if (groupId) {
|
||||
params['groupid'] = groupId;
|
||||
if (fixedParams.groupId) {
|
||||
params['groupid'] = fixedParams.groupId;
|
||||
}
|
||||
|
||||
if (ignoreCache) {
|
||||
|
@ -578,11 +596,6 @@ export class AddonModAssignProvider {
|
|||
* @return Cache key.
|
||||
*/
|
||||
protected getSubmissionStatusCacheKey(assignId: number, userId: number, groupId?: number, isBlind?: boolean): string {
|
||||
if (!userId) {
|
||||
isBlind = false;
|
||||
userId = this.sitesProvider.getCurrentSiteUserId();
|
||||
}
|
||||
|
||||
return this.getSubmissionsCacheKey(assignId) + ':' + userId + ':' + (isBlind ? 1 : 0) + ':' + groupId;
|
||||
}
|
||||
|
||||
|
@ -809,7 +822,10 @@ export class AddonModAssignProvider {
|
|||
invalidateSubmissionStatusData(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, siteId?: string):
|
||||
Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getSubmissionStatusCacheKey(assignId, userId, groupId, isBlind));
|
||||
const fixedParams = this.fixSubmissionStatusParams(site, userId, groupId, isBlind);
|
||||
|
||||
return site.invalidateWsCacheForKey(this.getSubmissionStatusCacheKey(assignId, fixedParams.userId,
|
||||
fixedParams.groupId, fixedParams.isBlind));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<ion-item text-wrap *ngIf="item.typ != 'pagebreak'" [color]="item.dependitem > 0 ? 'light' : ''" [class.core-danger-item]="item.isEmpty || item.hasError">
|
||||
<ion-label *ngIf="item.name" [core-mark-required]="item.required" stacked>
|
||||
<span *ngIf="feedback.autonumbering && item.itemnumber">{{item.itemnumber}}. </span>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="item.name" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="item.name" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" [wsNotFiltered]="true"></core-format-text>
|
||||
<span *ngIf="item.postfix" class="addon-mod_feedback-postfix">{{item.postfix}}</span>
|
||||
</ion-label>
|
||||
<div item-content class="addon-mod_feedback-form-content" *ngIf="item.template">
|
||||
|
|
|
@ -105,7 +105,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
|||
|
||||
if (this.canGetFolder) {
|
||||
promise = this.folderProvider.getFolder(this.courseId, this.module.id).then((folder) => {
|
||||
return this.courseProvider.loadModuleContents(this.module, this.courseId).then(() => {
|
||||
return this.courseProvider.loadModuleContents(this.module, this.courseId, undefined, false, refresh).then(() => {
|
||||
folderContents = this.module.contents;
|
||||
|
||||
return folder;
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<ion-icon name="trash"></ion-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>{{ note.content }}</ion-item>
|
||||
<ion-item text-wrap><core-format-text [text]="note.content" [filter]="false"></core-format-text></ion-item>
|
||||
</ion-card>
|
||||
</ion-list>
|
||||
|
||||
|
|
|
@ -180,18 +180,19 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
if (this.platform.is('android')) {
|
||||
// In Android we ignore the keyboard height because it is not part of the web view.
|
||||
height = this.domUtils.getContentHeight(this.content) - this.getSurroundingHeight(this.element);
|
||||
} else if (this.platform.is('ios') && this.kbHeight > 0) {
|
||||
// Keyboard open in iOS.
|
||||
// In this case, the header disappears or is scrollable, so we need to adjust the calculations.
|
||||
} else if (this.platform.is('ios') && this.kbHeight > 0 && this.platform.version().major < 12) {
|
||||
// Keyboard open in iOS 11 or previous. The window height changes when the keyboard is open.
|
||||
height = window.innerHeight - this.getSurroundingHeight(this.element);
|
||||
|
||||
if (this.element.getBoundingClientRect().top < 40) {
|
||||
// In iOS sometimes the editor is placed below the status bar. Move the scroll a bit so it doesn't happen.
|
||||
window.scrollTo(window.scrollX, window.scrollY - 40);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Header is fixed, use the content to calculate the editor height.
|
||||
height = this.domUtils.getContentHeight(this.content) - this.kbHeight - this.getSurroundingHeight(this.element);
|
||||
|
||||
}
|
||||
|
||||
if (height > this.minHeight) {
|
||||
|
@ -549,12 +550,14 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
}
|
||||
|
||||
/**
|
||||
* Hide the toolbar.
|
||||
* Hide the toolbar in phone mode.
|
||||
*/
|
||||
hideToolbar($event: any): void {
|
||||
this.stopBubble($event);
|
||||
|
||||
this.toolbarHidden = true;
|
||||
if (this.isPhone) {
|
||||
this.toolbarHidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -661,7 +661,6 @@ export class CoreCourseHelperProvider {
|
|||
|
||||
const mainFile = files[0],
|
||||
fileUrl = this.fileHelper.getFileUrl(mainFile),
|
||||
timemodified = this.fileHelper.getFileTimemodified(mainFile),
|
||||
result = {
|
||||
fixedUrl: undefined,
|
||||
path: undefined,
|
||||
|
@ -678,48 +677,23 @@ export class CoreCourseHelperProvider {
|
|||
return this.filepoolProvider.getPackageStatus(siteId, component, componentId).then((status) => {
|
||||
result.status = status;
|
||||
|
||||
const isWifi = this.appProvider.isWifi(),
|
||||
isOnline = this.appProvider.isOnline();
|
||||
|
||||
if (status === CoreConstants.DOWNLOADED) {
|
||||
// Get the local file URL.
|
||||
return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl).catch((error) => {
|
||||
// File not found, mark the module as not downloaded and reject.
|
||||
// File not found, mark the module as not downloaded and try again.
|
||||
return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.NOT_DOWNLOADED, component,
|
||||
componentId).then(() => {
|
||||
|
||||
return Promise.reject(error);
|
||||
return this.downloadModuleWithMainFile(module, courseId, fixedUrl, files, status, component,
|
||||
componentId, siteId);
|
||||
});
|
||||
});
|
||||
} else if (status === CoreConstants.DOWNLOADING && !this.appProvider.isDesktop()) {
|
||||
// Return the online URL.
|
||||
return fixedUrl;
|
||||
} else {
|
||||
if (!isOnline && status === CoreConstants.NOT_DOWNLOADED) {
|
||||
// Not downloaded and we're offline, reject.
|
||||
return Promise.reject(this.translate.instant('core.networkerrormsg'));
|
||||
}
|
||||
|
||||
return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, mainFile.filesize).then(() => {
|
||||
// Download and then return the local URL.
|
||||
return this.downloadModule(module, courseId, component, componentId, files, siteId).then(() => {
|
||||
return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl);
|
||||
});
|
||||
}, () => {
|
||||
// Start the download if in wifi, but return the URL right away so the file is opened.
|
||||
if (isWifi) {
|
||||
this.downloadModule(module, courseId, component, componentId, files, siteId);
|
||||
}
|
||||
|
||||
if (!this.fileHelper.isStateDownloaded(status) || isOnline) {
|
||||
// Not downloaded or online, return the online URL.
|
||||
return fixedUrl;
|
||||
} else {
|
||||
// Outdated but offline, so we return the local URL. Use getUrlByUrl so it's added to the queue.
|
||||
return this.filepoolProvider.getUrlByUrl(siteId, fileUrl, component, componentId, timemodified,
|
||||
false, false, mainFile);
|
||||
}
|
||||
});
|
||||
return this.downloadModuleWithMainFile(module, courseId, fixedUrl, files, status, component, componentId,
|
||||
siteId);
|
||||
}
|
||||
}).then((path) => {
|
||||
result.path = path;
|
||||
|
@ -735,6 +709,55 @@ export class CoreCourseHelperProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to download a module that has a main file and return the local file's path and other info.
|
||||
* This is meant for modules like mod_resource.
|
||||
*
|
||||
* @param module The module to download.
|
||||
* @param courseId The course ID of the module.
|
||||
* @param fixedUrl Main file's fixed URL.
|
||||
* @param files List of files of the module.
|
||||
* @param status The package status.
|
||||
* @param component The component to link the files to.
|
||||
* @param componentId An ID to use in conjunction with the component.
|
||||
* @param siteId The site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected downloadModuleWithMainFile(module: any, courseId: number, fixedUrl: string, files: any[], status: string,
|
||||
component?: string, componentId?: string | number, siteId?: string): Promise<string> {
|
||||
|
||||
const isOnline = this.appProvider.isOnline();
|
||||
const mainFile = files[0];
|
||||
const fileUrl = this.fileHelper.getFileUrl(mainFile);
|
||||
const timemodified = this.fileHelper.getFileTimemodified(mainFile);
|
||||
|
||||
if (!isOnline && status === CoreConstants.NOT_DOWNLOADED) {
|
||||
// Not downloaded and we're offline, reject.
|
||||
return Promise.reject(this.translate.instant('core.networkerrormsg'));
|
||||
}
|
||||
|
||||
return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, mainFile.filesize).then(() => {
|
||||
// Download and then return the local URL.
|
||||
return this.downloadModule(module, courseId, component, componentId, files, siteId).then(() => {
|
||||
return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl);
|
||||
});
|
||||
}, () => {
|
||||
// Start the download if in wifi, but return the URL right away so the file is opened.
|
||||
if (this.appProvider.isWifi()) {
|
||||
this.downloadModule(module, courseId, component, componentId, files, siteId);
|
||||
}
|
||||
|
||||
if (!this.fileHelper.isStateDownloaded(status) || isOnline) {
|
||||
// Not downloaded or online, return the online URL.
|
||||
return fixedUrl;
|
||||
} else {
|
||||
// Outdated but offline, so we return the local URL. Use getUrlByUrl so it's added to the queue.
|
||||
return this.filepoolProvider.getUrlByUrl(siteId, fileUrl, component, componentId, timemodified,
|
||||
false, false, mainFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to download a module.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue