Merge pull request #2541 from crazyserver/MOBILE-3519

Mobile 3519
main
Juan Leyva 2020-09-22 14:12:42 +02:00 committed by GitHub
commit e4b2e8b3c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 152 additions and 75 deletions

View File

@ -133,7 +133,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
} }
if (typeof data.deleted != 'undefined' && data.deleted) { if (typeof data.deleted != 'undefined' && data.deleted) {
if (data.post.parent == 0 && this.splitviewCtrl && this.splitviewCtrl.isOn()) { if (data.post.parentid == 0 && this.splitviewCtrl && this.splitviewCtrl.isOn()) {
// Discussion deleted, clear details page. // Discussion deleted, clear details page.
this.splitviewCtrl.emptyDetails(); this.splitviewCtrl.emptyDetails();
} }

View File

@ -47,30 +47,38 @@ export class AddonForumPostOptionsMenuComponent implements OnInit {
/** /**
* Component being initialized. * Component being initialized.
*/ */
ngOnInit(): void { async ngOnInit(): Promise<void> {
if (this.forumId) { if (this.post.id) {
if (this.post.id) { const site: CoreSite = this.sitesProvider.getCurrentSite();
const site: CoreSite = this.sitesProvider.getCurrentSite(); this.url = site.createSiteUrl('/mod/forum/discuss.php', {d: this.post.discussionid}, 'p' + this.post.id);
this.url = site.createSiteUrl('/mod/forum/discuss.php', {d: this.post.discussion}, 'p' + this.post.id);
this.forumProvider.getDiscussionPost(this.forumId, this.post.discussion, this.post.id, true).then((post) => {
this.canDelete = post.capabilities.delete && this.forumProvider.isDeletePostAvailable();
this.canEdit = post.capabilities.edit && this.forumProvider.isUpdatePostAvailable();
this.wordCount = post.wordcount;
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error getting discussion post.');
}).finally(() => {
this.loaded = true;
});
} else {
// Offline post, you can edit or discard the post.
this.canEdit = true;
this.canDelete = true;
this.loaded = true;
}
} else { } else {
// Offline post, you can edit or discard the post.
this.canEdit = true;
this.canDelete = true;
this.loaded = true; this.loaded = true;
return;
} }
if (typeof this.post.capabilities.delete == 'undefined') {
if (this.forumId) {
try {
this.post =
await this.forumProvider.getDiscussionPost(this.forumId, this.post.discussionid, this.post.id, true);
} catch (error) {
this.domUtils.showErrorModalDefault(error, 'Error getting discussion post.');
}
} else {
this.loaded = true;
return;
}
}
this.canDelete = this.post.capabilities.delete && this.forumProvider.isDeletePostAvailable();
this.canEdit = this.post.capabilities.edit && this.forumProvider.isUpdatePostAvailable();
this.wordCount = this.post.haswordcount && this.post.wordcount;
this.loaded = true;
} }
/** /**

View File

@ -3,11 +3,11 @@
<ion-item text-wrap> <ion-item text-wrap>
<div class="addon-mod-forum-post-title" *ngIf="displaySubject"> <div class="addon-mod-forum-post-title" *ngIf="displaySubject">
<h2 text-wrap> <h2 text-wrap>
<core-icon name="fa-map-pin" *ngIf="post.parent == 0 && post.pinned"></core-icon> <core-icon name="fa-map-pin" *ngIf="discussion && !post.parentid && discussion.pinned"></core-icon>
<core-icon name="fa-star" class="addon-forum-star" *ngIf="post.parent == 0 && !post.pinned && post.starred"></core-icon> <core-icon name="fa-star" class="addon-forum-star" *ngIf="discussion && !post.parentid && !discussion.pinned && discussion.starred"></core-icon>
<core-format-text [text]="post.subject" contextLevel="module" [contextInstanceId]="forum && forum.cmid" [courseId]="courseId"></core-format-text> <core-format-text [text]="post.subject" contextLevel="module" [contextInstanceId]="forum && forum.cmid" [courseId]="courseId"></core-format-text>
</h2> </h2>
<ion-note float-end padding-left text-end *ngIf="trackPosts && !post.postread" [attr.aria-label]="'addon.mod_forum.unread' | translate"> <ion-note float-end padding-left text-end *ngIf="trackPosts && post.unread" [attr.aria-label]="'addon.mod_forum.unread' | translate">
<core-icon name="fa-circle" color="primary"></core-icon> <core-icon name="fa-circle" color="primary"></core-icon>
</ion-note> </ion-note>
<button ion-button icon-only clear color="dark" (click)="showOptionsMenu($event)" *ngIf="optionsMenuEnabled" [attr.aria-label]="('core.displayoptions' | translate)"> <button ion-button icon-only clear color="dark" (click)="showOptionsMenu($event)" *ngIf="optionsMenuEnabled" [attr.aria-label]="('core.displayoptions' | translate)">
@ -15,15 +15,15 @@
</button> </button>
</div> </div>
<div class="addon-mod-forum-post-info"> <div class="addon-mod-forum-post-info">
<ion-avatar *ngIf="post.userfullname" core-user-avatar [user]="post" item-start [courseId]="courseId"></ion-avatar> <ion-avatar *ngIf="post.author && post.author.fullname" core-user-avatar [user]="post.author" item-start [courseId]="courseId"></ion-avatar>
<div class="addon-mod-forum-post-author"> <div class="addon-mod-forum-post-author">
<h3 *ngIf="post.userfullname">{{post.userfullname}}</h3> <h3 *ngIf="post.author && post.author.fullname">{{post.author.fullname}}</h3>
<p *ngIf="post.groupname"><ion-icon name="people"></ion-icon> {{ post.groupname }}</p> <p *ngIf="post.author && post.author.groups"><ng-container *ngFor="let group of post.author.groups"><ion-icon name="people"></ion-icon> {{ group.name }} </ng-container></p>
<p *ngIf="post.modified">{{post.modified * 1000 | coreFormatDate: "strftimerecentfull"}}</p> <p *ngIf="post.timecreated">{{post.timecreated * 1000 | coreFormatDate: "strftimerecentfull"}}</p>
<p *ngIf="!post.modified"><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</p> <p *ngIf="!post.timecreated"><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</p>
</div> </div>
<ng-container *ngIf="!displaySubject"> <ng-container *ngIf="!displaySubject">
<ion-note float-end padding-left text-end *ngIf="trackPosts && !post.postread" [attr.aria-label]="'addon.mod_forum.unread' | translate"> <ion-note float-end padding-left text-end *ngIf="trackPosts && post.unread" [attr.aria-label]="'addon.mod_forum.unread' | translate">
<core-icon name="fa-circle" color="primary"></core-icon> <core-icon name="fa-circle" color="primary"></core-icon>
</ion-note> </ion-note>
<button ion-button icon-only clear color="dark" (click)="showOptionsMenu($event)" *ngIf="optionsMenuEnabled" [attr.aria-label]="('core.displayoptions' | translate)"> <button ion-button icon-only clear color="dark" (click)="showOptionsMenu($event)" *ngIf="optionsMenuEnabled" [attr.aria-label]="('core.displayoptions' | translate)">
@ -33,7 +33,7 @@
</div> </div>
</ion-item> </ion-item>
</ion-card-header> </ion-card-header>
<ion-card-content [attr.padding-top]="post.parent == 0 || null"> <ion-card-content [attr.padding-top]="post.parentid == 0 || null">
<div padding-bottom *ngIf="post.isprivatereply"> <div padding-bottom *ngIf="post.isprivatereply">
<ion-note color="danger">{{ 'addon.mod_forum.postisprivatereply' | translate }}</ion-note> <ion-note color="danger">{{ 'addon.mod_forum.postisprivatereply' | translate }}</ion-note>
</div> </div>
@ -47,17 +47,17 @@
<div item-start>{{ 'core.tag.tags' | translate }}:</div> <div item-start>{{ 'core.tag.tags' | translate }}:</div>
<core-tag-list [tags]="post.tags"></core-tag-list> <core-tag-list [tags]="post.tags"></core-tag-list>
</ion-item> </ion-item>
<core-rating-rate *ngIf="forum && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id" [itemSetId]="discussionId" [courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale" [userId]="post.userid" (onUpdate)="ratingUpdated()"></core-rating-rate> <core-rating-rate *ngIf="forum && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id" [itemSetId]="discussionId" [courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale" [userId]="post.author.id" (onUpdate)="ratingUpdated()"></core-rating-rate>
<core-rating-aggregate *ngIf="forum && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id" [courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale"></core-rating-aggregate> <core-rating-aggregate *ngIf="forum && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id" [courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale"></core-rating-aggregate>
<ion-item no-padding text-end *ngIf="post.id && post.canreply && !post.isprivatereply" class="addon-forum-reply-button"> <ion-item no-padding text-end *ngIf="post.id && post.capabilities.reply && !post.isprivatereply" class="addon-forum-reply-button">
<button ion-button icon-left clear small (click)="showReplyForm()" [attr.aria-controls]="'addon-forum-reply-edit-form-' + uniqueId" [attr.aria-expanded]="replyData.replyingTo === post.id"> <button ion-button icon-left clear small (click)="showReplyForm()" [attr.aria-controls]="'addon-forum-reply-edit-form-' + uniqueId" [attr.aria-expanded]="replyData.replyingTo === post.id">
<core-icon name="fa-reply"></core-icon> {{ 'addon.mod_forum.reply' | translate }} <core-icon name="fa-reply"></core-icon> {{ 'addon.mod_forum.reply' | translate }}
</button> </button>
</ion-item> </ion-item>
</div> </div>
<form ion-list [id]="'addon-forum-reply-edit-form-' + uniqueId" *ngIf="(post.id && !replyData.isEditing && replyData.replyingTo == post.id) || (!post.id && replyData.isEditing && replyData.replyingTo == post.parent)" #replyFormEl> <form ion-list [id]="'addon-forum-reply-edit-form-' + uniqueId" *ngIf="(post.id && !replyData.isEditing && replyData.replyingTo == post.id) || (!post.id && replyData.isEditing && replyData.replyingTo == post.parentid)" #replyFormEl>
<ion-item> <ion-item>
<ion-label stacked>{{ 'addon.mod_forum.subject' | translate }}</ion-label> <ion-label stacked>{{ 'addon.mod_forum.subject' | translate }}</ion-label>
<ion-input type="text" [placeholder]="'addon.mod_forum.subject' | translate" [(ngModel)]="replyData.subject" name="subject"></ion-input> <ion-input type="text" [placeholder]="'addon.mod_forum.subject' | translate" [(ngModel)]="replyData.subject" name="subject"></ion-input>
@ -70,13 +70,15 @@
<ion-label>{{ 'addon.mod_forum.privatereply' | translate }}</ion-label> <ion-label>{{ 'addon.mod_forum.privatereply' | translate }}</ion-label>
<ion-checkbox item-end [(ngModel)]="replyData.isprivatereply" name="isprivatereply"></ion-checkbox> <ion-checkbox item-end [(ngModel)]="replyData.isprivatereply" name="isprivatereply"></ion-checkbox>
</ion-item> </ion-item>
<ion-item-divider text-wrap (click)="toggleAdvanced()" class="core-expandable"> <ng-container *ngIf="forum.id && forum.maxattachments > 0">
<core-icon *ngIf="!advanced" name="fa-caret-right" item-start></core-icon> <ion-item-divider text-wrap (click)="toggleAdvanced()" class="core-expandable">
<core-icon *ngIf="advanced" name="fa-caret-down" item-start></core-icon> <core-icon *ngIf="!advanced" name="fa-caret-right" item-start></core-icon>
{{ 'addon.mod_forum.advanced' | translate }} <core-icon *ngIf="advanced" name="fa-caret-down" item-start></core-icon>
</ion-item-divider> {{ 'addon.mod_forum.advanced' | translate }}
<ng-container *ngIf="advanced"> </ion-item-divider>
<core-attachments *ngIf="forum.id && forum.maxattachments > 0" [files]="replyData.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [component]="component" [componentId]="forum.cmid" [allowOffline]="true"></core-attachments> <ng-container *ngIf="advanced">
<core-attachments [files]="replyData.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [component]="component" [componentId]="forum.cmid" [allowOffline]="true"></core-attachments>
</ng-container>
</ng-container> </ng-container>
<ion-grid> <ion-grid>
<ion-row> <ion-row>

View File

@ -43,6 +43,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
@Input() post: any; // Post. @Input() post: any; // Post.
@Input() courseId: number; // Post's course ID. @Input() courseId: number; // Post's course ID.
@Input() discussionId: number; // Post's' discussion ID. @Input() discussionId: number; // Post's' discussion ID.
@Input() discussion?: any; // Post's' discussion, only for starting posts.
@Input() component: string; // Component this post belong to. @Input() component: string; // Component this post belong to.
@Input() componentId: number; // Component ID. @Input() componentId: number; // Component ID.
@Input() replyData: any; // Object with the new post data. Usually shared between posts. @Input() replyData: any; // Object with the new post data. Usually shared between posts.
@ -92,14 +93,14 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
* Component being initialized. * Component being initialized.
*/ */
ngOnInit(): void { ngOnInit(): void {
this.uniqueId = this.post.id ? 'reply' + this.post.id : 'edit' + this.post.parent; this.uniqueId = this.post.id ? 'reply' + this.post.id : 'edit' + this.post.parentid;
const reTranslated = this.translate.instant('addon.mod_forum.re'); const reTranslated = this.translate.instant('addon.mod_forum.re');
this.displaySubject = !this.parentSubject || this.displaySubject = !this.parentSubject ||
(this.post.subject != this.parentSubject && this.post.subject != `Re: ${this.parentSubject}` && (this.post.subject != this.parentSubject && this.post.subject != `Re: ${this.parentSubject}` &&
this.post.subject != `${reTranslated} ${this.parentSubject}`); this.post.subject != `${reTranslated} ${this.parentSubject}`);
this.defaultReplySubject = (this.post.subject.startsWith('Re: ') || this.post.subject.startsWith(reTranslated)) this.defaultReplySubject = this.post.replysubject || ((this.post.subject.startsWith('Re: ') ||
? this.post.subject : `${reTranslated} ${this.post.subject}`; this.post.subject.startsWith(reTranslated)) ? this.post.subject : `${reTranslated} ${this.post.subject}`);
this.optionsMenuEnabled = !this.post.id || (this.forumProvider.isGetDiscussionPostAvailable() && this.optionsMenuEnabled = !this.post.id || (this.forumProvider.isGetDiscussionPostAvailable() &&
(this.forumProvider.isDeletePostAvailable() || this.forumProvider.isUpdatePostAvailable())); (this.forumProvider.isDeletePostAvailable() || this.forumProvider.isUpdatePostAvailable()));
@ -328,7 +329,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
this.syncId = this.forumSync.getDiscussionSyncId(this.discussionId); this.syncId = this.forumSync.getDiscussionSyncId(this.discussionId);
this.syncProvider.blockOperation(AddonModForumProvider.COMPONENT, this.syncId); this.syncProvider.blockOperation(AddonModForumProvider.COMPONENT, this.syncId);
this.setReplyFormData(this.post.parent, true, this.post.subject, this.post.message, this.post.attachments, this.setReplyFormData(this.post.parentid, true, this.post.subject, this.post.message, this.post.attachments,
this.post.isprivatereply); this.post.isprivatereply);
}).catch(() => { }).catch(() => {
// Cancelled. // Cancelled.
@ -460,9 +461,9 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
this.domUtils.showDeleteConfirm().then(() => { this.domUtils.showDeleteConfirm().then(() => {
const promises = []; const promises = [];
promises.push(this.forumOffline.deleteReply(this.post.parent)); promises.push(this.forumOffline.deleteReply(this.post.parentid));
if (this.forum.id) { if (this.forum.id) {
promises.push(this.forumHelper.deleteReplyStoredFiles(this.forum.id, this.post.parent).catch(() => { promises.push(this.forumHelper.deleteReplyStoredFiles(this.forum.id, this.post.parentid).catch(() => {
// Ignore errors, maybe there are no files. // Ignore errors, maybe there are no files.
})); }));
} }

View File

@ -1,6 +1,6 @@
<ion-header> <ion-header>
<ion-navbar core-back-button> <ion-navbar core-back-button>
<ion-title *ngIf="discussion"><core-format-text [text]="discussion.subject" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId"></core-format-text></ion-title> <ion-title *ngIf="startingPost"><core-format-text [text]="startingPost.subject" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end> <ion-buttons end>
<!-- The context menu will be added in here. --> <!-- The context menu will be added in here. -->
</ion-buttons> </ion-buttons>
@ -41,14 +41,14 @@
<core-icon name="fa-lock"></core-icon> {{ 'addon.mod_forum.discussionlocked' | translate }} <core-icon name="fa-lock"></core-icon> {{ 'addon.mod_forum.discussionlocked' | translate }}
</ion-card> </ion-card>
<div *ngIf="discussion" margin-bottom class="highlight"> <div *ngIf="startingPost" margin-bottom class="highlight">
<addon-mod-forum-post [post]="discussion" [courseId]="courseId" [discussionId]="discussionId" [component]="component" [componentId]="cmId" [replyData]="replyData" [originalData]="originalData" [forum]="forum" [accessInfo]="accessInfo" [trackPosts]="trackPosts" [ratingInfo]="ratingInfo" [leavingPage]="leavingPage" (onPostChange)="postListChanged()"></addon-mod-forum-post> <addon-mod-forum-post [post]="startingPost" [discussion]="discussion" [courseId]="courseId" [discussionId]="discussionId" [component]="component" [componentId]="cmId" [replyData]="replyData" [originalData]="originalData" [forum]="forum" [accessInfo]="accessInfo" [trackPosts]="trackPosts" [ratingInfo]="ratingInfo" [leavingPage]="leavingPage" (onPostChange)="postListChanged()"></addon-mod-forum-post>
</div> </div>
<ion-card *ngIf="sort != 'nested'"> <ion-card *ngIf="sort != 'nested'">
<ng-container *ngFor="let post of posts; first as first"> <ng-container *ngFor="let post of posts; first as first">
<ion-item-divider *ngIf="!first"></ion-item-divider> <ion-item-divider *ngIf="!first"></ion-item-divider>
<addon-mod-forum-post [post]="post" [courseId]="courseId" [discussionId]="discussionId" [component]="component" [componentId]="cmId" [replyData]="replyData" [originalData]="originalData" [parentSubject]="postSubjects[post.parent]" [forum]="forum" [accessInfo]="accessInfo" [trackPosts]="trackPosts" [ratingInfo]="ratingInfo" [leavingPage]="leavingPage" (onPostChange)="postListChanged()"></addon-mod-forum-post> <addon-mod-forum-post [post]="post" [courseId]="courseId" [discussionId]="discussionId" [component]="component" [componentId]="cmId" [replyData]="replyData" [originalData]="originalData" [parentSubject]="postSubjects[post.parentid]" [forum]="forum" [accessInfo]="accessInfo" [trackPosts]="trackPosts" [ratingInfo]="ratingInfo" [leavingPage]="leavingPage" (onPostChange)="postListChanged()"></addon-mod-forum-post>
</ng-container> </ng-container>
</ion-card> </ion-card>
@ -60,7 +60,7 @@
<ng-template #nestedPosts let-post="post"> <ng-template #nestedPosts let-post="post">
<ion-card> <ion-card>
<addon-mod-forum-post [post]="post" [courseId]="courseId" [discussionId]="discussionId" [component]="component" [componentId]="cmId" [replyData]="replyData" [originalData]="originalData" [parentSubject]="postSubjects[post.parent]" [forum]="forum" [accessInfo]="accessInfo" [trackPosts]="trackPosts" [ratingInfo]="ratingInfo" [leavingPage]="leavingPage" (onPostChange)="postListChanged()"></addon-mod-forum-post> <addon-mod-forum-post [post]="post" [courseId]="courseId" [discussionId]="discussionId" [component]="component" [componentId]="cmId" [replyData]="replyData" [originalData]="originalData" [parentSubject]="postSubjects[post.parentid]" [forum]="forum" [accessInfo]="accessInfo" [trackPosts]="trackPosts" [ratingInfo]="ratingInfo" [leavingPage]="leavingPage" (onPostChange)="postListChanged()"></addon-mod-forum-post>
</ion-card> </ion-card>
<div padding-left *ngIf="post.children.length && post.children[0].subject"> <div padding-left *ngIf="post.children.length && post.children[0].subject">
<ng-container *ngFor="let child of post.children"> <ng-container *ngFor="let child of post.children">

View File

@ -52,6 +52,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
forum: any = {}; forum: any = {};
accessInfo: any = {}; accessInfo: any = {};
discussion: any; discussion: any;
startingPost: any;
posts: any[]; posts: any[];
discussionLoaded = false; discussionLoaded = false;
postSubjects: { [id: string]: string }; postSubjects: { [id: string]: string };
@ -253,7 +254,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
} }
if (typeof data.deleted != 'undefined' && data.deleted) { if (typeof data.deleted != 'undefined' && data.deleted) {
if (data.post.parent == 0) { if (!data.post.parentid) {
if (this.svComponent && this.svComponent.isOn()) { if (this.svComponent && this.svComponent.isOn()) {
this.svComponent.emptyDetails(); this.svComponent.emptyDetails();
} else { } else {
@ -331,6 +332,8 @@ export class AddonModForumDiscussionPage implements OnDestroy {
return this.forumProvider.getDiscussionPosts(this.discussionId, this.cmId).then((response) => { return this.forumProvider.getDiscussionPosts(this.discussionId, this.cmId).then((response) => {
onlinePosts = response.posts; onlinePosts = response.posts;
ratingInfo = response.ratinginfo; ratingInfo = response.ratinginfo;
this.courseId = response.courseid || this.courseId;
this.forumId = response.forumid || this.forumId;
}).then(() => { }).then(() => {
// Check if there are responses stored in offline. // Check if there are responses stored in offline.
return this.forumOffline.getDiscussionReplies(this.discussionId).then((replies) => { return this.forumOffline.getDiscussionReplies(this.discussionId).then((replies) => {
@ -341,7 +344,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
const posts = {}; const posts = {};
onlinePosts.forEach((post) => { onlinePosts.forEach((post) => {
posts[post.id] = post; posts[post.id] = post;
hasUnreadPosts = hasUnreadPosts || !post.postread; hasUnreadPosts = hasUnreadPosts || !!post.unread;
}); });
replies.forEach((offlineReply) => { replies.forEach((offlineReply) => {
@ -370,18 +373,15 @@ export class AddonModForumDiscussionPage implements OnDestroy {
}).then(() => { }).then(() => {
let posts = offlineReplies.concat(onlinePosts); let posts = offlineReplies.concat(onlinePosts);
const startingPost = this.forumProvider.extractStartingPost(posts); this.startingPost = this.forumProvider.extractStartingPost(posts);
if (startingPost) {
// Update discussion data from first post.
this.discussion = Object.assign(this.discussion || {}, startingPost);
}
// If sort type is nested, normal sorting is disabled and nested posts will be displayed. // If sort type is nested, normal sorting is disabled and nested posts will be displayed.
if (this.sort == 'nested') { if (this.sort == 'nested') {
// Sort first by creation date to make format tree work. // Sort first by creation date to make format tree work.
this.forumProvider.sortDiscussionPosts(posts, 'ASC'); this.forumProvider.sortDiscussionPosts(posts, 'ASC');
posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion.id); const rootId = this.startingPost ? this.startingPost.id : (this.discussion ? this.discussion.id : 0);
posts = this.utils.formatTree(posts, 'parentid', 'id', rootId);
} else { } else {
// Set default reply subject. // Set default reply subject.
const direction = this.sort == 'flat-newest' ? 'DESC' : 'ASC'; const direction = this.sort == 'flat-newest' ? 'DESC' : 'ASC';
@ -410,7 +410,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
// Just in case the posts were fetched from WS when the cut-off date was not reached but it is now. // Just in case the posts were fetched from WS when the cut-off date was not reached but it is now.
if (this.forumHelper.isCutoffDateReached(forum) && !accessInfo.cancanoverridecutoff) { if (this.forumHelper.isCutoffDateReached(forum) && !accessInfo.cancanoverridecutoff) {
posts.forEach((post) => { posts.forEach((post) => {
post.canreply = false; post.capabilities.reply = false;
}); });
} }
})); }));
@ -424,24 +424,26 @@ export class AddonModForumDiscussionPage implements OnDestroy {
}).catch(() => { }).catch(() => {
// Ignore errors. // Ignore errors.
}).then(() => { }).then(() => {
if (!this.discussion && !this.startingPost) {
if (!this.discussion) {
// The discussion object was not passed as parameter and there is no starting post. Should not happen. // The discussion object was not passed as parameter and there is no starting post. Should not happen.
return Promise.reject('Invalid forum discussion.'); return Promise.reject('Invalid forum discussion.');
} }
if (this.discussion.userfullname && this.discussion.parent == 0 && this.forum.type == 'single') { if (this.startingPost.author && this.forum.type == 'single') {
// Hide author for first post and type single. // Hide author and groups for first post and type single.
this.discussion.userfullname = null; this.startingPost.author.fullname = null;
this.startingPost.author.groups = null;
} }
this.posts = posts; this.posts = posts;
this.ratingInfo = ratingInfo; this.ratingInfo = ratingInfo;
this.postSubjects = this.getAllPosts().reduce((postSubjects, post) => { this.postSubjects = this.getAllPosts().reduce((postSubjects, post) => {
postSubjects[post.id] = post.subject; postSubjects[post.id] = post.subject;
return postSubjects; return postSubjects;
}, { [this.discussion.id]: this.discussion.subject }); }, { [this.startingPost.id]: this.startingPost.subject });
}); });
}).then(() => { }).then(() => {
if (this.forumProvider.isSetPinStateAvailableForSite()) { if (this.forumProvider.isSetPinStateAvailableForSite()) {
@ -746,5 +748,4 @@ export class AddonModForumDiscussionPage implements OnDestroy {
return posts; return posts;
} }
} }

View File

@ -280,7 +280,7 @@ export class AddonModForumProvider {
* @return Starting post or undefined if not found. * @return Starting post or undefined if not found.
*/ */
extractStartingPost(posts: any[]): any { extractStartingPost(posts: any[]): any {
const index = posts.findIndex((post) => post.parent == 0); const index = posts.findIndex((post) => !post.parentid);
return index >= 0 ? posts.splice(index, 1).pop() : undefined; return index >= 0 ? posts.splice(index, 1).pop() : undefined;
} }
@ -305,6 +305,18 @@ export class AddonModForumProvider {
return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_get_discussion_post'); return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_get_discussion_post');
} }
/**
* Returns whether or not getDiscussionPost WS available or not.
*
* @param site Site. If not defined, current site.
* @return If WS is avalaible.
* @since 3.7
*/
isGetDiscussionPostsAvailable(site?: CoreSite): boolean {
return site ? site.wsAvailable('mod_forum_get_discussion_posts') :
this.sitesProvider.wsAvailableInCurrentSite('mod_forum_get_discussion_posts');
}
/** /**
* Returns whether or not deletePost WS available or not. * Returns whether or not deletePost WS available or not.
* *
@ -496,7 +508,43 @@ export class AddonModForumProvider {
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved with forum posts and rating info. * @return Promise resolved with forum posts and rating info.
*/ */
getDiscussionPosts(discussionId: number, cmId: number, siteId?: string): Promise<{posts: any[], ratinginfo?: CoreRatingInfo}> { getDiscussionPosts(discussionId: number, cmId: number, siteId?: string): Promise<{posts: any[], courseid?: number,
forumid?: number, ratinginfo?: CoreRatingInfo}> {
// Convenience function to translate legacy data to new format.
const translateLegacyPostsFormat = (posts: any[]): any[] => {
return posts.map((post) => {
const newPost = {
id: post.id ,
discussionid: post.discussion,
parentid: post.parent,
hasparent: !!post.parent,
author: {
id: post.userid,
fullname: post.userfullname,
urls: { profileimage: post.userpictureurl },
},
timecreated: post.created,
subject: post.subject,
message: post.message,
attachments : post.attachments,
capabilities: {
reply: !!post.canreply,
},
unread: !post.postread,
isprivatereply: !!post.isprivatereply,
tags: post.tags
};
if (post.groupname) {
newPost.author['groups'] = [{name: post.groupname}];
}
return newPost;
});
};
const params = { const params = {
discussionid: discussionId discussionid: discussionId
}; };
@ -507,8 +555,15 @@ export class AddonModForumProvider {
}; };
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
return site.read('mod_forum_get_forum_discussion_posts', params, preSets).then((response) => { const wsName = this.isGetDiscussionPostsAvailable(site) ? 'mod_forum_get_discussion_posts' :
'mod_forum_get_forum_discussion_posts';
return site.read(wsName, params, preSets).then((response) => {
if (response) { if (response) {
if (wsName == 'mod_forum_get_forum_discussion_posts') {
response.posts = translateLegacyPostsFormat(response.posts);
}
this.storeUserData(response.posts); this.storeUserData(response.posts);
return response; return response;
@ -1053,6 +1108,16 @@ export class AddonModForumProvider {
const users = {}; const users = {};
list.forEach((entry) => { list.forEach((entry) => {
if (entry.author) {
const authorId = parseInt(entry.author.id);
if (!isNaN(authorId) && !users[authorId]) {
users[authorId] = {
id: entry.author.id,
fullname: entry.author.fullname,
profileimageurl: entry.author.urls.profileimage
};
}
}
const userId = parseInt(entry.userid); const userId = parseInt(entry.userid);
if (!isNaN(userId) && !users[userId]) { if (!isNaN(userId) && !users[userId]) {
users[userId] = { users[userId] = {

View File

@ -87,7 +87,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
*/ */
protected setFields(): void { protected setFields(): void {
const profileUrl = this.profileUrl || (this.user && (this.user.profileimageurl || this.user.userprofileimageurl || const profileUrl = this.profileUrl || (this.user && (this.user.profileimageurl || this.user.userprofileimageurl ||
this.user.userpictureurl || this.user.profileimageurlsmall)); this.user.userpictureurl || this.user.profileimageurlsmall || (this.user.urls && this.user.urls.profileimage));
if (typeof profileUrl == 'string') { if (typeof profileUrl == 'string') {
this.avatarUrl = profileUrl; this.avatarUrl = profileUrl;

View File

@ -228,7 +228,7 @@ export class CoreEditorOfflineProvider {
if (entry) { if (entry) {
if (entry.pageinstance != pageInstance) { if (entry.pageinstance != pageInstance) {
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` + this.logger.warn(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
`element '${elementId}'`); `element '${elementId}'`);
throw null; throw null;
} }

View File

@ -166,7 +166,7 @@ export class CoreMimetypeUtilsProvider {
if (this.canBeEmbedded(ext)) { if (this.canBeEmbedded(ext)) {
file.embedType = this.getExtensionType(ext); file.embedType = this.getExtensionType(ext);
path = CoreFile.instance.convertFileSrc(path || file.fileurl || (file.toURL && file.toURL())); path = CoreFile.instance.convertFileSrc(path || file.fileurl || file.url || (file.toURL && file.toURL()));
if (file.embedType == 'image') { if (file.embedType == 'image') {
return '<img src="' + path + '">'; return '<img src="' + path + '">';