diff --git a/src/addons/mod/forum/components/components.module.ts b/src/addons/mod/forum/components/components.module.ts
index 17a5497d1..7ba512416 100644
--- a/src/addons/mod/forum/components/components.module.ts
+++ b/src/addons/mod/forum/components/components.module.ts
@@ -21,11 +21,15 @@ import { CoreTagComponentsModule } from '@features/tag/components/components.mod
import { AddonModForumIndexComponent } from './index/index';
import { AddonModForumPostComponent } from './post/post';
+import { AddonModForumPostOptionsMenuComponent } from './post-options-menu/post-options-menu';
+import { AddonModForumDiscussionOptionsMenuComponent } from './discussion-options-menu/discussion-options-menu';
@NgModule({
declarations: [
AddonModForumIndexComponent,
AddonModForumPostComponent,
+ AddonModForumPostOptionsMenuComponent,
+ AddonModForumDiscussionOptionsMenuComponent,
],
imports: [
CoreSharedModule,
diff --git a/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.html b/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.html
new file mode 100644
index 000000000..5332499ea
--- /dev/null
+++ b/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.html
@@ -0,0 +1,36 @@
+
+
+
+ {{ 'addon.mod_forum.lockdiscussion' | translate }}
+
+
+
+
+
+ {{ 'addon.mod_forum.unlockdiscussion' | translate }}
+
+
+
+
+
+ {{ 'addon.mod_forum.pindiscussion' | translate }}
+
+
+
+
+
+ {{ 'addon.mod_forum.unpindiscussion' | translate }}
+
+
+
+
+
+ {{ 'addon.mod_forum.addtofavourites' | translate }}
+
+
+
+
+
+ {{ 'addon.mod_forum.removefromfavourites' | translate }}
+
+
diff --git a/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts b/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts
new file mode 100644
index 000000000..5eeffaa5b
--- /dev/null
+++ b/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts
@@ -0,0 +1,143 @@
+// (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, Input, OnInit } from '@angular/core';
+import { CoreSites } from '@services/sites';
+import { CoreDomUtils } from '@services/utils/dom';
+import { PopoverController } from '@singletons';
+import { CoreEvents } from '@singletons/events';
+import { AddonModForum, AddonModForumDiscussion, AddonModForumProvider } from '../../services/forum.service';
+
+/**
+ * This component is meant to display a popover with the discussion options.
+ */
+@Component({
+ selector: 'addon-forum-discussion-options-menu',
+ templateUrl: 'discussion-options-menu.html',
+})
+export class AddonModForumDiscussionOptionsMenuComponent implements OnInit {
+
+ @Input() discussion!: AddonModForumDiscussion; // The discussion.
+ @Input() forumId!: number; // The forum Id.
+ @Input() cmId!: number; // The component module Id.
+
+ canPin = false;
+
+ /**
+ * Component being initialized.
+ */
+ async ngOnInit(): Promise {
+ if (!AddonModForum.instance.isSetPinStateAvailableForSite()) {
+ this.canPin = false;
+
+ return;
+ }
+
+ // Use the canAddDiscussion WS to check if the user can pin discussions.
+ try {
+ const response = await AddonModForum.instance.canAddDiscussionToAll(this.forumId, { cmId: this.cmId });
+
+ this.canPin = !!response.canpindiscussions;
+ } catch (error) {
+ this.canPin = false;
+ }
+ }
+
+ /**
+ * Lock or unlock the discussion.
+ *
+ * @param locked True to lock the discussion, false to unlock.
+ */
+ async setLockState(locked: boolean): Promise {
+ const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
+
+ try {
+ const response = await AddonModForum.instance.setLockState(this.forumId, this.discussion.discussion, locked);
+ const data = {
+ forumId: this.forumId,
+ discussionId: this.discussion.discussion,
+ cmId: this.cmId,
+ locked: response.locked,
+ };
+
+ CoreEvents.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, CoreSites.instance.getCurrentSiteId());
+ PopoverController.instance.dismiss({ action: 'lock', value: locked });
+ CoreDomUtils.instance.showToast('addon.mod_forum.lockupdated', true);
+ } catch (error) {
+ CoreDomUtils.instance.showErrorModal(error);
+ PopoverController.instance.dismiss();
+ } finally {
+ modal.dismiss();
+ }
+ }
+
+ /**
+ * Pin or unpin the discussion.
+ *
+ * @param pinned True to pin the discussion, false to unpin it.
+ */
+ async setPinState(pinned: boolean): Promise {
+ const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
+
+ try {
+ await AddonModForum.instance.setPinState(this.discussion.discussion, pinned);
+
+ const data = {
+ forumId: this.forumId,
+ discussionId: this.discussion.discussion,
+ cmId: this.cmId,
+ pinned: pinned,
+ };
+
+ CoreEvents.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, CoreSites.instance.getCurrentSiteId());
+ PopoverController.instance.dismiss({ action: 'pin', value: pinned });
+ CoreDomUtils.instance.showToast('addon.mod_forum.pinupdated', true);
+ } catch (error) {
+ CoreDomUtils.instance.showErrorModal(error);
+ PopoverController.instance.dismiss();
+ } finally {
+ modal.dismiss();
+ }
+ }
+
+ /**
+ * Star or unstar the discussion.
+ *
+ * @param starred True to star the discussion, false to unstar it.
+ */
+ async toggleFavouriteState(starred: boolean): Promise {
+ const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
+
+ try {
+ await AddonModForum.instance.toggleFavouriteState(this.discussion.discussion, starred);
+
+ const data = {
+ forumId: this.forumId,
+ discussionId: this.discussion.discussion,
+ cmId: this.cmId,
+ starred: starred,
+ };
+
+ CoreEvents.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, CoreSites.instance.getCurrentSiteId());
+ PopoverController.instance.dismiss({ action: 'star', value: starred });
+ CoreDomUtils.instance.showToast('addon.mod_forum.favouriteupdated', true);
+ } catch (error) {
+ CoreDomUtils.instance.showErrorModal(error);
+ PopoverController.instance.dismiss();
+ } finally {
+ modal.dismiss();
+ }
+ }
+
+}
diff --git a/src/addons/mod/forum/components/index/index.html b/src/addons/mod/forum/components/index/index.html
index 761c17ed4..7815b4c1b 100644
--- a/src/addons/mod/forum/components/index/index.html
+++ b/src/addons/mod/forum/components/index/index.html
@@ -80,7 +80,7 @@
fill="clear" color="dark"
[attr.aria-label]="('core.displayoptions' | translate)"
(click)="showOptionsMenu($event, discussion)">
-
+
diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts
index df4d71950..0b956a286 100644
--- a/src/addons/mod/forum/components/index/index.ts
+++ b/src/addons/mod/forum/components/index/index.ts
@@ -24,7 +24,7 @@ import {
AddonModForumDiscussion,
} from '@addons/mod/forum/services/forum.service';
import { AddonModForumOffline, AddonModForumOfflineDiscussion } from '@addons/mod/forum/services/offline.service';
-import { Translate } from '@singletons';
+import { PopoverController, Translate } from '@singletons';
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
import { AddonModForumHelper } from '@addons/mod/forum/services/helper.service';
import { CoreGroups, CoreGroupsProvider } from '@services/groups';
@@ -37,6 +37,7 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreCourse } from '@features/course/services/course';
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
+import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu';
/**
* Component that displays a forum entry page.
@@ -557,41 +558,39 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
/**
* Show the context menu.
*
- * @param e Click Event.
+ * @param event Click Event.
+ * @param discussion Discussion.
*/
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- showOptionsMenu(e: Event, discussion: any): void {
- alert('Show options menu not implemented');
+ async showOptionsMenu(event: Event, discussion: AddonModForumDiscussion): Promise {
+ const popover = await PopoverController.instance.create({
+ component: AddonModForumDiscussionOptionsMenuComponent,
+ componentProps: {
+ discussion,
+ forumId: this.forum!.id,
+ cmId: this.module!.id,
+ },
+ event,
+ });
- // @todo
- // e.preventDefault();
- // e.stopPropagation();
+ popover.present();
- // const popover = this.popoverCtrl.create(AddonForumDiscussionOptionsMenuComponent, {
- // discussion: discussion,
- // forumId: this.forum.id,
- // cmId: this.module.id,
- // });
- // popover.onDidDismiss((data) => {
- // if (data && data.action) {
- // switch (data.action) {
- // case 'lock':
- // discussion.locked = data.value;
- // break;
- // case 'pin':
- // discussion.pinned = data.value;
- // break;
- // case 'star':
- // discussion.starred = data.value;
- // break;
- // default:
- // break;
- // }
- // }
- // });
- // popover.present({
- // ev: e,
- // });
+ const result = await popover.onDidDismiss<{ action?: string; value: boolean }>();
+
+ if (result.data && result.data.action) {
+ switch (result.data.action) {
+ case 'lock':
+ discussion.locked = result.data.value;
+ break;
+ case 'pin':
+ discussion.pinned = result.data.value;
+ break;
+ case 'star':
+ discussion.starred = result.data.value;
+ break;
+ default:
+ break;
+ }
+ }
}
}
diff --git a/src/addons/mod/forum/components/post-options-menu/post-options-menu.html b/src/addons/mod/forum/components/post-options-menu/post-options-menu.html
new file mode 100644
index 000000000..cbf595c92
--- /dev/null
+++ b/src/addons/mod/forum/components/post-options-menu/post-options-menu.html
@@ -0,0 +1,26 @@
+
+
+
+
+ {{ 'addon.mod_forum.edit' | translate }}
+
+
+
+
+
+ {{ 'addon.mod_forum.delete' | translate }}
+ {{ 'core.discard' | translate }}
+
+
+
+
+ {{ 'core.numwords' | translate: {'$a': wordCount} }}
+
+
+
+
+
+ {{ 'core.openinbrowser' | translate }}
+
+
+
diff --git a/src/addons/mod/forum/components/post-options-menu/post-options-menu.scss b/src/addons/mod/forum/components/post-options-menu/post-options-menu.scss
new file mode 100644
index 000000000..4ef3e574f
--- /dev/null
+++ b/src/addons/mod/forum/components/post-options-menu/post-options-menu.scss
@@ -0,0 +1,11 @@
+:host {
+ core-loading:not(.core-loading-loaded) > .core-loading-container {
+ position: relative !important;
+ padding-top: 10px !important;
+ padding-bottom: 10px !important;
+ overflow: hidden;
+ }
+ core-loading > .core-loading-container .core-loading-message {
+ display: none;
+ }
+}
diff --git a/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts b/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts
new file mode 100644
index 000000000..ed5bf452f
--- /dev/null
+++ b/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts
@@ -0,0 +1,132 @@
+// (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, Input, OnDestroy, OnInit } from '@angular/core';
+import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
+import { CoreApp } from '@services/app';
+import { AddonModForum, AddonModForumPost } from '@addons/mod/forum/services/forum.service';
+import { Network, NgZone, PopoverController } from '@singletons';
+import { Subscription } from 'rxjs';
+import { CoreDomUtils } from '@services/utils/dom';
+
+/**
+ * This component is meant to display a popover with the post options.
+ */
+@Component({
+ selector: 'addon-forum-post-options-menu',
+ templateUrl: 'post-options-menu.html',
+ styleUrls: ['./post-options-menu.scss'],
+})
+export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy {
+
+ @Input() post!: AddonModForumPost; // The post.
+ @Input() cmId!: number;
+ @Input() forumId!: number; // The forum Id.
+
+ wordCount?: number | null; // Number of words when available.
+ canEdit = false;
+ canDelete = false;
+ loaded = false;
+ url?: string;
+ isOnline!: boolean;
+ offlinePost!: boolean;
+
+ protected onlineObserver?: Subscription;
+
+ /**
+ * Component being initialized.
+ */
+ async ngOnInit(): Promise {
+ this.isOnline = CoreApp.instance.isOnline();
+
+ this.onlineObserver = Network.instance.onChange().subscribe(() => {
+ // Execute the callback in the Angular zone, so change detection doesn't stop working.
+ NgZone.instance.run(() => {
+ this.isOnline = CoreApp.instance.isOnline();
+ });
+ });
+
+ if (this.post.id > 0) {
+ const site = CoreSites.instance.getCurrentSite()!;
+ this.url = site.createSiteUrl('/mod/forum/discuss.php', { d: this.post.discussionid.toString() }, 'p' + this.post.id);
+ this.offlinePost = false;
+ } else {
+ // Offline post, you can edit or discard the post.
+ this.loaded = true;
+ this.offlinePost = true;
+
+ return;
+ }
+
+ if (typeof this.post.capabilities.delete == 'undefined') {
+ if (this.forumId) {
+ try {
+ this.post =
+ await AddonModForum.instance.getDiscussionPost(this.forumId, this.post.discussionid, this.post.id, {
+ cmId: this.cmId,
+ readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
+ });
+ } catch (error) {
+ CoreDomUtils.instance.showErrorModalDefault(error, 'Error getting discussion post.');
+ }
+ } else {
+ this.loaded = true;
+
+ return;
+ }
+ }
+
+ this.canDelete = !!this.post.capabilities.delete && AddonModForum.instance.isDeletePostAvailable();
+ this.canEdit = !!this.post.capabilities.edit && AddonModForum.instance.isUpdatePostAvailable();
+ this.wordCount = (this.post.haswordcount && this.post.wordcount) || null;
+ this.loaded = true;
+ }
+
+ /**
+ * Component destroyed.
+ */
+ ngOnDestroy(): void {
+ this.onlineObserver?.unsubscribe();
+ }
+
+ /**
+ * Close the popover.
+ */
+ dismiss(): void {
+ PopoverController.instance.dismiss();
+ }
+
+ /**
+ * Delete a post.
+ */
+ deletePost(): void {
+ if (!this.offlinePost) {
+ PopoverController.instance.dismiss({ action: 'delete' });
+ } else {
+ PopoverController.instance.dismiss({ action: 'deleteoffline' });
+ }
+ }
+
+ /**
+ * Edit a post.
+ */
+ editPost(): void {
+ if (!this.offlinePost) {
+ PopoverController.instance.dismiss({ action: 'edit' });
+ } else {
+ PopoverController.instance.dismiss({ action: 'editoffline' });
+ }
+ }
+
+}
diff --git a/src/addons/mod/forum/components/post/post.html b/src/addons/mod/forum/components/post/post.html
index 1ac11c4df..77d4581a7 100644
--- a/src/addons/mod/forum/components/post/post.html
+++ b/src/addons/mod/forum/components/post/post.html
@@ -19,7 +19,7 @@
-
+
@@ -44,7 +44,7 @@
-
+
diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts
index 85279dddc..f6a20e889 100644
--- a/src/addons/mod/forum/components/post/post.ts
+++ b/src/addons/mod/forum/components/post/post.ts
@@ -38,7 +38,7 @@ import {
AddonModForumProvider,
} from '../../services/forum.service';
import { CoreTag } from '@features/tag/services/tag';
-import { Translate } from '@singletons';
+import { PopoverController, Translate } from '@singletons';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
import { IonContent } from '@ionic/angular';
import { AddonModForumSync } from '../../services/sync.service';
@@ -47,6 +47,7 @@ import { CoreTextUtils } from '@services/utils/text';
import { AddonModForumHelper } from '../../services/helper.service';
import { AddonModForumOffline, AddonModForumReplyOptions } from '../../services/offline.service';
import { CoreUtils } from '@services/utils/utils';
+import { AddonModForumPostOptionsMenuComponent } from '../post-options-menu/post-options-menu';
/**
* Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
@@ -202,13 +203,39 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
/**
* Show the context menu.
*
- * @param e Click Event.
+ * @param event Click Event.
*/
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- showOptionsMenu(e: Event): void {
- alert('Options menu not implemented');
+ async showOptionsMenu(event: Event): Promise {
+ const popover = await PopoverController.instance.create({
+ component: AddonModForumPostOptionsMenuComponent,
+ componentProps: {
+ post: this.post,
+ forumId: this.forum.id,
+ cmId: this.forum.cmid,
+ },
+ event,
+ });
- // @todo
+ popover.present();
+
+ const result = await popover.onDidDismiss<{ action?: string }>();
+
+ if (result.data && result.data.action) {
+ switch (result.data.action) {
+ case 'edit':
+ this.editPost();
+ break;
+ case 'editoffline':
+ this.editOfflineReply();
+ break;
+ case 'delete':
+ this.deletePost();
+ break;
+ case 'deleteoffline':
+ this.discardOfflineReply();
+ break;
+ }
+ }
}
/**
diff --git a/src/addons/mod/forum/services/forum.service.ts b/src/addons/mod/forum/services/forum.service.ts
index a26fac535..3a42d6cf8 100644
--- a/src/addons/mod/forum/services/forum.service.ts
+++ b/src/addons/mod/forum/services/forum.service.ts
@@ -1442,9 +1442,19 @@ export type AddonModForumPost = {
isprivatereply: boolean; // Isprivatereply.
capabilities: {
reply: boolean; // Whether the user can reply to the post.
+ view?: boolean; // Whether the user can view the post.
+ edit?: boolean; // Whether the user can edit the post.
+ delete?: boolean; // Whether the user can delete the post.
+ split?: boolean; // Whether the user can split the post.
+ selfenrol?: boolean; // Whether the user can self enrol into the course.
+ export?: boolean; // Whether the user can export the post.
+ controlreadstatus?: boolean; // Whether the user can control the read status of the post.
+ canreplyprivately?: boolean; // Whether the user can post a private reply.
};
attachment?: 0 | 1;
attachments?: (CoreFileEntry | AddonModForumWSPostAttachment)[];
+ haswordcount?: boolean; // Haswordcount.
+ wordcount?: number; // Wordcount.
tags?: { // Tags.
id: number; // Tag id.
name: string; // Tag name.