diff --git a/scripts/langindex.json b/scripts/langindex.json index daac220eb..2e3002fd5 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -572,6 +572,9 @@ "addon.mod_forum.cannotcreatediscussion": "forum", "addon.mod_forum.couldnotadd": "forum", "addon.mod_forum.cutoffdatereached": "forum", + "addon.mod_forum.delete": "forum", + "addon.mod_forum.deletedpost": "forum", + "addon.mod_forum.deletesure": "forum", "addon.mod_forum.discussion": "forum", "addon.mod_forum.discussionlistsortbycreatedasc": "forum", "addon.mod_forum.discussionlistsortbycreateddesc": "forum", @@ -1718,6 +1721,7 @@ "core.nocomments": "moodle", "core.nograde": "moodle", "core.none": "moodle", + "core.nooptionavailable": "local_moodlemobileapp", "core.nopasswordchangeforced": "local_moodlemobileapp", "core.nopermissionerror": "local_moodlemobileapp", "core.nopermissions": "error", diff --git a/src/addon/mod/forum/components/components.module.ts b/src/addon/mod/forum/components/components.module.ts index 8b0c288d8..5a3180af3 100644 --- a/src/addon/mod/forum/components/components.module.ts +++ b/src/addon/mod/forum/components/components.module.ts @@ -25,12 +25,14 @@ import { CoreTagComponentsModule } from '@core/tag/components/components.module' import { AddonModForumIndexComponent } from './index/index'; import { AddonModForumPostComponent } from './post/post'; import { AddonForumDiscussionOptionsMenuComponent } from './discussion-options-menu/discussion-options-menu'; +import { AddonForumPostOptionsMenuComponent } from './post-options-menu/post-options-menu'; @NgModule({ declarations: [ AddonModForumIndexComponent, AddonModForumPostComponent, - AddonForumDiscussionOptionsMenuComponent + AddonForumDiscussionOptionsMenuComponent, + AddonForumPostOptionsMenuComponent ], imports: [ CommonModule, @@ -48,11 +50,13 @@ import { AddonForumDiscussionOptionsMenuComponent } from './discussion-options-m exports: [ AddonModForumIndexComponent, AddonModForumPostComponent, - AddonForumDiscussionOptionsMenuComponent + AddonForumDiscussionOptionsMenuComponent, + AddonForumPostOptionsMenuComponent ], entryComponents: [ AddonModForumIndexComponent, - AddonForumDiscussionOptionsMenuComponent + AddonForumDiscussionOptionsMenuComponent, + AddonForumPostOptionsMenuComponent ] }) export class AddonModForumComponentsModule {} diff --git a/src/addon/mod/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index ec64d2e1f..f3c181186 100644 --- a/src/addon/mod/forum/components/index/index.ts +++ b/src/addon/mod/forum/components/index/index.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, Optional, Injector, ViewChild } from '@angular/core'; -import { Content, ModalController, NavController, PopoverController } from 'ionic-angular'; +import { Content, ModalController, PopoverController } from 'ionic-angular'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; @@ -75,7 +75,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom constructor(injector: Injector, @Optional() protected content: Content, - protected navCtrl: NavController, protected modalCtrl: ModalController, protected groupsProvider: CoreGroupsProvider, protected userProvider: CoreUserProvider, @@ -110,28 +109,36 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.replyObserver = this.eventsProvider.on(AddonModForumProvider.REPLY_DISCUSSION_EVENT, this.eventReceived.bind(this, false)); this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data) => { - this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => { - // If it's a new discussion in tablet mode, try to open it. - if (data.discussionId) { - // Discussion sent to server, search it in the list of discussions. - const discussion = this.discussions.find((disc) => { - return data.discussionId = disc.discussion; - }); + if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) { + this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => { + if (data.discussionId) { + // Discussion changed, search it in the list of discussions. + const discussion = this.discussions.find((disc) => { + return data.discussionId = disc.discussion; + }); - if (discussion) { - if (typeof data.locked != 'undefined') { - discussion.locked = data.locked; - } - if (typeof data.pinned != 'undefined') { - discussion.pinned = data.pinned; - } - if (typeof data.starred != 'undefined') { - discussion.starred = data.starred; + if (discussion) { + if (typeof data.locked != 'undefined') { + discussion.locked = data.locked; + } + if (typeof data.pinned != 'undefined') { + discussion.pinned = data.pinned; + } + if (typeof data.starred != 'undefined') { + discussion.starred = data.starred; + } } } - } - }); + if (typeof data.deleted != 'undefined' && data.deleted) { + if (data.post.parent == 0 && this.splitviewCtrl && this.splitviewCtrl.isOn()) { + // Discussion deleted, clear details page. + this.splitviewCtrl.emptyDetails(); + } + this.showLoadingAndRefresh(false); + } + }); + } }); // Select the current opened discussion. diff --git a/src/addon/mod/forum/components/post-options-menu/addon-forum-post-options-menu.html b/src/addon/mod/forum/components/post-options-menu/addon-forum-post-options-menu.html new file mode 100644 index 000000000..48d9a9d89 --- /dev/null +++ b/src/addon/mod/forum/components/post-options-menu/addon-forum-post-options-menu.html @@ -0,0 +1,17 @@ + + + +

{{ 'addon.mod_forum.edit' | translate }}

+
+ + +

{{ 'addon.mod_forum.delete' | translate }}

+

{{ 'core.discard' | translate }}

+
+ +

{{ 'core.numwords' | translate: {'$a': wordCount} }}

+
+ +

{{ 'core.nooptionavailable' | translate }}

+
+
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 new file mode 100644 index 000000000..b92b8d6d8 --- /dev/null +++ b/src/addon/mod/forum/components/post-options-menu/post-options-menu.ts @@ -0,0 +1,97 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit } from '@angular/core'; +import { NavParams, ViewController } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { AddonModForumProvider } from '../../providers/forum'; + +/** + * This component is meant to display a popover with the post options. + */ +@Component({ + selector: 'addon-forum-post-options-menu', + templateUrl: 'addon-forum-post-options-menu.html' +}) +export class AddonForumPostOptionsMenuComponent implements OnInit { + post: any; // The post. + forumId: number; // The forum Id. + wordCount: number; // Number of words when available. + canEdit = false; + canDelete = false; + loaded = false; + + constructor(navParams: NavParams, + protected viewCtrl: ViewController, + protected domUtils: CoreDomUtilsProvider, + protected forumProvider: AddonModForumProvider) { + this.post = navParams.get('post'); + this.forumId = navParams.get('forumId'); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (this.forumId) { + if (this.post.id) { + this.forumProvider.getDiscussionPost(this.forumId, this.post.discussion, this.post.id).then((post) => { + this.canDelete = post.capabilities.delete && this.forumProvider.isDeletePostAvailable(); + this.canEdit = false; + 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 { + this.loaded = true; + } + } + + /** + * Close the popover. + */ + dismiss(): void { + this.viewCtrl.dismiss(); + } + + /** + * Delete a post. + */ + deletePost(): void { + if (this.post.id) { + this.viewCtrl.dismiss({action: 'delete'}); + } else { + this.viewCtrl.dismiss({action: 'deleteoffline'}); + } + } + + /** + * Edit a post. + */ + editPost(): void { + if (this.post.id) { + this.viewCtrl.dismiss({action: 'edit'}); + } else { + this.viewCtrl.dismiss({action: 'editoffline'}); + } + } +} 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 87f191833..48bf2e818 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 @@ -7,6 +7,9 @@ +
@@ -19,12 +22,15 @@ +
- {{ 'addon.mod_forum.postisprivatereply' | translate }} + {{ 'addon.mod_forum.postisprivatereply' | translate }}
@@ -40,16 +46,11 @@ -
- - - @@ -81,11 +82,6 @@ - - - - - diff --git a/src/addon/mod/forum/components/post/post.ts b/src/addon/mod/forum/components/post/post.ts index 74801550b..da7e383d7 100644 --- a/src/addon/mod/forum/components/post/post.ts +++ b/src/addon/mod/forum/components/post/post.ts @@ -14,18 +14,21 @@ import { Component, Input, Output, Optional, EventEmitter, OnInit, OnDestroy } from '@angular/core'; import { FormControl } from '@angular/forms'; -import { Content } from 'ionic-angular'; +import { Content, PopoverController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreSyncProvider } from '@providers/sync'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; import { AddonModForumProvider } from '../../providers/forum'; import { AddonModForumHelperProvider } from '../../providers/helper'; import { AddonModForumOfflineProvider } from '../../providers/offline'; import { AddonModForumSyncProvider } from '../../providers/sync'; import { CoreRatingInfo } from '@core/rating/providers/rating'; import { CoreTagProvider } from '@core/tag/providers/tag'; +import { AddonForumPostOptionsMenuComponent } from '../post-options-menu/post-options-menu'; /** * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.). @@ -55,6 +58,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { advanced = false; // Display all form fields. tagsEnabled: boolean; displaySubject = true; + optionsMenuEnabled = false; protected syncId: string; @@ -69,7 +73,10 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { private forumOffline: AddonModForumOfflineProvider, private forumSync: AddonModForumSyncProvider, private tagProvider: CoreTagProvider, - @Optional() private content: Content) { + @Optional() private content: Content, + protected popoverCtrl: PopoverController, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider) { this.onPostChange = new EventEmitter(); this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); } @@ -84,10 +91,44 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { this.displaySubject = this.post.parent == 0 || (this.post.subject != this.defaultSubject && this.post.subject != 'Re: ' + this.defaultSubject && this.post.subject != reTranslated + this.defaultSubject); + + this.optionsMenuEnabled = !this.post.id || (this.forumProvider.isGetDiscussionPostAvailable() && + (this.forumProvider.isDeletePostAvailable())); } /** - * Set data to new post, clearing temporary files and updating original data. + * Deletes an online post. + */ + deletePost(): void { + this.domUtils.showDeleteConfirm('addon.mod_forum.deletesure').then(() => { + const modal = this.domUtils.showModalLoading('core.deleting', true); + + this.forumProvider.deletePost(this.post.id).then((response) => { + + const data = { + forumId: this.forum.id, + discussionId: this.discussionId, + cmId: this.forum.cmid, + deleted: response.status, + post: this.post + }; + + this.eventsProvider.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, + this.sitesProvider.getCurrentSiteId()); + + this.domUtils.showToast('addon.mod_forum.deletedpost', true); + }).catch((error) => { + this.domUtils.showErrorModal(error); + }).finally(() => { + modal.dismiss(); + }); + }).catch((error) => { + // Do nothing. + }); + } + + /** + * Set data to new reply post, clearing temporary files and updating original data. * * @param replyingTo Id of post beeing replied. * @param isEditing True it's an offline reply beeing edited, false otherwise. @@ -96,7 +137,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { * @param isPrivate True if it's private reply. * @param files Reply attachments. */ - protected setReplyData(replyingTo?: number, isEditing?: boolean, subject?: string, message?: string, files?: any[], + protected setReplyFormData(replyingTo?: number, isEditing?: boolean, subject?: string, message?: string, files?: any[], isPrivate?: boolean): void { // Delete the local files from the tmp folder if any. this.uploaderProvider.clearTmpFiles(this.replyData.files); @@ -121,14 +162,52 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { this.advanced = this.replyData.files.length > 0; } + /** + * Show the context menu. + * + * @param e Click Event. + */ + showOptionsMenu(e: Event): void { + e.preventDefault(); + e.stopPropagation(); + + const popover = this.popoverCtrl.create(AddonForumPostOptionsMenuComponent, { + post: this.post, + forumId: this.forum.id + }); + popover.onDidDismiss((data) => { + if (data && data.action) { + switch (data.action) { + case 'edit': + // Not implemented. + break; + case 'editoffline': + this.editOfflineReply(); + break; + case 'delete': + this.deletePost(); + break; + case 'deleteoffline': + this.discardOfflineReply(); + break; + default: + break; + } + } + }); + popover.present({ + ev: e + }); + } + /** * Set this post as being replied to. */ - showReply(): void { + showReplyForm(): void { if (this.replyData.isEditing) { // User is editing a post, data needs to be resetted. Ask confirm if there is unsaved data. this.confirmDiscard().then(() => { - this.setReplyData(this.post.id); + this.setReplyFormData(this.post.id); if (this.content) { setTimeout(() => { @@ -143,7 +222,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { return; } else if (!this.replyData.replyingTo) { // User isn't replying, it's a brand new reply. Initialize the data. - this.setReplyData(this.post.id); + this.setReplyFormData(this.post.id); } else { // The post being replied has changed but the data will be kept. this.replyData.replyingTo = this.post.id; @@ -162,13 +241,13 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { /** * Set this post as being edited to. */ - editReply(): void { + editOfflineReply(): void { // Ask confirm if there is unsaved data. this.confirmDiscard().then(() => { this.syncId = this.forumSync.getDiscussionSyncId(this.discussionId); this.syncProvider.blockOperation(AddonModForumProvider.COMPONENT, this.syncId); - this.setReplyData(this.post.parent, true, this.post.subject, this.post.message, this.post.attachments, + this.setReplyFormData(this.post.parent, true, this.post.subject, this.post.message, this.post.attachments, this.post.isprivatereply); }).catch(() => { // Cancelled. @@ -259,7 +338,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { } // Reset data. - this.setReplyData(); + this.setReplyFormData(); this.onPostChange.emit(); @@ -279,7 +358,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { cancel(): void { this.confirmDiscard().then(() => { // Reset data. - this.setReplyData(); + this.setReplyFormData(); if (this.syncId) { this.syncProvider.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId); @@ -292,8 +371,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { /** * Discard offline reply. */ - discard(): void { - this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => { + discardOfflineReply(): void { + this.domUtils.showDeleteConfirm().then(() => { const promises = []; promises.push(this.forumOffline.deleteReply(this.post.parent)); @@ -305,7 +384,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { return Promise.all(promises).finally(() => { // Reset data. - this.setReplyData(); + this.setReplyFormData(); this.onPostChange.emit(); @@ -322,7 +401,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { * Function called when rating is updated online. */ ratingUpdated(): void { - this.forumProvider.invalidateDiscussionPosts(this.discussionId); + this.forumProvider.invalidateDiscussionPosts(this.discussionId, this.forum.id); } /** diff --git a/src/addon/mod/forum/lang/en.json b/src/addon/mod/forum/lang/en.json index 29215eb9d..14c06e9b8 100644 --- a/src/addon/mod/forum/lang/en.json +++ b/src/addon/mod/forum/lang/en.json @@ -9,6 +9,9 @@ "cannotcreatediscussion": "Could not create new discussion", "couldnotadd": "Could not add your post due to an unknown error", "cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.", + "delete": "Delete", + "deletedpost": "The post has been deleted", + "deletesure": "Are you sure you want to delete this post?", "discussion": "Discussion", "discussionlistsortbycreatedasc": "Sort by creation date in ascending order", "discussionlistsortbycreateddesc": "Sort by creation date in descending order", diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index b4b5d424d..454a0147a 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, Optional, OnDestroy, ViewChild, NgZone } from '@angular/core'; -import { IonicPage, NavParams, Content } from 'ionic-angular'; +import { IonicPage, NavParams, Content, NavController } from 'ionic-angular'; import { Network } from '@ionic-native/network'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; @@ -46,8 +46,8 @@ export class AddonModForumDiscussionPage implements OnDestroy { courseId: number; discussionId: number; - forum: any; - accessInfo: any; + forum: any = {}; + accessInfo: any = {}; discussion: any; posts: any[]; discussionLoaded = false; @@ -106,7 +106,8 @@ export class AddonModForumDiscussionPage implements OnDestroy { private forumHelper: AddonModForumHelperProvider, private forumSync: AddonModForumSyncProvider, private ratingOffline: CoreRatingOfflineProvider, - @Optional() private svComponent: CoreSplitViewComponent) { + @Optional() private svComponent: CoreSplitViewComponent, + protected navCtrl: NavController) { this.courseId = navParams.get('courseId'); this.cmId = navParams.get('cmId'); this.forumId = navParams.get('forumId'); @@ -190,17 +191,32 @@ export class AddonModForumDiscussionPage implements OnDestroy { }); this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data) => { - this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => { - if (typeof data.locked != 'undefined') { - this.discussion.locked = data.locked; - } - if (typeof data.pinned != 'undefined') { - this.discussion.pinned = data.pinned; - } - if (typeof data.starred != 'undefined') { - this.discussion.starred = data.starred; - } - }); + if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.cmId) { + this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => { + if (typeof data.locked != 'undefined') { + this.discussion.locked = data.locked; + } + if (typeof data.pinned != 'undefined') { + this.discussion.pinned = data.pinned; + } + if (typeof data.starred != 'undefined') { + this.discussion.starred = data.starred; + } + + if (typeof data.deleted != 'undefined' && data.deleted) { + if (data.post.parent == 0) { + if (this.svComponent && this.svComponent.isOn()) { + this.svComponent.emptyDetails(); + } else { + this.navCtrl.pop(); + } + } else { + this.discussionLoaded = false; + this.refreshPosts(); + } + } + }); + } }); } @@ -358,8 +374,6 @@ export class AddonModForumDiscussionPage implements OnDestroy { return Promise.all(promises); }).catch(() => { // Ignore errors. - this.forum = {}; - this.accessInfo = {}; }).then(() => { this.defaultSubject = this.translate.instant('addon.mod_forum.re') + ' ' + (this.discussion ? this.discussion.subject : ''); @@ -496,7 +510,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { const promises = [ this.forumProvider.invalidateForumData(this.courseId), - this.forumProvider.invalidateDiscussionPosts(this.discussionId), + this.forumProvider.invalidateDiscussionPosts(this.discussionId, this.forumId), this.forumProvider.invalidateAccessInformation(this.forumId), this.forumProvider.invalidateCanAddDiscussion(this.forumId) ]; diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index c59e5cdf7..3afc7b4c2 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -74,6 +74,7 @@ export class AddonModForumProvider { /** * Get common part of cache key for can add discussion WS calls. + * TODO: Use getForumDataCacheKey as a prefix. * * @param forumId Forum ID. * @return Cache key. @@ -82,6 +83,38 @@ export class AddonModForumProvider { return this.ROOT_CACHE_KEY + 'canadddiscussion:' + forumId + ':'; } + /** + * Get prefix cache key for all forum activity data WS calls. + * + * @param forumId Forum ID. + * @return Cache key. + */ + protected getForumDataPrefixCacheKey(forumId: number): string { + return this.ROOT_CACHE_KEY + forumId; + } + + /** + * Get cache key for discussion post data WS calls. + * + * @param forumId Forum ID. + * @param discussionId Discussion ID. + * @param postId Course ID. + * @return Cache key. + */ + protected getDiscussionPostDataCacheKey(forumId: number, discussionId: number, postId: number): string { + return this.getForumDiscussionDataCacheKey(forumId, discussionId) + ':post:' + postId; + } + + /** + * Get cache key for forum data WS calls. + * + * @param courseId Course ID. + * @return Cache key. + */ + protected getForumDiscussionDataCacheKey(forumId: number, discussionId: number): string { + return this.getForumDataPrefixCacheKey(forumId) + ':discussion:' + discussionId; + } + /** * Get cache key for forum data WS calls. * @@ -94,6 +127,7 @@ export class AddonModForumProvider { /** * Get cache key for forum access information WS calls. + * TODO: Use getForumDataCacheKey as a prefix. * * @param forumId Forum ID. * @return Cache key. @@ -104,6 +138,7 @@ export class AddonModForumProvider { /** * Get cache key for forum discussion posts WS calls. + * TODO: Use getForumDiscussionDataCacheKey instead. * * @param discussionId Discussion ID. * @return Cache key. @@ -218,6 +253,24 @@ export class AddonModForumProvider { return this.canAddDiscussion(forumId, AddonModForumProvider.ALL_PARTICIPANTS); } + /** + * Delete a post. + * + * @param postId Post id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + * @since 3.8 + */ + deletePost(postId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + postid: postId + }; + + return site.write('mod_forum_delete_post', params); + }); + } + /** * Extract the starting post of a discussion from a list of posts. The post is removed from the array passed as a parameter. * @@ -244,6 +297,26 @@ export class AddonModForumProvider { return this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan(['3.1.5', '3.2.2']); } + /** + * Returns whether or not getDiscussionPost WS available or not. + * + * @return If WS is avalaible. + * @since 3.8 + */ + isGetDiscussionPostAvailable(): boolean { + return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_get_discussion_post'); + } + + /** + * Returns whether or not deletePost WS available or not. + * + * @return If WS is avalaible. + * @since 3.8 + */ + isDeletePostAvailable(): boolean { + return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_delete_post'); + } + /** * Format discussions, setting groupname if the discussion group is valid. * @@ -306,6 +379,35 @@ export class AddonModForumProvider { }); } + /** + * Get a particular discussion post. + * + * @param forumId Forum ID. + * @param discussionId Discussion ID. + * @param postId Post ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the post is retrieved. + */ + getDiscussionPost(forumId: number, discussionId: number, postId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + postid: postId + }; + const preSets = { + cacheKey: this.getDiscussionPostDataCacheKey(forumId, discussionId, postId), + updateFrequency: CoreSite.FREQUENCY_RARELY + }; + + return site.read('mod_forum_get_discussion_post', params, preSets).then((response) => { + if (response.post) { + return response.post; + } else { + return Promise.reject(null); + } + }); + }); + } + /** * Get a forum by course module ID. * @@ -641,7 +743,7 @@ export class AddonModForumProvider { const promises = []; response.discussions.forEach((discussion) => { - promises.push(this.invalidateDiscussionPosts(discussion.discussion)); + promises.push(this.invalidateDiscussionPosts(discussion.discussion, forum.id)); }); return this.utils.allPromises(promises); @@ -673,12 +775,19 @@ export class AddonModForumProvider { * Invalidates forum discussion posts. * * @param discussionId Discussion ID. + * @param forumId Forum ID. If not set, we can't invalidate individual post information. * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the data is invalidated. */ - invalidateDiscussionPosts(discussionId: number, siteId?: string): Promise { + invalidateDiscussionPosts(discussionId: number, forumId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.invalidateWsCacheForKey(this.getDiscussionPostsCacheKey(discussionId)); + const promises = [site.invalidateWsCacheForKey(this.getDiscussionPostsCacheKey(discussionId))]; + + if (forumId) { + promises.push(site.invalidateWsCacheForKeyStartingWith(this.getForumDiscussionDataCacheKey(forumId, discussionId))); + } + + return this.utils.allPromises(promises); }); } @@ -844,7 +953,7 @@ export class AddonModForumProvider { * @param discussionId DIscussion id. * @param locked True to lock, false to unlock. * @param siteId Site ID. If not defined, current site. - * @return Promise resvoled when done. + * @return Promise resolved when done. * @since 3.7 */ setLockState(forumId: number, discussionId: number, locked: boolean, siteId?: string): Promise { @@ -878,7 +987,7 @@ export class AddonModForumProvider { * @param discussionId Discussion id. * @param locked True to pin, false to unpin. * @param siteId Site ID. If not defined, current site. - * @return Promise resvoled when done. + * @return Promise resolved when done. * @since 3.7 */ setPinState(discussionId: number, pinned: boolean, siteId?: string): Promise { @@ -898,7 +1007,7 @@ export class AddonModForumProvider { * @param discussionId Discussion id. * @param starred True to star, false to unstar. * @param siteId Site ID. If not defined, current site. - * @return Promise resvoled when done. + * @return Promise resolved when done. * @since 3.7 */ toggleFavouriteState(discussionId: number, starred: boolean, siteId?: string): Promise { diff --git a/src/addon/mod/forum/providers/push-click-handler.ts b/src/addon/mod/forum/providers/push-click-handler.ts index ce23e4f01..5d3891d81 100644 --- a/src/addon/mod/forum/providers/push-click-handler.ts +++ b/src/addon/mod/forum/providers/push-click-handler.ts @@ -62,7 +62,7 @@ export class AddonModForumPushClickHandler implements CorePushNotificationsClick pageParams.postId = Number(data.postid || contextUrlParams.urlHash.replace('p', '')); } - return this.forumProvider.invalidateDiscussionPosts(pageParams.discussionId, notification.site).catch(() => { + return this.forumProvider.invalidateDiscussionPosts(pageParams.discussionId, undefined, notification.site).catch(() => { // Ignore errors. }).then(() => { return this.linkHelper.goInSite(undefined, 'AddonModForumDiscussionPage', pageParams, notification.site); diff --git a/src/addon/mod/forum/providers/sync.ts b/src/addon/mod/forum/providers/sync.ts index a71def029..2b204df72 100644 --- a/src/addon/mod/forum/providers/sync.ts +++ b/src/addon/mod/forum/providers/sync.ts @@ -326,7 +326,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { updated = true; // Invalidate discussions of updated ratings. - promises.push(this.forumProvider.invalidateDiscussionPosts(result.itemSet.itemSetId, siteId)); + promises.push(this.forumProvider.invalidateDiscussionPosts(result.itemSet.itemSetId, undefined, siteId)); } if (result.warnings.length) { // Fetch forum to construct the warning message. @@ -502,7 +502,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { if (forumId) { promises.push(this.forumProvider.invalidateDiscussionsList(forumId, siteId)); } - promises.push(this.forumProvider.invalidateDiscussionPosts(discussionId, siteId)); + promises.push(this.forumProvider.invalidateDiscussionPosts(discussionId, forumId, siteId)); return this.utils.allPromises(promises).catch(() => { // Ignore errors. diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index b7de06e75..93afb0faf 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -571,6 +571,9 @@ "addon.mod_forum.cannotcreatediscussion": "Could not create new discussion", "addon.mod_forum.couldnotadd": "Could not add your post due to an unknown error", "addon.mod_forum.cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.", + "addon.mod_forum.delete": "Delete", + "addon.mod_forum.deletedpost": "The post has been deleted", + "addon.mod_forum.deletesure": "Are you sure you want to delete this post?", "addon.mod_forum.discussion": "Discussion", "addon.mod_forum.discussionlistsortbycreatedasc": "Sort by creation date in ascending order", "addon.mod_forum.discussionlistsortbycreateddesc": "Sort by creation date in descending order", @@ -1713,6 +1716,7 @@ "core.nocomments": "No comments", "core.nograde": "No grade", "core.none": "None", + "core.nooptionavailable": "No option available", "core.nopasswordchangeforced": "You cannot proceed without changing your password.", "core.nopermissionerror": "Sorry, but you do not currently have permissions to do that", "core.nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).", diff --git a/src/components/local-file/local-file.ts b/src/components/local-file/local-file.ts index dc01724f1..e1c46b1c2 100644 --- a/src/components/local-file/local-file.ts +++ b/src/components/local-file/local-file.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; import { CoreFileProvider } from '@providers/file'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; @@ -48,9 +47,12 @@ export class CoreLocalFileComponent implements OnInit { editMode: boolean; relativePath: string; - constructor(private mimeUtils: CoreMimetypeUtilsProvider, private utils: CoreUtilsProvider, private translate: TranslateService, - private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider, - private domUtils: CoreDomUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { + constructor(private mimeUtils: CoreMimetypeUtilsProvider, + private utils: CoreUtilsProvider, + private textUtils: CoreTextUtilsProvider, + private fileProvider: CoreFileProvider, + private domUtils: CoreDomUtilsProvider, + private timeUtils: CoreTimeUtilsProvider) { this.onDelete = new EventEmitter(); this.onRename = new EventEmitter(); this.onClick = new EventEmitter(); diff --git a/src/lang/en.json b/src/lang/en.json index 8a1f726e8..328b287a5 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -182,6 +182,7 @@ "notapplicable": "n/a", "notenrolledprofile": "This profile is not available because this user is not enrolled in this course.", "notice": "Notice", + "nooptionavailable": "No option available", "notingroup": "Sorry, but you need to be part of a group to see this page.", "notsent": "Not sent", "now": "now",