Merge pull request #2546 from crazyserver/MOBILE-3519-2

MOBILE-3519 forum: Fix offline replies new format
main
Juan Leyva 2020-09-25 14:58:26 +02:00 committed by GitHub
commit dab20c896a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 71 additions and 37 deletions

View File

@ -1,12 +1,12 @@
<core-loading [hideUntil]="loaded" class="core-loading-center"> <core-loading [hideUntil]="loaded" class="core-loading-center">
<ion-item text-wrap (click)="editPost()" *ngIf="canEdit"> <ion-item text-wrap (click)="editPost()" *ngIf="offlinePost || (canEdit && isOnline)">
<ion-icon name="create" item-start></ion-icon> <ion-icon name="create" item-start></ion-icon>
<h2>{{ 'addon.mod_forum.edit' | translate }}</h2> <h2>{{ 'addon.mod_forum.edit' | translate }}</h2>
</ion-item> </ion-item>
<ion-item text-wrap (click)="deletePost()" *ngIf="canDelete"> <ion-item text-wrap (click)="deletePost()" *ngIf="offlinePost || (canDelete && isOnline)">
<ion-icon name="trash" item-start></ion-icon> <ion-icon name="trash" item-start></ion-icon>
<h2 *ngIf="post.id">{{ 'addon.mod_forum.delete' | translate }}</h2> <h2 *ngIf="!offlinePost">{{ 'addon.mod_forum.delete' | translate }}</h2>
<h2 *ngIf="!post.id">{{ 'core.discard' | translate }}</h2> <h2 *ngIf="offlinePost">{{ 'core.discard' | translate }}</h2>
</ion-item> </ion-item>
<ion-item text-wrap (click)="dismiss()" *ngIf="wordCount"> <ion-item text-wrap (click)="dismiss()" *ngIf="wordCount">
<h2>{{ 'core.numwords' | translate: {'$a': wordCount} }}</h2> <h2>{{ 'core.numwords' | translate: {'$a': wordCount} }}</h2>

View File

@ -12,12 +12,14 @@
// 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, OnInit } from '@angular/core'; import { Component, OnInit, NgZone } from '@angular/core';
import { NavParams, ViewController } from 'ionic-angular'; import { NavParams, ViewController } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider, CoreSitesReadingStrategy } from '@providers/sites'; import { CoreSitesProvider, CoreSitesReadingStrategy } from '@providers/sites';
import { CoreSite } from '@classes/site'; import { CoreSite } from '@classes/site';
import { AddonModForumProvider } from '../../providers/forum'; import { AddonModForumProvider } from '../../providers/forum';
import { CoreApp } from '@providers/app';
import { Network } from '@ionic-native/network';
/** /**
* This component is meant to display a popover with the post options. * This component is meant to display a popover with the post options.
@ -34,10 +36,15 @@ export class AddonForumPostOptionsMenuComponent implements OnInit {
canDelete = false; canDelete = false;
loaded = false; loaded = false;
url: string; url: string;
isOnline: boolean;
offlinePost: boolean;
protected cmId: number; protected cmId: number;
protected onlineObserver: any;
constructor(navParams: NavParams, constructor(navParams: NavParams,
network: Network,
zone: NgZone,
protected viewCtrl: ViewController, protected viewCtrl: ViewController,
protected domUtils: CoreDomUtilsProvider, protected domUtils: CoreDomUtilsProvider,
protected forumProvider: AddonModForumProvider, protected forumProvider: AddonModForumProvider,
@ -45,20 +52,28 @@ export class AddonForumPostOptionsMenuComponent implements OnInit {
this.post = navParams.get('post'); this.post = navParams.get('post');
this.forumId = navParams.get('forumId'); this.forumId = navParams.get('forumId');
this.cmId = navParams.get('cmId'); this.cmId = navParams.get('cmId');
this.isOnline = CoreApp.instance.isOnline();
this.onlineObserver = network.onchange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
zone.run(() => {
this.isOnline = CoreApp.instance.isOnline();
});
});
} }
/** /**
* Component being initialized. * Component being initialized.
*/ */
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
if (this.post.id) { if (this.post.id > 0) {
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.discussionid}, 'p' + this.post.id);
this.offlinePost = false;
} else { } else {
// Offline post, you can edit or discard the post. // Offline post, you can edit or discard the post.
this.canEdit = true;
this.canDelete = true;
this.loaded = true; this.loaded = true;
this.offlinePost = true;
return; return;
} }
@ -98,7 +113,7 @@ export class AddonForumPostOptionsMenuComponent implements OnInit {
* Delete a post. * Delete a post.
*/ */
deletePost(): void { deletePost(): void {
if (this.post.id) { if (!this.offlinePost) {
this.viewCtrl.dismiss({action: 'delete'}); this.viewCtrl.dismiss({action: 'delete'});
} else { } else {
this.viewCtrl.dismiss({action: 'deleteoffline'}); this.viewCtrl.dismiss({action: 'deleteoffline'});
@ -109,10 +124,17 @@ export class AddonForumPostOptionsMenuComponent implements OnInit {
* Edit a post. * Edit a post.
*/ */
editPost(): void { editPost(): void {
if (this.post.id) { if (!this.offlinePost) {
this.viewCtrl.dismiss({action: 'edit'}); this.viewCtrl.dismiss({action: 'edit'});
} else { } else {
this.viewCtrl.dismiss({action: 'editoffline'}); this.viewCtrl.dismiss({action: 'editoffline'});
} }
} }
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.onlineObserver && this.onlineObserver.unsubscribe();
}
} }

View File

@ -50,14 +50,14 @@
<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-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.capabilities.reply && !post.isprivatereply" class="addon-forum-reply-button"> <ion-item no-padding text-end *ngIf="post.id > 0 && 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.parentid)" #replyFormEl> <form ion-list [id]="'addon-forum-reply-edit-form-' + uniqueId" *ngIf="(post.id > 0 && !replyData.isEditing && replyData.replyingTo == post.id) || (post.id <=0 && 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>

View File

@ -93,7 +93,7 @@ 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.parentid; this.uniqueId = this.post.id > 0 ? '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 ||
@ -102,7 +102,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
this.defaultReplySubject = this.post.replysubject || ((this.post.subject.startsWith('Re: ') || this.defaultReplySubject = this.post.replysubject || ((this.post.subject.startsWith('Re: ') ||
this.post.subject.startsWith(reTranslated)) ? 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 < 0 || (this.forumProvider.isGetDiscussionPostAvailable() &&
(this.forumProvider.isDeletePostAvailable() || this.forumProvider.isUpdatePostAvailable())); (this.forumProvider.isDeletePostAvailable() || this.forumProvider.isUpdatePostAvailable()));
} }

View File

@ -360,7 +360,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
offlineReplies.push(reply); offlineReplies.push(reply);
// Disable reply of the parent. Reply in offline to the same post is not allowed, edit instead. // Disable reply of the parent. Reply in offline to the same post is not allowed, edit instead.
posts[reply.parent].canreply = false; posts[reply.parentid].capabilities.reply = false;
})); }));
}); });

View File

@ -589,8 +589,13 @@ export class AddonModForumProvider {
sortDiscussionPosts(posts: any[], direction: string): void { sortDiscussionPosts(posts: any[], direction: string): void {
// @todo: Check children when sorting. // @todo: Check children when sorting.
posts.sort((a, b) => { posts.sort((a, b) => {
a = parseInt(a.created, 10); a = parseInt(a.timecreated, 10) || 0;
b = parseInt(b.created, 10); b = parseInt(b.timecreated, 10) || 0;
if (a == 0 || b == 0) {
// Leave 0 at the end.
return b - a;
}
if (direction == 'ASC') { if (direction == 'ASC') {
return a - b; return a - b;
} else { } else {

View File

@ -161,24 +161,23 @@ export class AddonModForumHelperProvider {
*/ */
convertOfflineReplyToOnline(offlineReply: any, siteId?: string): Promise<any> { convertOfflineReplyToOnline(offlineReply: any, siteId?: string): Promise<any> {
const reply: any = { const reply: any = {
attachments: [], id: -offlineReply.timecreated,
canreply: false, discussionid: offlineReply.discussionid,
children: [], parentid: offlineReply.postid,
created: offlineReply.timecreated, hasparent: !!offlineReply.postid,
discussion: offlineReply.discussionid, author: {
id: false, id: offlineReply.userid,
mailed: 0, },
mailnow: 0, timecreated: false,
message: offlineReply.message,
messageformat: 1,
messagetrust: 0,
modified: false,
parent: offlineReply.postid,
postread: false,
subject: offlineReply.subject, subject: offlineReply.subject,
totalscore: 0, message: offlineReply.message,
userid: offlineReply.userid, attachments: [],
isprivatereply: offlineReply.options && offlineReply.options.private capabilities: {
reply: false,
},
unread: false,
isprivatereply: offlineReply.options && offlineReply.options.private,
tags: null
}, },
promises = []; promises = [];
@ -187,7 +186,7 @@ export class AddonModForumHelperProvider {
reply.attachments = offlineReply.options.attachmentsid.online || []; reply.attachments = offlineReply.options.attachmentsid.online || [];
if (offlineReply.options.attachmentsid.offline) { if (offlineReply.options.attachmentsid.offline) {
promises.push(this.getReplyStoredFiles(offlineReply.forumid, reply.parent, siteId, reply.userid) promises.push(this.getReplyStoredFiles(offlineReply.forumid, reply.parentid, siteId, offlineReply.userid)
.then((files) => { .then((files) => {
reply.attachments = reply.attachments.concat(files); reply.attachments = reply.attachments.concat(files);
})); }));
@ -196,8 +195,8 @@ export class AddonModForumHelperProvider {
// Get user data. // Get user data.
promises.push(this.userProvider.getProfile(offlineReply.userid, offlineReply.courseid, true).then((user) => { promises.push(this.userProvider.getProfile(offlineReply.userid, offlineReply.courseid, true).then((user) => {
reply.userfullname = user.fullname; reply.author.fullname = user.fullname;
reply.userpictureurl = user.profileimageurl; reply.author.urls = { profileimage: user.profileimageurl };
}).catch(() => { }).catch(() => {
// Ignore errors. // Ignore errors.
})); }));

View File

@ -581,6 +581,10 @@ export class CoreUtilsProvider {
parent = node[parentFieldName]; parent = node[parentFieldName];
node.children = []; node.children = [];
if (!id || !parent) {
this.logger.error(`Node with incorrect ${idFieldName}:${id} or ${parentFieldName}:${parent} found on formatTree`);
}
// Use map to look-up the parents. // Use map to look-up the parents.
map[id] = index; map[id] = index;
if (parent != rootParentId) { if (parent != rootParentId) {
@ -597,12 +601,16 @@ export class CoreUtilsProvider {
mapDepth[id] = mapDepth[parent]; mapDepth[id] = mapDepth[parent];
// Change the parent to be the one that is two levels up the hierarchy. // Change the parent to be the one that is two levels up the hierarchy.
node.parent = parentOfParent; node.parent = parentOfParent;
} else {
this.logger.error(`Node parent of parent:${parentOfParent} not found on formatTree`);
} }
} else { } else {
parentNode.children.push(node); parentNode.children.push(node);
// Increase the depth level. // Increase the depth level.
mapDepth[id] = mapDepth[parent] + 1; mapDepth[id] = mapDepth[parent] + 1;
} }
} else {
this.logger.error(`Node parent:${parent} not found on formatTree`);
} }
} else { } else {
tree.push(node); tree.push(node);