From 6f45f090cb59bbf5f7e763e245638e23f6dc657b Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 23 Feb 2021 12:12:39 +0100 Subject: [PATCH] MOBILE-3643 forum: Migrate option menus --- .../mod/forum/components/components.module.ts | 4 + .../discussion-options-menu.html | 36 +++++ .../discussion-options-menu.ts | 143 ++++++++++++++++++ .../mod/forum/components/index/index.html | 2 +- .../mod/forum/components/index/index.ts | 65 ++++---- .../post-options-menu/post-options-menu.html | 26 ++++ .../post-options-menu/post-options-menu.scss | 11 ++ .../post-options-menu/post-options-menu.ts | 132 ++++++++++++++++ .../mod/forum/components/post/post.html | 4 +- src/addons/mod/forum/components/post/post.ts | 39 ++++- .../mod/forum/services/forum.service.ts | 10 ++ 11 files changed, 430 insertions(+), 42 deletions(-) create mode 100644 src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.html create mode 100644 src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts create mode 100644 src/addons/mod/forum/components/post-options-menu/post-options-menu.html create mode 100644 src/addons/mod/forum/components/post-options-menu/post-options-menu.scss create mode 100644 src/addons/mod/forum/components/post-options-menu/post-options-menu.ts 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.