MOBILE-4031 forum: Display group selector if supported

main
Dani Palou 2022-05-26 10:59:59 +02:00
parent 9799ceeba6
commit 2a87212e98
11 changed files with 384 additions and 150 deletions

View File

@ -654,6 +654,8 @@
"addon.mod_forum.numreplies": "local_moodlemobileapp",
"addon.mod_forum.pindiscussion": "forum",
"addon.mod_forum.pinupdated": "forum",
"addon.mod_forum.postaddedsuccess": "forum",
"addon.mod_forum.postingroup": "local_moodlemobileapp",
"addon.mod_forum.postisprivatereply": "forum",
"addon.mod_forum.posttoforum": "forum",
"addon.mod_forum.posttomygroups": "forum",

View File

@ -15,8 +15,11 @@
import { Params } from '@angular/router';
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
import { CoreUser } from '@features/user/services/user';
import { CoreGroupInfo, CoreGroups } from '@services/groups';
import { CoreUtils } from '@services/utils/utils';
import {
AddonModForum,
AddonModForumCanAddDiscussion,
AddonModForumData,
AddonModForumDiscussion,
AddonModForumProvider,
@ -35,7 +38,12 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
forum?: AddonModForumData;
trackPosts = false;
usesGroups = false;
supportsChangeGroup = false;
selectedSortOrder: AddonModForumSortOrder | null = null;
groupId = 0;
groupInfo?: CoreGroupInfo;
allPartsPermissions?: AddonModForumCanAddDiscussion;
canAddDiscussionToGroup = true;
constructor(courseId: number, cmId: number, discussionsPathPrefix: string) {
super();
@ -94,12 +102,20 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
* @inheritdoc
*/
getItemQueryParams(discussion: AddonModForumDiscussionItem): Params {
return {
const params: Params = {
courseId: this.COURSE_ID,
cmId: this.CM_ID,
forumId: this.forum?.id,
...(this.isOnlineDiscussion(discussion) ? { discussion, trackPosts: this.trackPosts } : {}),
};
if (this.isOnlineDiscussion(discussion)) {
params.discussion = discussion;
params.trackPosts = this.trackPosts;
} else if (this.isNewDiscussionForm(discussion)) {
params.groupId = this.usesGroups ? this.groupId : undefined;
}
return params;
}
/**
@ -133,6 +149,42 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
}
}
/**
* Load group info.
*/
async loadGroupInfo(forumId: number): Promise<void> {
[this.groupInfo, this.allPartsPermissions] = await Promise.all([
CoreGroups.getActivityGroupInfo(this.CM_ID, false),
CoreUtils.ignoreErrors(AddonModForum.canAddDiscussionToAll(forumId, { cmId: this.CM_ID })),
]);
this.supportsChangeGroup = AddonModForum.isGetDiscussionPostsAvailable();
this.usesGroups = !!(this.groupInfo.separateGroups || this.groupInfo.visibleGroups);
this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo);
await this.loadSelectedGroupData();
}
/**
* Load some specific data for current group.
*
* @return Promise resolved when done.
*/
async loadSelectedGroupData(): Promise<void> {
if (!this.usesGroups) {
this.canAddDiscussionToGroup = true;
} else if (this.groupId === 0) {
this.canAddDiscussionToGroup = !this.allPartsPermissions || this.allPartsPermissions.status;
} else if (this.forum) {
const addDiscussionData = await AddonModForum.canAddDiscussion(this.forum.id, this.groupId, { cmId: this.CM_ID });
this.canAddDiscussionToGroup = addDiscussionData.status;
} else {
// Shouldn't happen, assume the user can.
this.canAddDiscussionToGroup = true;
}
}
/**
* @inheritdoc
*/
@ -174,6 +226,7 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
cmId: this.forum.cmid,
sortOrder: this.selectedSortOrder.value,
page,
groupId: this.groupId,
});
let discussions = response.discussions;
@ -252,6 +305,36 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
return offlineDiscussions;
}
/**
* Invalidate cache data.
*
* @return Promise resolved when done.
*/
async invalidateCache(): Promise<void> {
const promises: Promise<void>[] = [];
promises.push(AddonModForum.invalidateForumData(this.COURSE_ID));
if (this.forum) {
promises.push(AddonModForum.invalidateDiscussionsList(this.forum.id));
promises.push(AddonModForum.invalidateCanAddDiscussion(this.forum.id));
promises.push(CoreGroups.invalidateActivityGroupInfo(this.forum.cmid));
}
await Promise.all(promises);
}
/**
* Invalidate list cache data.
*
* @return Promise resolved when done.
*/
async invalidateList(): Promise<void> {
if (this.forum) {
await AddonModForum.invalidateDiscussionsList(this.forum.id);
}
}
}
/**

View File

@ -23,6 +23,20 @@
</ion-item>
</core-course-module-info>
<ion-item class="ion-text-wrap core-group-selector" lines="none"
*ngIf="supportsChangeGroup && groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
<ion-label id="addon-forum-groupslabel">
<ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container>
<ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container>
</ion-label>
<ion-select [(ngModel)]="groupId" (ionChange)="groupChanged()" aria-labelledby="addon-forum-groupslabel"
interface="action-sheet" [interfaceOptions]="{header: 'core.group' | translate}">
<ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">
{{groupOpt.name}}
</ion-select-option>
</ion-select>
</ion-item>
<!-- Cut-off date or due date message -->
<ion-card class="core-info-card" *ngIf="availabilityMessage">
<ion-item>
@ -39,6 +53,17 @@
</ion-item>
</ion-card>
<!-- Cannot add discussion to group messages. -->
<ion-card class="core-info-card" *ngIf="usesGroups && canAddDiscussion && !canAddDiscussionToGroup">
<ion-item>
<ion-icon name="fas-info-circle" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<span *ngIf="groupId <= 0">{{ 'addon.mod_forum.cannotadddiscussionall' | translate }}</span>
<span *ngIf="groupId > 0">{{ 'addon.mod_forum.cannotadddiscussion' | translate }}</span>
</ion-label>
</ion-item>
</ion-card>
<ng-container *ngIf="forum">
<core-empty-box *ngIf="!discussions || !discussions.hasDiscussions" icon="far-comments"
[message]="'addon.mod_forum.forumnodiscussionsyet' | translate">
@ -128,7 +153,7 @@
<core-course-module-navigation collapsible-footer [hidden]="showLoading" [courseId]="courseId" [currentModuleId]="module.id">
</core-course-module-navigation>
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="forum && canAddDiscussion">
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAddDiscussionToGroup">
<ion-fab-button (click)="openNewDiscussion()" [attr.aria-label]="addDiscussionText">
<ion-icon name="fas-plus" aria-hidden="true"></ion-icon>
<span class="sr-only">{{ addDiscussionText }}</span>

View File

@ -52,4 +52,8 @@
}
.core-group-selector {
border-top: 1px solid var(--spacer-color);
}
}

View File

@ -31,7 +31,7 @@ import { AddonModForumOffline } from '@addons/mod/forum/services/forum-offline';
import { Translate } from '@singletons';
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
import { CoreGroups, CoreGroupsProvider } from '@services/groups';
import { CoreGroupInfo } from '@services/groups';
import { CoreEvents, CoreEventObserver } from '@singletons/events';
import {
AddonModForumAutoSyncData,
@ -42,7 +42,6 @@ import {
import { CoreSites } from '@services/sites';
import { CoreUser } from '@features/user/services/user';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { CoreCourse } from '@features/course/services/course';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu';
@ -82,9 +81,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
availabilityMessage: string | null = null;
sortingAvailable!: boolean;
sortOrders: AddonModForumSortOrder[] = [];
canPin = false;
hasOfflineRatings = false;
showQAMessage = false;
isSetPinAvailable = false;
sortOrderSelectorModalOptions: ModalOptions = {
component: AddonModForumSortOrderSelectorComponent,
};
@ -123,6 +122,36 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
return this.discussions?.getSource().selectedSortOrder ?? undefined;
}
get supportsChangeGroup(): boolean {
return this.discussions?.getSource().supportsChangeGroup ?? false;
}
get groupId(): number {
return this.discussions?.getSource().groupId ?? 0;
}
set groupId(value: number) {
if (this.discussions) {
this.discussions.getSource().groupId = value;
}
}
get groupInfo(): CoreGroupInfo | undefined {
return this.discussions?.getSource().groupInfo;
}
get usesGroups(): boolean {
return !!(this.discussions?.getSource().usesGroups);
}
get canPin(): boolean {
return !!(this.isSetPinAvailable && this.discussions?.getSource().allPartsPermissions?.canpindiscussions);
}
get canAddDiscussionToGroup(): boolean {
return !!(this.forum && this.canAddDiscussion && this.discussions?.getSource().canAddDiscussionToGroup);
}
/**
* Check whether a discussion is online.
*
@ -150,6 +179,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewdiscussion');
this.sortingAvailable = AddonModForum.isDiscussionListSortingAvailable();
this.sortOrders = AddonModForum.getAvailableSortOrders();
this.isSetPinAvailable = AddonModForum.isSetPinStateAvailableForSite();
this.sortOrderSelectorModalOptions.componentProps = {
sortOrders: this.sortOrders,
@ -383,19 +413,10 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
const promises: Promise<void>[] = [];
// Check if the activity uses groups.
promises.push(
CoreGroups.instance
.getActivityGroupMode(forum.cmid)
.then(async mode => {
discussions.getSource().usesGroups =
mode === CoreGroupsProvider.SEPARATEGROUPS || mode === CoreGroupsProvider.VISIBLEGROUPS;
return;
}),
);
promises.push(discussions.getSource().loadGroupInfo(forum.id));
promises.push(
AddonModForum.instance
AddonModForum
.getAccessInformation(forum.id, { cmId: this.module.id })
.then(async accessInfo => {
// Disallow adding discussions if cut-off date is reached and the user has not the
@ -410,26 +431,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
}),
);
if (AddonModForum.isSetPinStateAvailableForSite()) {
// Use the canAddDiscussion WS to check if the user can pin discussions.
promises.push(
AddonModForum.instance
.canAddDiscussionToAll(forum.id, { cmId: this.module.id })
.then(async response => {
this.canPin = !!response.canpindiscussions;
return;
})
.catch(async () => {
this.canPin = false;
return;
}),
);
} else {
this.canPin = false;
}
await Promise.all(promises);
}
@ -461,21 +462,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
* @return Promise resolved when done.
*/
protected async fetchSortOrderPreference(): Promise<void> {
const getSortOrder = async () => {
if (!this.sortingAvailable) {
return null;
}
const value = await CoreUtils.ignoreErrors(
CoreUser.getUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER),
);
return value ? parseInt(value, 10) : null;
};
const discussions = await this.promisedDiscussions;
const value = await getSortOrder();
const selectedOrder = this.sortOrders.find(sortOrder => sortOrder.value === value) || this.sortOrders[0];
const selectedOrder = await AddonModForum.getSelectedSortOrder();
discussions.getSource().selectedSortOrder = selectedOrder;
@ -492,11 +480,11 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
protected async invalidateContent(): Promise<void> {
const promises: Promise<void>[] = [];
promises.push(AddonModForum.invalidateForumData(this.courseId));
if (this.discussions) {
promises.push(this.discussions.getSource().invalidateCache());
}
if (this.forum) {
promises.push(AddonModForum.invalidateDiscussionsList(this.forum.id));
promises.push(CoreGroups.invalidateActivityGroupMode(this.forum.cmid));
promises.push(AddonModForum.invalidateAccessInformation(this.forum.id));
}
@ -545,36 +533,52 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
* @param isNewDiscussion Whether it's a new discussion event.
* @param data Event data.
*/
protected eventReceived(
protected async eventReceived(
isNewDiscussion: boolean,
data: AddonModForumNewDiscussionData | AddonModForumReplyDiscussionData,
): void {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) {
this.showLoadingAndRefresh(false).finally(() => {
// If it's a new discussion in tablet mode, try to open it.
if (isNewDiscussion && CoreScreen.isTablet) {
const newDiscussionData = data as AddonModForumNewDiscussionData;
const discussion = this.discussions?.items.find(disc => {
if (this.discussions?.getSource().isOfflineDiscussion(disc)) {
return disc.timecreated === newDiscussionData.discTimecreated;
}
if (this.discussions?.getSource().isOnlineDiscussion(disc)) {
return (newDiscussionData.discussionIds ?? []).includes(disc.discussion);
}
return false;
});
if (this.discussions && (discussion || !this.discussions.empty)) {
this.discussions.select(discussion ?? this.discussions.items[0]);
}
}
});
// Check completion since it could be configured to complete once the user adds a new discussion or replies.
this.checkCompletion();
): Promise<void> {
if ((!this.forum || this.forum.id !== data.forumId) && data.cmId !== this.module.id) {
return; // Not current forum.
}
// Check completion since it could be configured to complete once the user adds a new discussion or replies.
this.checkCompletion();
try {
if (isNewDiscussion) {
CoreDomUtils.showToast('addon.mod_forum.postaddedsuccess', true);
const newDiscGroupId = (data as AddonModForumNewDiscussionData).groupId;
if (!newDiscGroupId || newDiscGroupId < 0 || !this.groupId || newDiscGroupId === this.groupId) {
await this.showLoadingAndRefresh(false);
} else {
// Discussion is in a different group than the one currently viewed, only invalidate data.
await this.discussions?.getSource().invalidateList();
}
} else {
await this.showLoadingAndRefresh(false);
}
} finally {
// If it's a new discussion in tablet mode, try to open it.
if (isNewDiscussion && CoreScreen.isTablet && this.discussions) {
const newDiscussionData = data as AddonModForumNewDiscussionData;
const discussion = this.discussions.items.find(disc => {
if (this.discussions?.getSource().isOfflineDiscussion(disc)) {
return disc.timecreated === newDiscussionData.discTimecreated;
}
if (this.discussions?.getSource().isOnlineDiscussion(disc)) {
return (newDiscussionData.discussionIds ?? []).includes(disc.discussion);
}
return false;
});
this.discussions.select(discussion ?? null);
}
}
}
/**
@ -661,6 +665,24 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
}
}
/**
* Group has changed.
*/
async groupChanged(): Promise<void> {
const modal = await CoreDomUtils.showModalLoading();
try {
await Promise.all([
this.discussions?.getSource().loadSelectedGroupData(),
this.discussions?.reload(),
]);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true);
} finally {
modal.dismiss();
}
}
}
/**
@ -696,7 +718,7 @@ class AddonModForumDiscussionsManager extends CoreListItemsManager<AddonModForum
}
try {
await AddonModForum.instance.logView(forum.id, forum.name);
await AddonModForum.logView(forum.id, forum.name);
CoreCourse.checkModuleCompletion(this.page.courseId, this.page.module.completiondata);
} catch {

View File

@ -45,6 +45,8 @@
"numreplies": "{{numreplies}} replies",
"pindiscussion": "Pin this discussion",
"pinupdated": "The pin option has been updated.",
"postaddedsuccess": "Your post was successfully added.",
"postingroup": "Posting in group \"{{groupname}}\".",
"postisprivatereply": "This is a private reply. It is only visible to you and anyone with the capability to view private replies, such as teachers or managers.",
"posttoforum": "Post to forum",
"posttomygroups": "Post a copy to all groups",

View File

@ -49,7 +49,7 @@
<ion-label id="addon-mod-forum-groupslabel">{{ 'addon.mod_forum.group' | translate }}</ion-label>
<ion-select [(ngModel)]="newDiscussion.groupId" [disabled]="newDiscussion.postToAllGroups"
aria-labelledby="addon-mod-forum-groupslabel" interface="action-sheet" name="groupid"
[interfaceOptions]="{header: 'addon.mod_forum.group' | translate}">
[interfaceOptions]="{header: 'addon.mod_forum.group' | translate}" (ionChange)="calculateGroupName()">
<ion-select-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-select-option>
</ion-select>
</ion-item>
@ -66,7 +66,11 @@
[allowOffline]="true" [courseId]="courseId">
</core-attachments>
</div>
<ion-item>
<ion-item *ngIf="showGroups && groupName && !newDiscussion.postToAllGroups" class="addon-forum-group-info">
<ion-icon name="fas-info-circle" slot="start" aria-hidden="true"></ion-icon>
<ion-label>{{ 'addon.mod_forum.postingroup' | translate:{groupname: groupName} }}</ion-label>
</ion-item>
<ion-item class="addon-forum-new-discussion-buttons">
<ion-label>
<ion-row>
<ion-col *ngIf="hasOffline">

View File

@ -61,6 +61,7 @@ type NewDiscussionData = {
@Component({
selector: 'page-addon-mod-forum-new-discussion',
templateUrl: 'new-discussion.html',
styleUrls: ['new-discussion.scss'],
})
export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLeave {
@ -91,6 +92,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
advanced = false; // Display all form fields.
accessInfo: AddonModForumAccessInformation = {};
courseId!: number;
groupName?: string;
discussions?: AddonModForumNewDiscussionDiscussionsSwipeManager;
@ -102,6 +104,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
protected isDestroyed = false;
protected originalData?: Partial<NewDiscussionData>;
protected forceLeave = false;
protected initialGroupId?: number;
constructor(protected route: ActivatedRoute, @Optional() protected splitView: CoreSplitViewComponent) {}
@ -115,6 +118,10 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
this.forumId = CoreNavigator.getRequiredRouteNumberParam('forumId');
this.timeCreated = CoreNavigator.getRequiredRouteNumberParam('timeCreated');
this.initialGroupId = CoreNavigator.getRouteNumberParam('groupId');
// Discussion list uses 0 for all participants, but this page WebServices use a different value. Convert it.
this.initialGroupId = this.initialGroupId === 0 ? AddonModForumProvider.ALL_PARTICIPANTS : this.initialGroupId;
if (this.timeCreated !== 0 && (routeData.swipeEnabled ?? true)) {
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
@ -188,8 +195,9 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
this.groups = forumGroups;
this.groupIds = forumGroups.map((group) => group.id).filter((id) => id > 0);
// Do not override group id.
this.newDiscussion.groupId = this.newDiscussion.groupId || forumGroups[0].id;
this.newDiscussion.groupId = this.newDiscussion.groupId || this.getInitialGroupId();
this.showGroups = true;
this.calculateGroupName();
if (this.groupIds.length <= 1) {
this.newDiscussion.postToAllGroups = false;
}
@ -263,6 +271,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
this.newDiscussion.subscribe = !!discussion.options.discussionsubscribe;
this.newDiscussion.pin = !!discussion.options.discussionpinned;
this.messageControl.setValue(discussion.message);
this.calculateGroupName();
// Treat offline attachments if any.
if (typeof discussion.options.attachmentsid === 'object' && discussion.options.attachmentsid.offline) {
@ -377,6 +386,16 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
return forumGroups.filter(forumGroup => userGroupsIds.indexOf(forumGroup.id) > -1);
}
/**
* Get the initial group ID.
*
* @return Initial group ID.
*/
protected getInitialGroupId(): number {
return (this.initialGroupId && this.groups.find(group => group.id === this.initialGroupId)) ?
this.initialGroupId : this.groups[0].id;
}
/**
* Add the "All participants" option to a list of groups if the user can add a discussion to all participants.
*
@ -453,6 +472,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
cmId: this.cmId,
discussionIds: discussionIds,
discTimecreated: discTimecreated,
groupId: this.showGroups && !this.newDiscussion.postToAllGroups ? this.newDiscussion.groupId : undefined,
},
CoreSites.getCurrentSiteId(),
);
@ -588,6 +608,17 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
this.advanced = !this.advanced;
}
/**
* Calculate current group's name.
*/
calculateGroupName(): void {
if (this.newDiscussion.groupId <= 0) {
this.groupName = undefined;
} else {
this.groupName = this.groups.find(group => group.id === this.newDiscussion.groupId)?.name;
}
}
/**
* Check if we can leave the page or not.
*

View File

@ -0,0 +1,22 @@
@import "~theme/globals";
:host {
.addon-forum-group-info {
> ion-icon[slot] {
color: var(--ion-color-info);
@include margin-horizontal(null, 16px);
}
}
.addon-forum-new-discussion-buttons {
ion-label {
margin-top: 0;
}
ion-col {
padding-top: 0;
padding-bottom: 0;
}
}
}

View File

@ -159,19 +159,33 @@ export class AddonModForumProvider {
return ROOT_CACHE_KEY + 'discussion:' + discussionId;
}
/**
* Get common cache key for forum discussions list WS calls.
*
* @param forumId Forum ID.
* @return Cache key.
*/
protected getDiscussionsListCommonCacheKey(forumId: number): string {
return ROOT_CACHE_KEY + 'discussions:' + forumId;
}
/**
* Get cache key for forum discussions list WS calls.
*
* @param forumId Forum ID.
* @param sortOrder Sort order.
* @param groupId Group ID.
* @return Cache key.
*/
protected getDiscussionsListCacheKey(forumId: number, sortOrder: number): string {
let key = ROOT_CACHE_KEY + 'discussions:' + forumId;
protected getDiscussionsListCacheKey(forumId: number, sortOrder: number, groupId?: number): string {
let key = this.getDiscussionsListCommonCacheKey(forumId);
if (sortOrder != AddonModForumProvider.SORTORDER_LASTPOST_DESC) {
key += ':' + sortOrder;
}
if (groupId) {
key += `:group${groupId}`;
}
return key;
}
@ -700,6 +714,26 @@ export class AddonModForumProvider {
return sortOrders;
}
/**
* Get sort order selected by the user.
*
* @return Promise resolved with sort order.
*/
async getSelectedSortOrder(): Promise<AddonModForumSortOrder> {
const sortOrders = this.getAvailableSortOrders();
let sortOrderValue: number | null = null;
if (this.isDiscussionListSortingAvailable()) {
const preferenceValue = await CoreUtils.ignoreErrors(
CoreUser.getUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER),
);
sortOrderValue = preferenceValue ? parseInt(preferenceValue, 10) : null;
}
return sortOrders.find(sortOrder => sortOrder.value === sortOrderValue) || sortOrders[0];
}
/**
* Get forum discussions.
*
@ -729,6 +763,7 @@ export class AddonModForumProvider {
// Since Moodle 3.7.
method = 'mod_forum_get_forum_discussions';
(params as AddonModForumGetForumDiscussionsWSParams).sortorder = options.sortOrder;
(params as AddonModForumGetForumDiscussionsWSParams).groupid = options.groupId;
} else {
if (options.sortOrder !== AddonModForumProvider.SORTORDER_LASTPOST_DESC) {
throw new Error('Sorting not supported with the old WS method.');
@ -945,10 +980,7 @@ export class AddonModForumProvider {
async invalidateDiscussionsList(forumId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await CoreUtils.allPromises(
this.getAvailableSortOrders()
.map(sortOrder => site.invalidateWsCacheForKey(this.getDiscussionsListCacheKey(forumId, sortOrder.value))),
);
await site.invalidateWsCacheForKeyStartingWith(this.getDiscussionsListCommonCacheKey(forumId));
}
/**
@ -1499,6 +1531,7 @@ export type AddonModForumLegacyPost = {
export type AddonModForumGetDiscussionsOptions = CoreCourseCommonModWSOptions & {
sortOrder?: number; // Sort order.
page?: number; // Page. Defaults to 0.
groupId?: number; // Group ID.
};
/**
@ -2079,6 +2112,7 @@ export type AddonModForumNewDiscussionData = {
cmId: number;
discussionIds?: number[] | null;
discTimecreated?: number;
groupId?: number; // The discussion group if it's created in a certain group, ALL_PARTICIPANTS for all participants.
};
/**

View File

@ -95,51 +95,80 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
* @param options Other options.
* @return Promise resolved with array of posts.
*/
protected getPostsForPrefetch(
protected async getPostsForPrefetch(
forum: AddonModForumData,
options: CoreCourseCommonModWSOptions = {},
): Promise<AddonModForumPost[]> {
const promises = AddonModForum.getAvailableSortOrders().map((sortOrder) => {
// Only prefetch selected sort order.
const sortOrder = await AddonModForum.getSelectedSortOrder();
const groupsIds = await this.getGroupsIdsToPrefetch(forum);
const results = await Promise.all(groupsIds.map(async (groupId) => {
// Get discussions in first 2 pages.
const discussionsOptions = {
sortOrder: sortOrder.value,
groupId: groupId,
numPages: 2,
...options, // Include all options.
};
return AddonModForum.getDiscussionsInPages(forum.id, discussionsOptions).then((response) => {
if (response.error) {
throw new Error('Failed getting discussions');
}
const response = await AddonModForum.getDiscussionsInPages(forum.id, discussionsOptions);
const promises: Promise<{ posts: AddonModForumPost[] }>[] = [];
if (response.error) {
throw new Error('Failed getting discussions');
}
response.discussions.forEach((discussion) => {
promises.push(AddonModForum.getDiscussionPosts(discussion.discussion, options));
return await Promise.all(
response.discussions.map((discussion) => AddonModForum.getDiscussionPosts(discussion.discussion, options)),
);
}));
const posts: AddonModForumPost[] = [];
const postIds: Record<number, boolean> = {}; // To make the array unique.
results.forEach((groupResults) => {
groupResults.forEach((groupDiscussion) => {
groupDiscussion.posts.forEach((post) => {
if (!postIds[post.id]) {
postIds[post.id] = true;
posts.push(post);
}
});
return Promise.all(promises);
});
});
return Promise.all(promises).then((results) => {
// Each order has returned its own list of posts. Merge all the lists, preventing duplicates.
const posts: AddonModForumPost[] = [];
const postIds = {}; // To make the array unique.
return posts;
}
results.forEach((orderResults) => {
orderResults.forEach((orderResult) => {
orderResult.posts.forEach((post) => {
if (!postIds[post.id]) {
postIds[post.id] = true;
posts.push(post);
}
});
});
});
/**
* Get the group IDs to prefetch in a forum.
* Prefetch all participants if the user can view them. Otherwise, prefetch the groups the user can view.
*
* @param forum Forum instance.
* @return Promise resolved with array of group IDs.
*/
protected async getGroupsIdsToPrefetch(forum: AddonModForumData): Promise<number[]> {
const groupInfo = await CoreGroups.getActivityGroupInfo(forum.cmid);
return posts;
});
const supportsChangeGroup = AddonModForum.isGetDiscussionPostsAvailable();
const usesGroups = !!(groupInfo.separateGroups || groupInfo.visibleGroups);
if (!usesGroups) {
return [0];
}
const allPartsGroup = groupInfo.groups?.find(group => group.id === 0);
if (allPartsGroup) {
return [0]; // Prefetch all participants.
}
if (!supportsChangeGroup) {
// Cannot change group, prefetch only the default group.
return [groupInfo.defaultGroupId];
}
return groupInfo.groups?.map(group => group.id) ?? [0];
}
/**
@ -225,11 +254,6 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
// Prefetch access information.
promises.push(AddonModForum.getAccessInformation(forum.id, modOptions));
// Prefetch sort order preference.
if (AddonModForum.isDiscussionListSortingAvailable()) {
promises.push(CoreUser.getUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER, siteId));
}
// Get course data, needed to determine upload max size if it's configured to be course limit.
promises.push(CoreUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId)));
@ -269,34 +293,15 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
// Activity uses groups, prefetch allowed groups.
const result = await CoreGroups.getActivityAllowedGroups(forum.cmid, undefined, siteId);
if (mode === CoreGroupsProvider.SEPARATEGROUPS) {
// Groups are already filtered by WS. Prefetch canAddDiscussionToAll to determine if user can pin/attach.
await CoreUtils.ignoreErrors(AddonModForum.canAddDiscussionToAll(forum.id, options));
return;
}
if (canCreateDiscussions) {
// Prefetch data to check the visible groups when creating discussions.
const response = await CoreUtils.ignoreErrors(
AddonModForum.canAddDiscussionToAll(forum.id, options),
{ status: false },
);
if (response.status) {
// User can post to all groups, nothing else to prefetch.
return;
}
// The user can't post to all groups, let's check which groups he can post to.
await Promise.all(
result.groups.map(
async (group) => CoreUtils.ignoreErrors(
AddonModForum.canAddDiscussion(forum.id, group.id, options),
),
await Promise.all(
result.groups.map(
async (group) => CoreUtils.ignoreErrors(
AddonModForum.canAddDiscussion(forum.id, group.id, options),
),
);
}
).concat(
CoreUtils.ignoreErrors(AddonModForum.canAddDiscussionToAll(forum.id, options)),
),
);
} catch (error) {
// Ignore errors if cannot create discussions.
if (canCreateDiscussions) {