MOBILE-2877 comments: Sync comments views

main
Pau Ferrer Ocaña 2019-11-28 12:51:18 +01:00
parent 9f51f547c4
commit 85b4395909
5 changed files with 192 additions and 74 deletions

View File

@ -44,9 +44,12 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy {
protected updateSiteObserver; protected updateSiteObserver;
protected refreshCommentsObserver; protected refreshCommentsObserver;
protected commentsCountObserver;
constructor(private navCtrl: NavController, private commentsProvider: CoreCommentsProvider, constructor(private navCtrl: NavController,
sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, private commentsProvider: CoreCommentsProvider,
sitesProvider: CoreSitesProvider,
eventsProvider: CoreEventsProvider,
@Optional() private svComponent: CoreSplitViewComponent) { @Optional() private svComponent: CoreSplitViewComponent) {
this.onLoading = new EventEmitter<boolean>(); this.onLoading = new EventEmitter<boolean>();
@ -76,6 +79,19 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy {
}); });
} }
}, sitesProvider.getCurrentSiteId()); }, sitesProvider.getCurrentSiteId());
// Refresh comments count if event received.
this.commentsCountObserver = eventsProvider.on(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, (data) => {
// Verify these comments need to be updated.
if (this.undefinedOrEqual(data, 'contextLevel') && this.undefinedOrEqual(data, 'instanceId') &&
this.undefinedOrEqual(data, 'component') && this.undefinedOrEqual(data, 'itemId') &&
this.undefinedOrEqual(data, 'area') && !this.countError) {
if (!this.commentsCount.endsWith('+')) {
// Parse and unparse string.
this.commentsCount = parseInt(this.commentsCount, 10) + data.countChange + '';
}
}
}, sitesProvider.getCurrentSiteId());
} }
/** /**
@ -167,6 +183,7 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy {
ngOnDestroy(): void { ngOnDestroy(): void {
this.updateSiteObserver && this.updateSiteObserver.off(); this.updateSiteObserver && this.updateSiteObserver.off();
this.refreshCommentsObserver && this.refreshCommentsObserver.off(); this.refreshCommentsObserver && this.refreshCommentsObserver.off();
this.commentsCountObserver && this.commentsCountObserver.off();
} }
/** /**

View File

@ -1,5 +1,5 @@
<core-loading *ngIf="!disabled" [hideUntil]="commentsLoaded || !displaySpinner"> <core-loading *ngIf="!disabled" [hideUntil]="commentsLoaded || !displaySpinner">
<div (click)="openComments($event)" *ngIf="!countError" [class.core-comments-clickable]="!disabled"> <div *ngIf="!countError" (click)="openComments($event)" [class.core-comments-clickable]="!disabled">
{{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }} {{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
</div> </div>
<div *ngIf="countError"> <div *ngIf="countError">

View File

@ -63,11 +63,18 @@ export class CoreCommentsViewerPage implements OnDestroy {
protected syncObserver: any; protected syncObserver: any;
protected currentUser: any; protected currentUser: any;
constructor(navParams: NavParams, private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider, constructor(navParams: NavParams,
private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private modalCtrl: ModalController, protected sitesProvider: CoreSitesProvider,
private commentsProvider: CoreCommentsProvider, private offlineComments: CoreCommentsOfflineProvider, protected userProvider: CoreUserProvider,
eventsProvider: CoreEventsProvider, private commentsSync: CoreCommentsSyncProvider, protected domUtils: CoreDomUtilsProvider,
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { protected translate: TranslateService,
protected modalCtrl: ModalController,
protected commentsProvider: CoreCommentsProvider,
protected offlineComments: CoreCommentsOfflineProvider,
protected eventsProvider: CoreEventsProvider,
protected commentsSync: CoreCommentsSyncProvider,
protected textUtils: CoreTextUtilsProvider,
protected timeUtils: CoreTimeUtilsProvider) {
this.contextLevel = navParams.get('contextLevel'); this.contextLevel = navParams.get('contextLevel');
this.instanceId = navParams.get('instanceId'); this.instanceId = navParams.get('instanceId');
@ -127,31 +134,8 @@ export class CoreCommentsViewerPage implements OnDestroy {
return promise.catch(() => { return promise.catch(() => {
// Ignore errors. // Ignore errors.
}).then(() => { }).then(() => {
return this.offlineComments.getComment(this.contextLevel, this.instanceId, this.componentName, this.itemId, return this.loadOfflineData();
this.area).then((offlineComment) => { }).then(() => {
this.offlineComment = offlineComment;
if (offlineComment && !this.currentUser) {
return this.userProvider.getProfile(this.currentUserId, undefined, true).then((user) => {
this.currentUser = user;
this.offlineComment.profileimageurl = user.profileimageurl;
this.offlineComment.fullname = user.fullname;
this.offlineComment.userid = user.id;
}).catch(() => {
// Ignore errors.
});
} else if (offlineComment) {
this.offlineComment.profileimageurl = this.currentUser.profileimageurl;
this.offlineComment.fullname = this.currentUser.fullname;
this.offlineComment.userid = this.currentUser.id;
}
return this.offlineComments.getDeletedComments(this.contextLevel, this.instanceId, this.componentName, this.itemId,
this.area);
});
}).then((deletedComments) => {
this.hasOffline = !!this.offlineComment || deletedComments.length > 0;
// Get comments data. // Get comments data.
return this.commentsProvider.getComments(this.contextLevel, this.instanceId, this.componentName, this.itemId, return this.commentsProvider.getComments(this.contextLevel, this.instanceId, this.componentName, this.itemId,
this.area, this.page).then((response) => { this.area, this.page).then((response) => {
@ -165,30 +149,10 @@ export class CoreCommentsViewerPage implements OnDestroy {
this.canLoadMore = response.comments.length > 0 && response.comments.length >= CoreCommentsProvider.pageSize; this.canLoadMore = response.comments.length > 0 && response.comments.length >= CoreCommentsProvider.pageSize;
} }
return Promise.all(comments.map((comment) => { return Promise.all(comments.map((comment) => this.loadCommentProfile(comment)));
// Get the user profile image.
return this.userProvider.getProfile(comment.userid, undefined, true).then((user) => {
comment.profileimageurl = user.profileimageurl;
return comment;
}).catch(() => {
// Ignore errors.
return comment;
});
}));
}).then((comments) => { }).then((comments) => {
this.comments = this.comments.concat(comments); this.comments = this.comments.concat(comments);
deletedComments && deletedComments.forEach((deletedComment) => {
const comment = this.comments.find((comment) => {
return comment.id == deletedComment.commentid;
});
if (comment) {
comment.deleted = deletedComment.deleted;
}
});
this.canDeleteComments = this.addDeleteCommentsAvailable && (this.hasOffline || this.comments.some((comment) => { this.canDeleteComments = this.addDeleteCommentsAvailable && (this.hasOffline || this.comments.some((comment) => {
return !!comment.delete; return !!comment.delete;
})); }));
@ -231,11 +195,11 @@ export class CoreCommentsViewerPage implements OnDestroy {
* @return Resolved when done. * @return Resolved when done.
*/ */
refreshComments(showErrors: boolean, refresher?: any): Promise<any> { refreshComments(showErrors: boolean, refresher?: any): Promise<any> {
this.commentsLoaded = false;
this.refreshIcon = 'spinner'; this.refreshIcon = 'spinner';
this.syncIcon = 'spinner'; this.syncIcon = 'spinner';
return this.commentsProvider.invalidateCommentsData(this.contextLevel, this.instanceId, this.componentName, return this.invalidateComments().finally(() => {
this.itemId, this.area).finally(() => {
this.page = 0; this.page = 0;
this.comments = []; this.comments = [];
@ -297,10 +261,25 @@ export class CoreCommentsViewerPage implements OnDestroy {
const modal = this.modalCtrl.create('CoreCommentsAddPage', params); const modal = this.modalCtrl.create('CoreCommentsAddPage', params);
modal.onDidDismiss((data) => { modal.onDidDismiss((data) => {
if (data && data.comments) { if (data && data.comments) {
this.comments = data.comments.concat(this.comments); this.invalidateComments();
return Promise.all(data.comments.map((comment) => this.loadCommentProfile(comment))).then((addedComments) => {
// Add the comment to the top.
this.comments = addedComments.concat(this.comments);
this.canDeleteComments = this.addDeleteCommentsAvailable; this.canDeleteComments = this.addDeleteCommentsAvailable;
this.eventsProvider.trigger(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, {
contextLevel: this.contextLevel,
instanceId: this.instanceId,
component: this.componentName,
itemId: this.itemId,
area: this.area,
countChange: addedComments.length,
}, this.sitesProvider.getCurrentSiteId());
});
} else if (data && !data.comments) { } else if (data && !data.comments) {
this.fetchComments(false); // Comments added in offline mode.
return this.loadOfflineData();
} }
}); });
modal.present(); modal.present();
@ -310,26 +289,45 @@ export class CoreCommentsViewerPage implements OnDestroy {
* Delete a comment. * Delete a comment.
* *
* @param e Click event. * @param e Click event.
* @param comment Comment to delete. * @param deleteComment Comment to delete.
*/ */
deleteComment(e: Event, comment: any): void { deleteComment(e: Event, deleteComment: any): void {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const time = this.timeUtils.userDate((comment.lastmodified || comment.timecreated) * 1000, 'core.strftimerecentfull'); const time = this.timeUtils.userDate((deleteComment.lastmodified || deleteComment.timecreated) * 1000,
'core.strftimerecentfull');
comment.contextlevel = this.contextLevel; deleteComment.contextlevel = this.contextLevel;
comment.instanceid = this.instanceId; deleteComment.instanceid = this.instanceId;
comment.component = this.componentName; deleteComment.component = this.componentName;
comment.itemid = this.itemId; deleteComment.itemid = this.itemId;
comment.area = this.area; deleteComment.area = this.area;
this.domUtils.showDeleteConfirm('core.comments.deletecommentbyon', {$a: this.domUtils.showDeleteConfirm('core.comments.deletecommentbyon', {$a:
{ user: comment.fullname || '', time: time } }).then(() => { { user: deleteComment.fullname || '', time: time } }).then(() => {
this.commentsProvider.deleteComment(comment).then(() => { this.commentsProvider.deleteComment(deleteComment).then((deletedOnline) => {
this.showDelete = false; this.showDelete = false;
this.refreshComments(true); if (deletedOnline) {
const index = this.comments.findIndex((comment) => comment.id == deleteComment.id);
if (index >= 0) {
this.comments.splice(index, 1);
}
} else {
this.loadOfflineData();
}
this.eventsProvider.trigger(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, {
contextLevel: this.contextLevel,
instanceId: this.instanceId,
component: this.componentName,
itemId: this.itemId,
area: this.area,
countChange: -1,
}, this.sitesProvider.getCurrentSiteId());
this.invalidateComments();
this.domUtils.showToast('core.comments.eventcommentdeleted', true, 3000); this.domUtils.showToast('core.comments.eventcommentdeleted', true, 3000);
}).catch((error) => { }).catch((error) => {
@ -340,6 +338,91 @@ export class CoreCommentsViewerPage implements OnDestroy {
}); });
} }
/**
* Invalidate comments.
*
* @return Resolved when done.
*/
protected invalidateComments(): Promise<void> {
return this.commentsProvider.invalidateCommentsData(this.contextLevel, this.instanceId, this.componentName, this.itemId,
this.area);
}
/**
* Loads the profile info onto the comment object.
*
* @param comment Comment object.
* @return Promise resolved with modified comment when done.
*/
protected loadCommentProfile(comment: any): Promise<any> {
// Get the user profile image.
return this.userProvider.getProfile(comment.userid, undefined, true).then((user) => {
comment.profileimageurl = user.profileimageurl;
comment.fullname = user.fullname;
comment.userid = user.id;
return comment;
}).catch(() => {
// Ignore errors.
return comment;
});
}
/**
* Load offline comments.
*
* @return Promise resolved when done.
*/
protected loadOfflineData(): Promise<void> {
const promises = [];
let hasDeletedComments = false;
// Load the only offline comment allowed if any.
promises.push(this.offlineComments.getComment(this.contextLevel, this.instanceId, this.componentName, this.itemId,
this.area).then((offlineComment) => {
if (offlineComment && !this.currentUser) {
offlineComment.userid = this.currentUserId;
this.loadCommentProfile(offlineComment).then((comment) => {
// Save this fields for further requests.
if (comment.fullname) {
this.currentUser = {};
this.currentUser.profileimageurl = comment.profileimageurl;
this.currentUser.fullname = comment.fullname;
this.currentUser.userid = comment.userid;
}
});
} else if (offlineComment) {
offlineComment.profileimageurl = this.currentUser.profileimageurl;
offlineComment.fullname = this.currentUser.fullname;
offlineComment.userid = this.currentUser.id;
}
this.offlineComment = offlineComment;
}));
// Load deleted comments offline.
promises.push(this.offlineComments.getDeletedComments(this.contextLevel, this.instanceId, this.componentName, this.itemId,
this.area).then((deletedComments) => {
hasDeletedComments = deletedComments && deletedComments.length > 0;
hasDeletedComments && deletedComments.forEach((deletedComment) => {
const comment = this.comments.find((comment) => {
return comment.id == deletedComment.commentid;
});
if (comment) {
comment.deleted = deletedComment.deleted;
}
});
}));
return Promise.all(promises).then(() => {
this.hasOffline = !!this.offlineComment || hasDeletedComments;
});
}
/** /**
* Restore a comment. * Restore a comment.
* *

View File

@ -26,6 +26,7 @@ import { CoreCommentsOfflineProvider } from './offline';
export class CoreCommentsProvider { export class CoreCommentsProvider {
static REFRESH_COMMENTS_EVENT = 'core_comments_refresh_comments'; static REFRESH_COMMENTS_EVENT = 'core_comments_refresh_comments';
static COMMENTS_COUNT_CHANGED_EVENT = 'core_comments_count_changed';
protected ROOT_CACHE_KEY = 'mmComments:'; protected ROOT_CACHE_KEY = 'mmComments:';
static pageSize = 1; // At least it will be one. static pageSize = 1; // At least it will be one.
@ -162,15 +163,18 @@ export class CoreCommentsProvider {
* *
* @param comment Comment object to delete. * @param comment Comment object to delete.
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that comments * @return Promise resolved when deleted (with true if deleted in online, false otherwise), rejected otherwise. Promise resolved
* have been deleted, the resolve param can contain errors for comments not deleted. * doesn't mean that comments have been deleted, the resolve param can contain errors for comments not deleted.
*/ */
deleteComment(comment: any, siteId?: string): Promise<void> { deleteComment(comment: any, siteId?: string): Promise<boolean> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Offline comment, just delete it.
if (!comment.id) { if (!comment.id) {
return this.commentsOffline.removeComment(comment.contextlevel, comment.instanceid, comment.component, comment.itemid, return this.commentsOffline.removeComment(comment.contextlevel, comment.instanceid, comment.component, comment.itemid,
comment.area, siteId); comment.area, siteId).then(() => {
return false;
});
} }
// Convenience function to store the action to be synchronized later. // Convenience function to store the action to be synchronized later.

View File

@ -159,6 +159,7 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider {
const errors = [], const errors = [],
promises = [], promises = [],
deleteCommentIds = []; deleteCommentIds = [];
let countChange = 0;
comments.forEach((comment) => { comments.forEach((comment) => {
if (comment.commentid) { if (comment.commentid) {
@ -166,6 +167,8 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider {
} else { } else {
promises.push(this.commentsProvider.addCommentOnline(comment.content, contextLevel, instanceId, component, promises.push(this.commentsProvider.addCommentOnline(comment.content, contextLevel, instanceId, component,
itemId, area, siteId).then((response) => { itemId, area, siteId).then((response) => {
countChange++;
return this.commentsOffline.removeComment(contextLevel, instanceId, component, itemId, area, siteId); return this.commentsOffline.removeComment(contextLevel, instanceId, component, itemId, area, siteId);
})); }));
} }
@ -174,6 +177,8 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider {
if (deleteCommentIds.length > 0) { if (deleteCommentIds.length > 0) {
promises.push(this.commentsProvider.deleteCommentsOnline(deleteCommentIds, contextLevel, instanceId, component, promises.push(this.commentsProvider.deleteCommentsOnline(deleteCommentIds, contextLevel, instanceId, component,
itemId, area, siteId).then((response) => { itemId, area, siteId).then((response) => {
countChange--;
return this.commentsOffline.removeDeletedComments(contextLevel, instanceId, component, itemId, area, return this.commentsOffline.removeDeletedComments(contextLevel, instanceId, component, itemId, area,
siteId); siteId);
})); }));
@ -181,6 +186,15 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider {
// Send the comments. // Send the comments.
return Promise.all(promises).then(() => { return Promise.all(promises).then(() => {
this.eventsProvider.trigger(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, {
contextLevel: contextLevel,
instanceId: instanceId,
component: component,
itemId: itemId,
area: area,
countChange: countChange,
}, this.sitesProvider.getCurrentSiteId());
// Fetch the comments from server to be sure they're up to date. // Fetch the comments from server to be sure they're up to date.
return this.commentsProvider.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId) return this.commentsProvider.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId)
.then(() => { .then(() => {