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 }}
0">
@@ -40,16 +46,11 @@
-
-
-
- {{ 'addon.mod_forum.edit' | translate }}
-
-
@@ -81,11 +82,6 @@
{{ 'core.cancel' | translate }}
-
-
- {{ 'core.discard' | translate }}
-
-
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",