From 4d8697a6015734520049997f4e8067b086d444a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 26 Aug 2020 12:59:45 +0200 Subject: [PATCH 1/3] MOBILE-3519 forum: Hide advanced section when cannot attach files --- .../components/post/addon-mod-forum-post.html | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/addon/mod/forum/components/post/addon-mod-forum-post.html b/src/addon/mod/forum/components/post/addon-mod-forum-post.html index 10c1f36fc..71f4aaddc 100644 --- a/src/addon/mod/forum/components/post/addon-mod-forum-post.html +++ b/src/addon/mod/forum/components/post/addon-mod-forum-post.html @@ -70,13 +70,15 @@ {{ 'addon.mod_forum.privatereply' | translate }} - - - - {{ 'addon.mod_forum.advanced' | translate }} - - - + + + + + {{ 'addon.mod_forum.advanced' | translate }} + + + + From 6c894a8abee4530f7268272c0df9fe2d16d28aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 31 Aug 2020 12:28:03 +0200 Subject: [PATCH 2/3] MOBILE-3519 forum: Use forumid and courseid from WS --- src/addon/mod/forum/pages/discussion/discussion.ts | 12 +++++++----- src/addon/mod/forum/providers/forum.ts | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index bc5b23206..c8d18d494 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -331,6 +331,8 @@ export class AddonModForumDiscussionPage implements OnDestroy { return this.forumProvider.getDiscussionPosts(this.discussionId, this.cmId).then((response) => { onlinePosts = response.posts; ratingInfo = response.ratinginfo; + this.courseId = response.courseid; + this.forumId = response.forumid; }).then(() => { // Check if there are responses stored in offline. return this.forumOffline.getDiscussionReplies(this.discussionId).then((replies) => { @@ -371,17 +373,13 @@ export class AddonModForumDiscussionPage implements OnDestroy { let posts = offlineReplies.concat(onlinePosts); const 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 (this.sort == 'nested') { // Sort first by creation date to make format tree work. this.forumProvider.sortDiscussionPosts(posts, 'ASC'); - posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion.id); + posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion ? this.discussion.id : startingPost.id); } else { // Set default reply subject. const direction = this.sort == 'flat-newest' ? 'DESC' : 'ASC'; @@ -424,6 +422,10 @@ export class AddonModForumDiscussionPage implements OnDestroy { }).catch(() => { // Ignore errors. }).then(() => { + if (startingPost) { + // Update discussion data from first post. + this.discussion = Object.assign(this.discussion || {}, startingPost); + } if (!this.discussion) { // The discussion object was not passed as parameter and there is no starting post. Should not happen. diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index fa6cebdfc..ff91b9935 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -496,7 +496,8 @@ export class AddonModForumProvider { * @param siteId Site ID. If not defined, current site. * @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}> { const params = { discussionid: discussionId }; From afce5d28b16a025293da9ba732f4774e65e86d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 1 Sep 2020 19:20:39 +0200 Subject: [PATCH 3/3] MOBILE-3519 forum: Use WS mod_forum_get_discussion_posts when available --- src/addon/mod/forum/components/index/index.ts | 2 +- .../post-options-menu/post-options-menu.ts | 50 ++++++++------ .../components/post/addon-mod-forum-post.html | 26 +++---- src/addon/mod/forum/components/post/post.ts | 13 ++-- .../forum/pages/discussion/discussion.html | 10 +-- .../mod/forum/pages/discussion/discussion.ts | 35 +++++----- src/addon/mod/forum/providers/forum.ts | 68 ++++++++++++++++++- src/components/user-avatar/user-avatar.ts | 2 +- src/core/editor/providers/editor-offline.ts | 2 +- src/providers/utils/mimetype.ts | 2 +- 10 files changed, 141 insertions(+), 69 deletions(-) diff --git a/src/addon/mod/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index 34ea7ba6c..5c3147867 100644 --- a/src/addon/mod/forum/components/index/index.ts +++ b/src/addon/mod/forum/components/index/index.ts @@ -133,7 +133,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom } 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. this.splitviewCtrl.emptyDetails(); } diff --git a/src/addon/mod/forum/components/post-options-menu/post-options-menu.ts b/src/addon/mod/forum/components/post-options-menu/post-options-menu.ts index cd0fb7fdf..3479d7154 100644 --- a/src/addon/mod/forum/components/post-options-menu/post-options-menu.ts +++ b/src/addon/mod/forum/components/post-options-menu/post-options-menu.ts @@ -47,30 +47,38 @@ export class AddonForumPostOptionsMenuComponent implements OnInit { /** * Component being initialized. */ - ngOnInit(): void { - if (this.forumId) { - if (this.post.id) { - const site: CoreSite = this.sitesProvider.getCurrentSite(); - 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; - } + async ngOnInit(): Promise { + if (this.post.id) { + const site: CoreSite = this.sitesProvider.getCurrentSite(); + this.url = site.createSiteUrl('/mod/forum/discuss.php', {d: this.post.discussionid}, 'p' + this.post.id); } else { + // Offline post, you can edit or discard the post. + this.canEdit = true; + this.canDelete = 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; } /** diff --git a/src/addon/mod/forum/components/post/addon-mod-forum-post.html b/src/addon/mod/forum/components/post/addon-mod-forum-post.html index 71f4aaddc..c1a08da81 100644 --- a/src/addon/mod/forum/components/post/addon-mod-forum-post.html +++ b/src/addon/mod/forum/components/post/addon-mod-forum-post.html @@ -3,11 +3,11 @@

- - + +

- +
- +
{{ 'addon.mod_forum.postisprivatereply' | translate }}
@@ -47,17 +47,17 @@
{{ 'core.tag.tags' | translate }}:
- + - + -
+ {{ 'addon.mod_forum.subject' | translate }} diff --git a/src/addon/mod/forum/components/post/post.ts b/src/addon/mod/forum/components/post/post.ts index cf4354222..b55449ef5 100644 --- a/src/addon/mod/forum/components/post/post.ts +++ b/src/addon/mod/forum/components/post/post.ts @@ -43,6 +43,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges @Input() post: any; // Post. @Input() courseId: number; // Post's course 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() componentId: number; // Component ID. @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. */ 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'); this.displaySubject = !this.parentSubject || (this.post.subject != this.parentSubject && this.post.subject != `Re: ${this.parentSubject}` && this.post.subject != `${reTranslated} ${this.parentSubject}`); - this.defaultReplySubject = (this.post.subject.startsWith('Re: ') || this.post.subject.startsWith(reTranslated)) - ? this.post.subject : `${reTranslated} ${this.post.subject}`; + this.defaultReplySubject = this.post.replysubject || ((this.post.subject.startsWith('Re: ') || + this.post.subject.startsWith(reTranslated)) ? this.post.subject : `${reTranslated} ${this.post.subject}`); this.optionsMenuEnabled = !this.post.id || (this.forumProvider.isGetDiscussionPostAvailable() && (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.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); }).catch(() => { // Cancelled. @@ -460,9 +461,9 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges this.domUtils.showDeleteConfirm().then(() => { const promises = []; - promises.push(this.forumOffline.deleteReply(this.post.parent)); + promises.push(this.forumOffline.deleteReply(this.post.parentid)); 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. })); } diff --git a/src/addon/mod/forum/pages/discussion/discussion.html b/src/addon/mod/forum/pages/discussion/discussion.html index 2cb0c0e84..bc403cdd9 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.html +++ b/src/addon/mod/forum/pages/discussion/discussion.html @@ -1,6 +1,6 @@ - + @@ -41,14 +41,14 @@ {{ 'addon.mod_forum.discussionlocked' | translate }} -
- +
+
- + @@ -60,7 +60,7 @@ - +
diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index c8d18d494..65fac0147 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -52,6 +52,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { forum: any = {}; accessInfo: any = {}; discussion: any; + startingPost: any; posts: any[]; discussionLoaded = false; postSubjects: { [id: string]: string }; @@ -253,7 +254,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { } if (typeof data.deleted != 'undefined' && data.deleted) { - if (data.post.parent == 0) { + if (!data.post.parentid) { if (this.svComponent && this.svComponent.isOn()) { this.svComponent.emptyDetails(); } else { @@ -331,8 +332,8 @@ export class AddonModForumDiscussionPage implements OnDestroy { return this.forumProvider.getDiscussionPosts(this.discussionId, this.cmId).then((response) => { onlinePosts = response.posts; ratingInfo = response.ratinginfo; - this.courseId = response.courseid; - this.forumId = response.forumid; + this.courseId = response.courseid || this.courseId; + this.forumId = response.forumid || this.forumId; }).then(() => { // Check if there are responses stored in offline. return this.forumOffline.getDiscussionReplies(this.discussionId).then((replies) => { @@ -343,7 +344,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { const posts = {}; onlinePosts.forEach((post) => { posts[post.id] = post; - hasUnreadPosts = hasUnreadPosts || !post.postread; + hasUnreadPosts = hasUnreadPosts || !!post.unread; }); replies.forEach((offlineReply) => { @@ -372,14 +373,15 @@ export class AddonModForumDiscussionPage implements OnDestroy { }).then(() => { let posts = offlineReplies.concat(onlinePosts); - const startingPost = this.forumProvider.extractStartingPost(posts); + this.startingPost = this.forumProvider.extractStartingPost(posts); // If sort type is nested, normal sorting is disabled and nested posts will be displayed. if (this.sort == 'nested') { // Sort first by creation date to make format tree work. this.forumProvider.sortDiscussionPosts(posts, 'ASC'); - posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion ? this.discussion.id : startingPost.id); + const rootId = this.startingPost ? this.startingPost.id : (this.discussion ? this.discussion.id : 0); + posts = this.utils.formatTree(posts, 'parentid', 'id', rootId); } else { // Set default reply subject. const direction = this.sort == 'flat-newest' ? 'DESC' : 'ASC'; @@ -408,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. if (this.forumHelper.isCutoffDateReached(forum) && !accessInfo.cancanoverridecutoff) { posts.forEach((post) => { - post.canreply = false; + post.capabilities.reply = false; }); } })); @@ -422,28 +424,26 @@ export class AddonModForumDiscussionPage implements OnDestroy { }).catch(() => { // Ignore errors. }).then(() => { - if (startingPost) { - // Update discussion data from first post. - this.discussion = Object.assign(this.discussion || {}, startingPost); - } - - if (!this.discussion) { + if (!this.discussion && !this.startingPost) { // The discussion object was not passed as parameter and there is no starting post. Should not happen. return Promise.reject('Invalid forum discussion.'); } - if (this.discussion.userfullname && this.discussion.parent == 0 && this.forum.type == 'single') { - // Hide author for first post and type single. - this.discussion.userfullname = null; + if (this.startingPost.author && this.forum.type == 'single') { + // Hide author and groups for first post and type single. + this.startingPost.author.fullname = null; + this.startingPost.author.groups = null; + } this.posts = posts; this.ratingInfo = ratingInfo; + this.postSubjects = this.getAllPosts().reduce((postSubjects, post) => { postSubjects[post.id] = post.subject; return postSubjects; - }, { [this.discussion.id]: this.discussion.subject }); + }, { [this.startingPost.id]: this.startingPost.subject }); }); }).then(() => { if (this.forumProvider.isSetPinStateAvailableForSite()) { @@ -748,5 +748,4 @@ export class AddonModForumDiscussionPage implements OnDestroy { return posts; } - } diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index ff91b9935..1ac3fdaf7 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -280,7 +280,7 @@ export class AddonModForumProvider { * @return Starting post or undefined if not found. */ 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; } @@ -305,6 +305,18 @@ export class AddonModForumProvider { 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. * @@ -498,6 +510,41 @@ export class AddonModForumProvider { */ 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 = { discussionid: discussionId }; @@ -508,8 +555,15 @@ export class AddonModForumProvider { }; 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 (wsName == 'mod_forum_get_forum_discussion_posts') { + response.posts = translateLegacyPostsFormat(response.posts); + } this.storeUserData(response.posts); return response; @@ -1054,6 +1108,16 @@ export class AddonModForumProvider { const users = {}; 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); if (!isNaN(userId) && !users[userId]) { users[userId] = { diff --git a/src/components/user-avatar/user-avatar.ts b/src/components/user-avatar/user-avatar.ts index d537765df..dc25a4bd3 100644 --- a/src/components/user-avatar/user-avatar.ts +++ b/src/components/user-avatar/user-avatar.ts @@ -87,7 +87,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { */ protected setFields(): void { 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') { this.avatarUrl = profileUrl; diff --git a/src/core/editor/providers/editor-offline.ts b/src/core/editor/providers/editor-offline.ts index 17d8b44ce..c8decce7d 100644 --- a/src/core/editor/providers/editor-offline.ts +++ b/src/core/editor/providers/editor-offline.ts @@ -228,7 +228,7 @@ export class CoreEditorOfflineProvider { if (entry) { 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}'`); throw null; } diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index eae580a46..7decdd627 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -166,7 +166,7 @@ export class CoreMimetypeUtilsProvider { if (this.canBeEmbedded(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') { return '';