From c823efe5e4796e00d0edfdeb5336e265c768fd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 24 Oct 2019 13:16:53 +0200 Subject: [PATCH 1/6] MOBILE-3205 forum: Discussion and post new architecture --- .../index/addon-mod-forum-index.html | 73 ++++++++++--------- .../mod/forum/components/index/index.scss | 49 +++++++++++++ .../components/post/addon-mod-forum-post.html | 64 +++++++++------- src/addon/mod/forum/components/post/post.scss | 47 ++++++++++++ 4 files changed, 171 insertions(+), 62 deletions(-) diff --git a/src/addon/mod/forum/components/index/addon-mod-forum-index.html b/src/addon/mod/forum/components/index/addon-mod-forum-index.html index 7a1641f92..9e9f3badf 100644 --- a/src/addon/mod/forum/components/index/addon-mod-forum-index.html +++ b/src/addon/mod/forum/components/index/addon-mod-forum-index.html @@ -43,51 +43,52 @@ - - -

-

- {{ 'core.notsent' | translate }} - {{discussion.userfullname}} -

-
- - - {{ discussion.groupname }} - + +
+

+ +

+
+
+ +

{{discussion.userfullname}}

+

{{ discussion.groupname }}

+

{{ 'core.notsent' | translate }}

+
- - -

- - - -

-

- -
{{ 'addon.mod_forum.unreadpostsnumber' | translate:{ '$a' : discussion.numunread} }}
-
- {{discussion.userfullname}} -

-

{{discussion.created | coreDateDayOrTime}}

-
- - - + +
+

+ + + +

+ +
+
+ +
+

{{discussion.userfullname}}

+

{{ discussion.groupname }}

+

{{discussion.created | coreFormatDate: "strftimedatetimeshort"}}

+
+
+ + - {{ discussion.groupname }} + {{ 'addon.mod_forum.lastpost' | translate }} + {{discussion.timemodified | coreTimeAgo}} + {{discussion.created | coreTimeAgo}} {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} - - - - - {{ 'addon.mod_forum.lastpost' | translate }} {{discussion.timemodified | coreTimeAgo}} + {{ discussion.numunread }} diff --git a/src/addon/mod/forum/components/index/index.scss b/src/addon/mod/forum/components/index/index.scss index 5b0fd16cc..911c5d809 100644 --- a/src/addon/mod/forum/components/index/index.scss +++ b/src/addon/mod/forum/components/index/index.scss @@ -1,5 +1,54 @@ +$addon-forum-avatar-size: 28px; + ion-app.app-root addon-mod-forum-index { .addon-forum-star { color: $core-star-color; } + + .addon-mod-forum-discussion.item { + .label { + margin-top: 4px; + + h2 { + margin-top: 8px; + margin-bottom: 8px; + font-weight: bold; + ion-icon { + @include margin(0, 6px, 0, 0); + } + } + h3 { + font-size: 1.6rem; + } + } + + ion-avatar { + width: $addon-forum-avatar-size; + height: $addon-forum-avatar-size; + min-width: $addon-forum-avatar-size; + min-height: $addon-forum-avatar-size; + &[item-start] { + @include margin(0, 8px, 0, 0); + } + img { + width: $addon-forum-avatar-size; + height: $addon-forum-avatar-size; + } + } + + .addon-mod-forum-discussion-title, + .addon-mod-forum-discussion-info { + display: flex; + align-items: center; + } + .addon-mod-forum-discussion-title h2, + .addon-mod-forum-discussion-info .addon-mod-forum-discussion-author { + flex-grow: 1; + } + + .addon-mod-forum-discussion-more-info { + font-size: 1.4rem; + clear: both; + } + } } 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 64f387bfc..f6cca3c17 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 @@ -1,22 +1,30 @@ - -

- - - -

-

- {{ 'core.notsent' | translate }} - - {{post.modified | coreDateDayOrTime}} -

{{ 'addon.mod_forum.unread' | translate }}
- - {{post.userfullname}} -

+
+

+ + + +

+ + + + +
+
- +
{{ 'addon.mod_forum.postisprivatereply' | translate }}
@@ -30,22 +38,26 @@
- -
{{ 'core.tag.tags' | translate }}:
- -
- - - - - +
+ +
{{ 'core.tag.tags' | translate }}:
+ +
+ + + + + + +
+ {{ 'addon.mod_forum.subject' | translate }} diff --git a/src/addon/mod/forum/components/post/post.scss b/src/addon/mod/forum/components/post/post.scss index 43358381e..429be2b80 100644 --- a/src/addon/mod/forum/components/post/post.scss +++ b/src/addon/mod/forum/components/post/post.scss @@ -2,4 +2,51 @@ ion-app.app-root addon-mod-forum-post { .addon-forum-star { color: $core-star-color; } + + .card-header .item { + .label { + margin-top: 4px; + + h2 { + margin-top: 8px; + margin-bottom: 8px; + font-weight: bold; + ion-icon { + @include margin(0, 6px, 0, 0); + } + } + h3 { + font-size: 1.6rem; + } + } + + ion-avatar { + width: $addon-forum-avatar-size; + height: $addon-forum-avatar-size; + min-width: $addon-forum-avatar-size; + min-height: $addon-forum-avatar-size; + &[item-start] { + @include margin(0, 8px, 0, 0); + } + img { + width: $addon-forum-avatar-size; + height: $addon-forum-avatar-size; + } + } + + .addon-mod-forum-post-title, + .addon-mod-forum-post-info { + display: flex; + align-items: center; + } + .addon-mod-forum-post-title h2, + .addon-mod-forum-post-info .addon-mod-forum-post-author { + flex-grow: 1; + } + } + + .addon-mod-forum-post-more-info div { + font-size: 1.4rem; + } } + From ccf4aeadea6d7f3330e9b795c726fede401f85e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 24 Oct 2019 15:24:21 +0200 Subject: [PATCH 2/6] MOBILE-3205 forum: Add status actions onto discussion list --- .../mod/forum/components/components.module.ts | 10 +- .../addon-forum-discussion-options-menu.html | 24 +++ .../discussion-options-menu.ts | 145 ++++++++++++++++++ .../index/addon-mod-forum-index.html | 8 +- src/addon/mod/forum/components/index/index.ts | 105 +++++++++++-- .../components/post/addon-mod-forum-post.html | 9 +- .../mod/forum/pages/discussion/discussion.ts | 16 ++ src/addon/mod/forum/providers/forum.ts | 2 +- 8 files changed, 289 insertions(+), 30 deletions(-) create mode 100644 src/addon/mod/forum/components/discussion-options-menu/addon-forum-discussion-options-menu.html create mode 100644 src/addon/mod/forum/components/discussion-options-menu/discussion-options-menu.ts diff --git a/src/addon/mod/forum/components/components.module.ts b/src/addon/mod/forum/components/components.module.ts index b26b599fe..8b0c288d8 100644 --- a/src/addon/mod/forum/components/components.module.ts +++ b/src/addon/mod/forum/components/components.module.ts @@ -24,11 +24,13 @@ import { CoreRatingComponentsModule } from '@core/rating/components/components.m 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'; @NgModule({ declarations: [ AddonModForumIndexComponent, - AddonModForumPostComponent + AddonModForumPostComponent, + AddonForumDiscussionOptionsMenuComponent ], imports: [ CommonModule, @@ -45,10 +47,12 @@ import { AddonModForumPostComponent } from './post/post'; ], exports: [ AddonModForumIndexComponent, - AddonModForumPostComponent + AddonModForumPostComponent, + AddonForumDiscussionOptionsMenuComponent ], entryComponents: [ - AddonModForumIndexComponent + AddonModForumIndexComponent, + AddonForumDiscussionOptionsMenuComponent ] }) export class AddonModForumComponentsModule {} diff --git a/src/addon/mod/forum/components/discussion-options-menu/addon-forum-discussion-options-menu.html b/src/addon/mod/forum/components/discussion-options-menu/addon-forum-discussion-options-menu.html new file mode 100644 index 000000000..ba53fa1cc --- /dev/null +++ b/src/addon/mod/forum/components/discussion-options-menu/addon-forum-discussion-options-menu.html @@ -0,0 +1,24 @@ + + +

{{ '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 }}

+
\ No newline at end of file diff --git a/src/addon/mod/forum/components/discussion-options-menu/discussion-options-menu.ts b/src/addon/mod/forum/components/discussion-options-menu/discussion-options-menu.ts new file mode 100644 index 000000000..142c25706 --- /dev/null +++ b/src/addon/mod/forum/components/discussion-options-menu/discussion-options-menu.ts @@ -0,0 +1,145 @@ +// (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 { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { AddonModForumProvider } from '../../providers/forum'; + +/** + * This component is meant to display a popover with the discussion options. + */ +@Component({ + selector: 'addon-forum-discussion-options-menu', + templateUrl: 'addon-forum-discussion-options-menu.html' +}) +export class AddonForumDiscussionOptionsMenuComponent implements OnInit { + discussion: any; // The discussion. + forumId: number; // The forum Id. + cmId: number; // The component module Id. + canPin = false; + + constructor(navParams: NavParams, + protected viewCtrl: ViewController, + protected forumProvider: AddonModForumProvider, + protected domUtils: CoreDomUtilsProvider, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider) { + this.discussion = navParams.get('discussion'); + this.forumId = navParams.get('forumId'); + this.cmId = navParams.get('cmId'); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (this.forumProvider.isSetPinStateAvailableForSite()) { + // Use the canAddDiscussion WS to check if the user can pin discussions. + this.forumProvider.canAddDiscussionToAll(this.forumId).then((response) => { + this.canPin = !!response.canpindiscussions; + }).catch(() => { + this.canPin = false; + }); + } else { + this.canPin = false; + } + } + + /** + * Lock or unlock the discussion. + * + * @param locked True to lock the discussion, false to unlock. + */ + setLockState(locked: boolean): void { + const modal = this.domUtils.showModalLoading('core.sending', true); + + this.forumProvider.setLockState(this.forumId, this.discussion.discussion, locked).then((response) => { + this.viewCtrl.dismiss({action: 'lock', value: locked}); + + const data = { + forumId: this.forumId, + discussionId: this.discussion.discussion, + cmId: this.cmId, + locked: response.locked + }; + this.eventsProvider.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, this.sitesProvider.getCurrentSiteId()); + + this.domUtils.showToast('addon.mod_forum.lockupdated', true); + }).catch((error) => { + this.domUtils.showErrorModal(error); + this.viewCtrl.dismiss(); + }).finally(() => { + modal.dismiss(); + }); + } + + /** + * Pin or unpin the discussion. + * + * @param pinned True to pin the discussion, false to unpin it. + */ + setPinState(pinned: boolean): void { + const modal = this.domUtils.showModalLoading('core.sending', true); + + this.forumProvider.setPinState(this.discussion.discussion, pinned).then(() => { + this.viewCtrl.dismiss({action: 'pin', value: pinned}); + + const data = { + forumId: this.forumId, + discussionId: this.discussion.discussion, + cmId: this.cmId, + pinned: pinned + }; + this.eventsProvider.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, this.sitesProvider.getCurrentSiteId()); + + this.domUtils.showToast('addon.mod_forum.pinupdated', true); + }).catch((error) => { + this.domUtils.showErrorModal(error); + this.viewCtrl.dismiss(); + }).finally(() => { + modal.dismiss(); + }); + } + + /** + * Star or unstar the discussion. + * + * @param starred True to star the discussion, false to unstar it. + */ + toggleFavouriteState(starred: boolean): void { + const modal = this.domUtils.showModalLoading('core.sending', true); + + this.forumProvider.toggleFavouriteState(this.discussion.discussion, starred).then(() => { + this.viewCtrl.dismiss({action: 'star', value: starred}); + + const data = { + forumId: this.forumId, + discussionId: this.discussion.discussion, + cmId: this.cmId, + starred: starred + }; + this.eventsProvider.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, this.sitesProvider.getCurrentSiteId()); + + this.domUtils.showToast('addon.mod_forum.favouriteupdated', true); + }).catch((error) => { + this.domUtils.showErrorModal(error); + this.viewCtrl.dismiss(); + }).finally(() => { + modal.dismiss(); + }); + } +} diff --git a/src/addon/mod/forum/components/index/addon-mod-forum-index.html b/src/addon/mod/forum/components/index/addon-mod-forum-index.html index 9e9f3badf..eda6f5033 100644 --- a/src/addon/mod/forum/components/index/addon-mod-forum-index.html +++ b/src/addon/mod/forum/components/index/addon-mod-forum-index.html @@ -65,7 +65,7 @@ - @@ -74,18 +74,18 @@

{{discussion.userfullname}}

{{ discussion.groupname }}

-

{{discussion.created | coreFormatDate: "strftimedatetimeshort"}}

+

{{discussion.created * 1000 | coreFormatDate: "strftimerecentfull"}}

- + {{ 'addon.mod_forum.lastpost' | translate }} {{discussion.timemodified | coreTimeAgo}} {{discussion.created | coreTimeAgo}} - + {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} {{ discussion.numunread }} diff --git a/src/addon/mod/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index 8edf898d8..ec64d2e1f 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 } from 'ionic-angular'; +import { Content, ModalController, NavController, 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'; @@ -27,6 +27,7 @@ import { AddonModForumHelperProvider } from '../../providers/helper'; import { AddonModForumOfflineProvider } from '../../providers/offline'; import { AddonModForumSyncProvider } from '../../providers/sync'; import { AddonModForumPrefetchHandler } from '../../providers/prefetch-handler'; +import { AddonForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu'; /** * Component that displays a forum entry page. @@ -61,6 +62,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected page = 0; protected trackPosts = false; protected usesGroups = false; + protected canPin = false; protected syncManualObserver: any; // It will observe the sync manual event. protected replyObserver: any; protected newDiscObserver: any; @@ -83,7 +85,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected forumSync: AddonModForumSyncProvider, protected prefetchDelegate: CoreCourseModulePrefetchDelegate, protected prefetchHandler: AddonModForumPrefetchHandler, - protected ratingOffline: CoreRatingOfflineProvider) { + protected ratingOffline: CoreRatingOfflineProvider, + protected popoverCtrl: PopoverController) { super(injector); this.sortingAvailable = this.forumProvider.isDiscussionListSortingAvailable(); @@ -106,8 +109,30 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.eventReceived.bind(this, true)); this.replyObserver = this.eventsProvider.on(AddonModForumProvider.REPLY_DISCUSSION_EVENT, this.eventReceived.bind(this, false)); - this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_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 (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; + } + } + + } + }); + }); // Select the current opened discussion. this.viewDiscObserver = this.eventsProvider.on(AddonModForumProvider.VIEW_DISCUSSION_EVENT, (data) => { @@ -211,18 +236,30 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom }); } }).then(() => { - return Promise.all([ - // Check if the activity uses groups. - this.groupsProvider.getActivityGroupMode(this.forum.cmid).then((mode) => { - this.usesGroups = (mode === CoreGroupsProvider.SEPARATEGROUPS || mode === CoreGroupsProvider.VISIBLEGROUPS); - }), - this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => { - // Disallow adding discussions if cut-off date is reached and the user has not the capability to override it. - // Just in case the forum was fetched from WS when the cut-off date was not reached but it is now. - const cutoffDateReached = this.forumHelper.isCutoffDateReached(this.forum) && !accessInfo.cancanoverridecutoff; - this.canAddDiscussion = this.forum.cancreatediscussions && !cutoffDateReached; - }), - ]); + const promises = []; + // Check if the activity uses groups. + promises.push(this.groupsProvider.getActivityGroupMode(this.forum.cmid).then((mode) => { + this.usesGroups = (mode === CoreGroupsProvider.SEPARATEGROUPS || mode === CoreGroupsProvider.VISIBLEGROUPS); + })); + promises.push(this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => { + // Disallow adding discussions if cut-off date is reached and the user has not the capability to override it. + // Just in case the forum was fetched from WS when the cut-off date was not reached but it is now. + const cutoffDateReached = this.forumHelper.isCutoffDateReached(this.forum) && !accessInfo.cancanoverridecutoff; + this.canAddDiscussion = this.forum.cancreatediscussions && !cutoffDateReached; + })); + + if (this.forumProvider.isSetPinStateAvailableForSite()) { + // Use the canAddDiscussion WS to check if the user can pin discussions. + promises.push(this.forumProvider.canAddDiscussionToAll(this.forum.id).then((response) => { + this.canPin = !!response.canpindiscussions; + }).catch(() => { + this.canPin = false; + })); + } else { + this.canPin = false; + } + + return Promise.all(promises); })); promises.push(this.fetchSortOrderPreference()); @@ -559,6 +596,42 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.sortOrderSelectorExpanded = true; } + /** + * Show the context menu. + * + * @param e Click Event. + */ + showOptionsMenu(e: Event, discussion: any): void { + e.preventDefault(); + e.stopPropagation(); + + 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 + }); + } + /** * Component being destroyed. */ 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 f6cca3c17..d0512dd01 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 @@ -6,19 +6,16 @@ - + - - + diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 6cf56461f..dec2adf0a 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -89,6 +89,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { hasOfflineRatings: boolean; protected ratingOfflineObserver: any; protected ratingSyncObserver: any; + protected changeDiscObserver: any; constructor(navParams: NavParams, network: Network, @@ -183,6 +184,20 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.hasOfflineRatings = false; } }); + + 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; + } + }); + }); } /** @@ -610,6 +625,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.syncManualObserver && this.syncManualObserver.off(); this.ratingOfflineObserver && this.ratingOfflineObserver.off(); this.ratingSyncObserver && this.ratingSyncObserver.off(); + this.changeDiscObserver && this.changeDiscObserver.off(); } /** diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index 55a568574..c59e5cdf7 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -35,7 +35,7 @@ export class AddonModForumProvider { static NEW_DISCUSSION_EVENT = 'addon_mod_forum_new_discussion'; static REPLY_DISCUSSION_EVENT = 'addon_mod_forum_reply_discussion'; static VIEW_DISCUSSION_EVENT = 'addon_mod_forum_view_discussion'; - static CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_lock_discussion'; + static CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_change_discussion_status'; static MARK_READ_EVENT = 'addon_mod_forum_mark_read'; static PREFERENCE_SORTORDER = 'forum_discussionlistsortorder'; From 34ac2a93d81f1cae9e2d1843866bd9864705afa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 25 Oct 2019 10:37:08 +0200 Subject: [PATCH 3/6] MOBILE-3205 forum: Show attachments as inline files --- .../addon-mod-assign-submission-file.html | 8 +- .../component/addon-mod-data-field-file.html | 8 +- .../components/post/addon-mod-forum-post.html | 7 +- .../forum/pages/discussion/discussion.html | 2 +- .../mod/resource/components/index/index.ts | 2 + src/addon/mod/resource/providers/helper.ts | 18 +---- .../addon-mod-workshop-submission.html | 7 +- src/components/components.module.ts | 3 + src/components/files/core-files.html | 9 +++ src/components/files/files.ts | 79 +++++++++++++++++++ src/providers/utils/mimetype.ts | 34 ++++++++ 11 files changed, 133 insertions(+), 44 deletions(-) create mode 100644 src/components/files/core-files.html create mode 100644 src/components/files/files.ts diff --git a/src/addon/mod/assign/submission/file/component/addon-mod-assign-submission-file.html b/src/addon/mod/assign/submission/file/component/addon-mod-assign-submission-file.html index 69d048e0c..bd4fd9d01 100644 --- a/src/addon/mod/assign/submission/file/component/addon-mod-assign-submission-file.html +++ b/src/addon/mod/assign/submission/file/component/addon-mod-assign-submission-file.html @@ -2,13 +2,7 @@

{{plugin.name}}

- - - - - - - +
diff --git a/src/addon/mod/data/fields/file/component/addon-mod-data-field-file.html b/src/addon/mod/data/fields/file/component/addon-mod-data-field-file.html index a7e8d8f11..bb71fc2cd 100644 --- a/src/addon/mod/data/fields/file/component/addon-mod-data-field-file.html +++ b/src/addon/mod/data/fields/file/component/addon-mod-data-field-file.html @@ -10,12 +10,6 @@
- - - - - - - +
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 d0512dd01..0b91ce3d3 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 @@ -27,12 +27,7 @@
- - - - - - +
diff --git a/src/addon/mod/forum/pages/discussion/discussion.html b/src/addon/mod/forum/pages/discussion/discussion.html index 571c89738..57cbce7c5 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.html +++ b/src/addon/mod/forum/pages/discussion/discussion.html @@ -38,7 +38,7 @@ - {{ 'addon.mod_forum.discussionlocked' | translate }} + {{ 'addon.mod_forum.discussionlocked' | translate }} diff --git a/src/addon/mod/resource/components/index/index.ts b/src/addon/mod/resource/components/index/index.ts index 6c33136a3..12913fe40 100644 --- a/src/addon/mod/resource/components/index/index.ts +++ b/src/addon/mod/resource/components/index/index.ts @@ -134,6 +134,8 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource return this.resourceHelper.getEmbeddedHtml(this.module, this.courseId).then((html) => { this.contentText = html; + + this.mode = this.contentText.length > 0 ? 'embedded' : 'external'; }); } else { this.mode = 'external'; diff --git a/src/addon/mod/resource/providers/helper.ts b/src/addon/mod/resource/providers/helper.ts index e751bf2c5..df5dfc870 100644 --- a/src/addon/mod/resource/providers/helper.ts +++ b/src/addon/mod/resource/providers/helper.ts @@ -53,23 +53,7 @@ export class AddonModResourceHelperProvider { getEmbeddedHtml(module: any, courseId: number): Promise { return this.courseHelper.downloadModuleWithMainFileIfNeeded(module, courseId, AddonModResourceProvider.COMPONENT, module.id, module.contents).then((result) => { - const file = module.contents[0], - ext = this.mimetypeUtils.getFileExtension(file.filename), - type = this.mimetypeUtils.getExtensionType(ext), - mimeType = this.mimetypeUtils.getMimeType(ext); - - if (type == 'image') { - return ''; - } - - if (type == 'audio' || type == 'video') { - return '<' + type + ' controls title="' + file.filename + '"" src="' + result.path + '">' + - '' + - ''; - } - - // Shouldn't reach here, the user should have called CoreMimetypeUtilsProvider#canBeEmbedded. - return ''; + return this.mimetypeUtils.getEmbeddedHtml(module.contents[0], result.path); }); } diff --git a/src/addon/mod/workshop/components/submission/addon-mod-workshop-submission.html b/src/addon/mod/workshop/components/submission/addon-mod-workshop-submission.html index 0db614d75..70b4e7063 100644 --- a/src/addon/mod/workshop/components/submission/addon-mod-workshop-submission.html +++ b/src/addon/mod/workshop/components/submission/addon-mod-workshop-submission.html @@ -25,12 +25,7 @@ - - - - - - + diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 2a57451cf..fe663a255 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -27,6 +27,7 @@ import { CoreProgressBarComponent } from './progress-bar/progress-bar'; import { CoreEmptyBoxComponent } from './empty-box/empty-box'; import { CoreSearchBoxComponent } from './search-box/search-box'; import { CoreFileComponent } from './file/file'; +import { CoreFilesComponent } from './files/files'; import { CoreIconComponent } from './icon/icon'; import { CoreContextMenuComponent } from './context-menu/context-menu'; import { CoreContextMenuItemComponent } from './context-menu/context-menu-item'; @@ -67,6 +68,7 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreEmptyBoxComponent, CoreSearchBoxComponent, CoreFileComponent, + CoreFilesComponent, CoreIconComponent, CoreContextMenuComponent, CoreContextMenuItemComponent, @@ -118,6 +120,7 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreEmptyBoxComponent, CoreSearchBoxComponent, CoreFileComponent, + CoreFilesComponent, CoreIconComponent, CoreContextMenuComponent, CoreContextMenuItemComponent, diff --git a/src/components/files/core-files.html b/src/components/files/core-files.html new file mode 100644 index 000000000..239728e29 --- /dev/null +++ b/src/components/files/core-files.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/components/files/files.ts b/src/components/files/files.ts new file mode 100644 index 000000000..9cc9fea6c --- /dev/null +++ b/src/components/files/files.ts @@ -0,0 +1,79 @@ +// (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, DoCheck, KeyValueDiffers } from '@angular/core'; +import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; +import { CoreUtilsProvider } from '@providers/utils/utils'; + +/** + * Component to render a file list. + * + * + * + */ +@Component({ + selector: 'core-files', + templateUrl: 'core-files.html' +}) +export class CoreFilesComponent implements OnInit, DoCheck { + @Input() files: any[]; // List of files. + @Input() component: string; // Component the downloaded files will be linked to. + @Input() componentId: string | number; // Component ID. + @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded. + // Use it for files that you cannot determine if they're outdated or not. + @Input() canDownload?: boolean | string = true; // Whether file can be downloaded. + @Input() showSize?: boolean | string = true; // Whether show filesize. + @Input() showTime?: boolean | string = true; // Whether show file time modified. + @Input() showInline = false; // If true, it will reorder and try to show inline files first. + + contentText: string; + + protected differ: any; // To detect changes in the data input. + + constructor(protected mimetypeUtils: CoreMimetypeUtilsProvider, + protected utils: CoreUtilsProvider, + differs: KeyValueDiffers) { + this.differ = differs.find([]).create(); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (this.utils.isTrueOrOne(this.showInline)) { + this.renderInlineFiles(); + } + } + + /** + * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays). + */ + ngDoCheck(): void { + if (this.utils.isTrueOrOne(this.showInline)) { + // Check if there's any change in the extraData object. + const changes = this.differ.diff(this.files); + if (changes) { + this.renderInlineFiles(); + } + } + } + + protected renderInlineFiles(): void { + this.contentText = this.files.reduce((previous, file) => { + const text = this.mimetypeUtils.getEmbeddedHtml(file); + + return text ? previous + '
' + text : previous; + }, ''); + } +} diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index d45d9af87..47dc09037 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -138,6 +138,40 @@ export class CoreMimetypeUtilsProvider { } } + /** + * Set the embed type to display an embedded file and mimetype if not found. + * + * @param file File object. + * @paran path Alternative path that will override fileurl from file object. + */ + getEmbeddedHtml(file: any, path?: string): string { + let ext; + + if (file.mimetype) { + ext = this.getExtension(file.mimetype); + } else { + ext = this.getFileExtension(file.filename); + file.mimetype = this.getMimeType(ext); + } + + if (this.canBeEmbedded(ext)) { + file.embedType = this.getExtensionType(ext); + + path = path || file.fileurl; + + if (file.embedType == 'image') { + return ''; + } + if (file.embedType == 'audio' || file.embedType == 'video') { + return '<' + file.embedType + ' controls title="' + file.filename + '" src="' + path + '">' + + '' + + ''; + } + } + + return ''; + } + /** * Get the URL of the icon of an extension. * From e1e2b839332bef8b29ceaecf269b1939ada7a87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 25 Oct 2019 12:32:28 +0200 Subject: [PATCH 4/6] MOBILE-3205 forum: Save forum discussion sorting --- .../mod/forum/pages/discussion/discussion.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index dec2adf0a..b4b5d424d 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -131,13 +131,17 @@ export class AddonModForumDiscussionPage implements OnDestroy { * View loaded. */ ionViewDidLoad(): void { - this.fetchPosts(true, false, true).then(() => { - if (this.postId) { - // Scroll to the post. - setTimeout(() => { - this.domUtils.scrollToElementBySelector(this.content, '#addon-mod_forum-post-' + this.postId); - }); - } + this.sitesProvider.getCurrentSite().getLocalSiteConfig('AddonModForumDiscussionSort', this.sort).then((value) => { + this.sort = value; + }).finally(() => { + this.fetchPosts(true, false, true).then(() => { + if (this.postId) { + // Scroll to the post. + setTimeout(() => { + this.domUtils.scrollToElementBySelector(this.content, '#addon-mod_forum-post-' + this.postId); + }); + } + }); }); } @@ -513,6 +517,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { changeSort(type: SortType): Promise { this.discussionLoaded = false; this.sort = type; + this.sitesProvider.getCurrentSite().setLocalSiteConfig('AddonModForumDiscussionSort', this.sort); this.domUtils.scrollToTop(this.content); return this.fetchPosts(); From 4bc90e892ad3d19739d97e8cf4bb62fca0cbbe36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 25 Oct 2019 13:10:29 +0200 Subject: [PATCH 5/6] MOBILE-3205 forum: Adapt forum discussion view --- .../components/post/addon-mod-forum-post.html | 168 +++++++++--------- src/addon/mod/forum/components/post/post.scss | 9 +- .../forum/pages/discussion/discussion.html | 4 +- .../forum/pages/discussion/discussion.scss | 2 +- 4 files changed, 96 insertions(+), 87 deletions(-) diff --git a/src/addon/mod/forum/components/post/addon-mod-forum-post.html b/src/addon/mod/forum/components/post/addon-mod-forum-post.html index 0b91ce3d3..600f96f30 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 @@ -1,89 +1,91 @@ - - -
-

- - - -

- - - -
-