Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/langindex.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 5 additions & 2 deletions src/addon/mod/forum/components/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 2 additions & 0 deletions src/addon/mod/forum/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
4 changes: 3 additions & 1 deletion src/addon/mod/forum/pages/discussion/discussion.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>

Expand Down
86 changes: 65 additions & 21 deletions src/addon/mod/forum/pages/discussion/discussion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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.
Expand All @@ -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) => {
Expand Down Expand Up @@ -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;
Expand All @@ -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) => {
Expand Down Expand Up @@ -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.
*/
Expand Down
23 changes: 23 additions & 0 deletions src/addon/mod/forum/providers/forum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:';
Expand Down Expand Up @@ -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.
*
Expand Down
35 changes: 35 additions & 0 deletions src/addon/mod/forum/providers/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {}

/**
Expand Down Expand Up @@ -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.
*
Expand Down
2 changes: 2 additions & 0 deletions src/assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down