Merge pull request #1887 from albertgasset/MOBILE-2988

MOBILE-2988 forum: Add the ability to lock discussions manually
main
Juan Leyva 2019-05-07 16:41:44 +02:00 committed by GitHub
commit 561dd1d4f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 24 deletions

View File

@ -494,11 +494,13 @@
"addon.mod_forum.errorgetgroups": "local_moodlemobileapp",
"addon.mod_forum.forumnodiscussionsyet": "local_moodlemobileapp",
"addon.mod_forum.group": "local_moodlemobileapp",
"addon.mod_forum.locked": "forum",
"addon.mod_forum.message": "forum",
"addon.mod_forum.modeflatnewestfirst": "forum",
"addon.mod_forum.modeflatoldestfirst": "forum",
"addon.mod_forum.modenested": "forum",
"addon.mod_forum.modulenameplural": "forum",
"addon.mod_forum.notlocked": "forum",
"addon.mod_forum.numdiscussions": "local_moodlemobileapp",
"addon.mod_forum.numreplies": "local_moodlemobileapp",
"addon.mod_forum.postisprivatereply": "forum",

View File

@ -58,6 +58,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
protected replyObserver: any;
protected newDiscObserver: any;
protected viewDiscObserver: any;
protected changeDiscObserver: any;
hasOfflineRatings: boolean;
protected ratingOfflineObserver: any;
@ -94,6 +95,8 @@ 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));
// Select the current opened discussion.
this.viewDiscObserver = this.eventsProvider.on(AddonModForumProvider.VIEW_DISCUSSION_EVENT, (data) => {
@ -446,9 +449,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
courseId: this.courseId,
cmId: this.module.id,
forumId: this.forum.id,
discussionId: discussion.discussion,
discussion: discussion,
trackPosts: this.trackPosts,
locked: discussion.locked
};
this.splitviewCtrl.push('AddonModForumDiscussionPage', params);
}
@ -480,6 +482,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
this.newDiscObserver && this.newDiscObserver.off();
this.replyObserver && this.replyObserver.off();
this.viewDiscObserver && this.viewDiscObserver.off();
this.changeDiscObserver && this.changeDiscObserver.off();
this.ratingOfflineObserver && this.ratingOfflineObserver.off();
this.ratingSyncObserver && this.ratingSyncObserver.off();
}

View File

@ -18,11 +18,13 @@
"errorgetgroups": "Error getting group settings.",
"forumnodiscussionsyet": "There are no discussions yet in this forum.",
"group": "Group",
"locked": "Locked",
"message": "Message",
"modeflatnewestfirst": "Display replies flat, with newest first",
"modeflatoldestfirst": "Display replies flat, with oldest first",
"modenested": "Display replies in nested form",
"modulenameplural": "Forums",
"notlocked": "Lock",
"numdiscussions": "{{numdiscussions}} discussions",
"numreplies": "{{numreplies}} replies",
"postisprivatereply": "This post was made privately and is not visible to all users.",

View File

@ -13,6 +13,8 @@
<core-context-menu-item [hidden]="sort == 'flat-oldest'" [priority]="500" [content]="'addon.mod_forum.modeflatoldestfirst' | translate" (action)="changeSort('flat-oldest')" iconAction="arrow-round-down"></core-context-menu-item>
<core-context-menu-item [hidden]="sort == 'flat-newest'" [priority]="450" [content]="'addon.mod_forum.modeflatnewestfirst' | translate" (action)="changeSort('flat-newest')" iconAction="arrow-round-up"></core-context-menu-item>
<core-context-menu-item [hidden]="sort == 'nested'" [priority]="400" [content]="'addon.mod_forum.modenested' | translate" (action)="changeSort('nested')" iconAction="swap"></core-context-menu-item>
<core-context-menu-item [hidden]="!discussion || !discussion.canlock || discussion.locked" [priority]="300" [content]="'addon.mod_forum.notlocked' | translate" (action)="setLockState(true)" iconAction="fa-unlock"></core-context-menu-item>
<core-context-menu-item [hidden]="!discussion || !discussion.canlock || !discussion.locked" [priority]="300" [content]="'addon.mod_forum.locked' | translate" (action)="setLockState(false)" iconAction="fa-lock"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
<ion-content>
@ -26,7 +28,7 @@
<ion-icon name="warning"></ion-icon> {{ 'core.hasdatatosync' | translate:{$a: discussionStr} }}
</ion-card>
<ion-card class="core-warning-card" *ngIf="locked">
<ion-card class="core-warning-card" *ngIf="discussion && discussion.locked">
<ion-icon name="warning"></ion-icon> {{ 'addon.mod_forum.discussionlocked' | translate }}
</ion-card>

View File

@ -54,7 +54,6 @@ export class AddonModForumDiscussionPage implements OnDestroy {
defaultSubject: string;
isOnline: boolean;
isSplitViewOn: boolean;
locked: boolean;
postHasOffline: boolean;
sort: SortType = 'flat-oldest';
trackPosts: boolean;
@ -108,9 +107,9 @@ export class AddonModForumDiscussionPage implements OnDestroy {
this.courseId = navParams.get('courseId');
this.cmId = navParams.get('cmId');
this.forumId = navParams.get('forumId');
this.discussionId = navParams.get('discussionId');
this.discussion = navParams.get('discussion');
this.discussionId = this.discussion ? this.discussion.discussion : navParams.get('discussionId');
this.trackPosts = navParams.get('trackPosts');
this.locked = navParams.get('locked');
this.postId = navParams.get('postId');
this.isOnline = this.appProvider.isOnline();
@ -222,7 +221,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
}
/**
* Convenience function to get forum discussions.
* Convenience function to get the posts.
*
* @param {boolean} [sync] Whether to try to synchronize the discussion.
* @param {boolean} [showErrors] Whether to show errors in a modal.
@ -243,11 +242,12 @@ export class AddonModForumDiscussionPage implements OnDestroy {
let onlinePosts = [];
const offlineReplies = [];
let hasUnreadPosts = false;
let ratingInfo;
return syncPromise.then(() => {
return this.forumProvider.getDiscussionPosts(this.discussionId).then((response) => {
onlinePosts = response.posts;
this.ratingInfo = response.ratinginfo;
ratingInfo = response.ratinginfo;
}).then(() => {
// Check if there are responses stored in offline.
return this.forumOffline.getDiscussionReplies(this.discussionId).then((replies) => {
@ -285,34 +285,23 @@ export class AddonModForumDiscussionPage implements OnDestroy {
});
});
}).then(() => {
const posts = offlineReplies.concat(onlinePosts);
this.discussion = this.forumProvider.extractStartingPost(posts);
if (!this.discussion) {
return Promise.reject('Invalid forum discussion');
}
let posts = offlineReplies.concat(onlinePosts);
// If sort type is nested, normal sorting is disabled and nested posts will be displayed.
if (this.sort == 'nested') {
// Sort first by creation date to make format tree work.
this.forumProvider.sortDiscussionPosts(posts, 'ASC');
this.posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion.id);
posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion.id);
} else {
// Set default reply subject.
const direction = this.sort == 'flat-newest' ? 'DESC' : 'ASC';
this.forumProvider.sortDiscussionPosts(posts, direction);
this.posts = posts;
}
this.defaultSubject = this.translate.instant('addon.mod_forum.re') + ' ' + this.discussion.subject;
this.replyData.subject = this.defaultSubject;
// Now try to get the forum.
return this.fetchForum().then((forum) => {
if (this.discussion.userfullname && this.discussion.parent == 0 && forum.type == 'single') {
// Hide author for first post and type single.
this.discussion.userfullname = null;
}
// "forum.istracked" is more reliable than "trackPosts".
if (typeof forum.istracked != 'undefined') {
this.trackPosts = forum.istracked;
@ -321,14 +310,44 @@ export class AddonModForumDiscussionPage implements OnDestroy {
this.forumId = forum.id;
this.cmId = forum.cmid;
this.forum = forum;
}).then(() => {
return this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => {
const promises = [];
promises.push(this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => {
this.accessInfo = accessInfo;
});
}));
// Fetch the discussion if not passed as parameter.
if (!this.discussion) {
promises.push(this.forumHelper.getDiscussionById(forum.id, this.discussionId).then((discussion) => {
this.discussion = discussion;
}).catch(() => {
// Ignore errors.
}));
}
return Promise.all(promises);
}).catch(() => {
// Ignore errors.
this.forum = {};
this.accessInfo = {};
}).then(() => {
const startingPost = this.forumProvider.extractStartingPost(posts);
if (startingPost) {
// Update discussion data from first post.
this.discussion = Object.assign(this.discussion || {}, startingPost);
} else if (!this.discussion) {
// The discussion object was not passed as parameter and there is no starting post.
return Promise.reject('Invalid forum discussion.');
}
if (this.discussion.userfullname && this.discussion.parent == 0 && this.forum.type == 'single') {
// Hide author for first post and type single.
this.discussion.userfullname = null;
}
this.posts = posts;
this.ratingInfo = ratingInfo;
});
}).then(() => {
return this.ratingOffline.hasRatings('mod_forum', 'post', 'module', this.cmId, this.discussionId).then((hasRatings) => {
@ -454,6 +473,31 @@ export class AddonModForumDiscussionPage implements OnDestroy {
return this.fetchPosts();
}
/**
* Lock or unlock the discussion.
*
* @param {boolean} 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.discussionId, locked).then((response) => {
this.discussion.locked = response.locked;
const data = {
forumId: this.forumId,
discussionId: this.discussionId,
cmId: this.cmId,
locked: this.discussion.locked
};
this.eventsProvider.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, this.sitesProvider.getCurrentSiteId());
}).catch((error) => {
this.domUtils.showErrorModal(error);
}).finally(() => {
modal.dismiss();
});
}
/**
* New post added.
*/

View File

@ -34,6 +34,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 MARK_READ_EVENT = 'addon_mod_forum_mark_read';
protected ROOT_CACHE_KEY = 'mmaModForum:';
@ -761,6 +762,28 @@ export class AddonModForumProvider {
});
}
/**
* Lock or unlock a discussion.
*
* @param {number} forumId Forum id.
* @param {number} discussionId DIscussion id.
* @param {boolean} locked True to lock, false to unlock.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resvoled when done.
* @since 3.7
*/
setLockState(forumId: number, discussionId: number, locked: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
forumid: forumId,
discussionid: discussionId,
targetstate: locked ? 0 : 1
};
return site.write('mod_forum_set_lock_state', params);
});
}
/**
* Store the users data from a discussions/posts list.
*

View File

@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreFileProvider } from '@providers/file';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModForumProvider } from './forum';
import { AddonModForumOfflineProvider } from './offline';
@ -25,8 +26,10 @@ import { AddonModForumOfflineProvider } from './offline';
@Injectable()
export class AddonModForumHelperProvider {
constructor(private fileProvider: CoreFileProvider,
private sitesProvider: CoreSitesProvider,
private uploaderProvider: CoreFileUploaderProvider,
private userProvider: CoreUserProvider,
private forumProvider: AddonModForumProvider,
private forumOffline: AddonModForumOfflineProvider) {}
/**
@ -119,6 +122,38 @@ export class AddonModForumHelperProvider {
});
}
/**
* Get a forum discussion by id.
*
* This function is inefficient because it needs to fetch all discussion pages in the worst case.
*
* @param {number} forumId Forum ID.
* @param {number} discussionId Discussion ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with the discussion data.
*/
getDiscussionById(forumId: number, discussionId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const findDiscussion = (page: number): Promise<any> => {
return this.forumProvider.getDiscussions(forumId, page, false, siteId).then((response) => {
if (response.discussions && response.discussions.length > 0) {
const discussion = response.discussions.find((discussion) => discussion.id == discussionId);
if (discussion) {
return discussion;
}
if (response.canLoadMore) {
return findDiscussion(page + 1);
}
}
return Promise.reject(null);
});
};
return findDiscussion(0);
}
/**
* Get a list of stored attachment files for a new discussion. See AddonModForumHelper#storeNewDiscussionFiles.
*

View File

@ -494,11 +494,13 @@
"addon.mod_forum.errorgetgroups": "Error getting group settings.",
"addon.mod_forum.forumnodiscussionsyet": "There are no discussions yet in this forum.",
"addon.mod_forum.group": "Group",
"addon.mod_forum.locked": "Locked",
"addon.mod_forum.message": "Message",
"addon.mod_forum.modeflatnewestfirst": "Display replies flat, with newest first",
"addon.mod_forum.modeflatoldestfirst": "Display replies flat, with oldest first",
"addon.mod_forum.modenested": "Display replies in nested form",
"addon.mod_forum.modulenameplural": "Forums",
"addon.mod_forum.notlocked": "Lock",
"addon.mod_forum.numdiscussions": "{{numdiscussions}} discussions",
"addon.mod_forum.numreplies": "{{numreplies}} replies",
"addon.mod_forum.postisprivatereply": "This post was made privately and is not visible to all users.",