commit
51410fd697
|
@ -37,7 +37,14 @@ class behat_app extends behat_app_helper {
|
||||||
protected $ionicurl = '';
|
protected $ionicurl = '';
|
||||||
|
|
||||||
/** @var array Config overrides */
|
/** @var array Config overrides */
|
||||||
protected $appconfig = ['disableUserTours' => true];
|
protected $appconfig = [
|
||||||
|
'disableUserTours' => true,
|
||||||
|
'toastDurations' => [ // Extend toast durations in Behat so they don't disappear too soon.
|
||||||
|
'short' => 7500,
|
||||||
|
'long' => 10000,
|
||||||
|
'sticky' => 0,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
protected $windowsize = '360x720';
|
protected $windowsize = '360x720';
|
||||||
|
|
||||||
|
|
|
@ -104,5 +104,10 @@
|
||||||
},
|
},
|
||||||
"wsrequestqueuelimit": 10,
|
"wsrequestqueuelimit": 10,
|
||||||
"wsrequestqueuedelay": 100,
|
"wsrequestqueuedelay": 100,
|
||||||
"calendarreminderdefaultvalue": 3600
|
"calendarreminderdefaultvalue": 3600,
|
||||||
|
"toastDurations": {
|
||||||
|
"short": 2000,
|
||||||
|
"long": 3500,
|
||||||
|
"sticky": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -654,6 +654,8 @@
|
||||||
"addon.mod_forum.numreplies": "local_moodlemobileapp",
|
"addon.mod_forum.numreplies": "local_moodlemobileapp",
|
||||||
"addon.mod_forum.pindiscussion": "forum",
|
"addon.mod_forum.pindiscussion": "forum",
|
||||||
"addon.mod_forum.pinupdated": "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.postisprivatereply": "forum",
|
||||||
"addon.mod_forum.posttoforum": "forum",
|
"addon.mod_forum.posttoforum": "forum",
|
||||||
"addon.mod_forum.posttomygroups": "forum",
|
"addon.mod_forum.posttomygroups": "forum",
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { AddonCalendarOffline } from '../../services/calendar-offline';
|
||||||
import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync';
|
import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync';
|
||||||
import { CoreNetwork } from '@services/network';
|
import { CoreNetwork } from '@services/network';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreLocalNotifications } from '@services/local-notifications';
|
import { CoreLocalNotifications } from '@services/local-notifications';
|
||||||
|
@ -556,7 +556,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onlineEventDeleted || this.event.id < 0) {
|
if (onlineEventDeleted || this.event.id < 0) {
|
||||||
CoreDomUtils.showToast('addon.calendar.eventcalendareventdeleted', true, 3000);
|
CoreDomUtils.showToast('addon.calendar.eventcalendareventdeleted', true, ToastDuration.LONG);
|
||||||
|
|
||||||
// Event deleted, close the view.
|
// Event deleted, close the view.
|
||||||
CoreNavigator.back();
|
CoreNavigator.back();
|
||||||
|
@ -611,7 +611,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.deleted && data.deleted.indexOf(this.eventId) != -1) {
|
if (data.deleted && data.deleted.indexOf(this.eventId) != -1) {
|
||||||
CoreDomUtils.showToast('addon.calendar.eventcalendareventdeleted', true, 3000);
|
CoreDomUtils.showToast('addon.calendar.eventcalendareventdeleted', true, ToastDuration.LONG);
|
||||||
|
|
||||||
// Event was deleted, close the view.
|
// Event was deleted, close the view.
|
||||||
CoreNavigator.back();
|
CoreNavigator.back();
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { CanLeave } from '@guards/can-leave';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||||
import { CoreSync } from '@services/sync';
|
import { CoreSync } from '@services/sync';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { CoreFormFields, CoreForms } from '@singletons/form';
|
import { CoreFormFields, CoreForms } from '@singletons/form';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
|
@ -467,7 +467,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
|
||||||
async timeUp(): Promise<void> {
|
async timeUp(): Promise<void> {
|
||||||
this.timeUpToast = await CoreDomUtils.showToastWithOptions({
|
this.timeUpToast = await CoreDomUtils.showToastWithOptions({
|
||||||
message: Translate.instant('addon.mod_assign.caneditsubmission'),
|
message: Translate.instant('addon.mod_assign.caneditsubmission'),
|
||||||
duration: 0,
|
duration: ToastDuration.STICKY,
|
||||||
buttons: [Translate.instant('core.dismiss')],
|
buttons: [Translate.instant('core.dismiss')],
|
||||||
cssClass: 'core-danger-toast',
|
cssClass: 'core-danger-toast',
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fi
|
||||||
import { CoreRatingOffline } from '@features/rating/services/rating-offline';
|
import { CoreRatingOffline } from '@features/rating/services/rating-offline';
|
||||||
import { FileEntry } from '@ionic-native/file/ngx';
|
import { FileEntry } from '@ionic-native/file/ngx';
|
||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { CoreFormFields } from '@singletons/form';
|
import { CoreFormFields } from '@singletons/form';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
@ -173,7 +173,11 @@ export class AddonModDataHelperProvider {
|
||||||
|
|
||||||
CoreEvents.trigger(AddonModDataProvider.ENTRY_CHANGED, { dataId: dataId, entryId: entryId }, siteId);
|
CoreEvents.trigger(AddonModDataProvider.ENTRY_CHANGED, { dataId: dataId, entryId: entryId }, siteId);
|
||||||
|
|
||||||
CoreDomUtils.showToast(approve ? 'addon.mod_data.recordapproved' : 'addon.mod_data.recorddisapproved', true, 3000);
|
CoreDomUtils.showToast(
|
||||||
|
approve ? 'addon.mod_data.recordapproved' : 'addon.mod_data.recorddisapproved',
|
||||||
|
true,
|
||||||
|
ToastDuration.LONG,
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore error, it was already displayed.
|
// Ignore error, it was already displayed.
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -725,7 +729,7 @@ export class AddonModDataHelperProvider {
|
||||||
|
|
||||||
CoreEvents.trigger(AddonModDataProvider.ENTRY_CHANGED, { dataId, entryId, deleted: true }, siteId);
|
CoreEvents.trigger(AddonModDataProvider.ENTRY_CHANGED, { dataId, entryId, deleted: true }, siteId);
|
||||||
|
|
||||||
CoreDomUtils.showToast('addon.mod_data.recorddeleted', true, 3000);
|
CoreDomUtils.showToast('addon.mod_data.recorddeleted', true, ToastDuration.LONG);
|
||||||
|
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||||
import { CoreUser } from '@features/user/services/user';
|
import { CoreUser } from '@features/user/services/user';
|
||||||
|
import { CoreGroupInfo, CoreGroups } from '@services/groups';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import {
|
import {
|
||||||
AddonModForum,
|
AddonModForum,
|
||||||
|
AddonModForumCanAddDiscussion,
|
||||||
AddonModForumData,
|
AddonModForumData,
|
||||||
AddonModForumDiscussion,
|
AddonModForumDiscussion,
|
||||||
AddonModForumProvider,
|
AddonModForumProvider,
|
||||||
|
@ -35,7 +38,12 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
|
||||||
forum?: AddonModForumData;
|
forum?: AddonModForumData;
|
||||||
trackPosts = false;
|
trackPosts = false;
|
||||||
usesGroups = false;
|
usesGroups = false;
|
||||||
|
supportsChangeGroup = false;
|
||||||
selectedSortOrder: AddonModForumSortOrder | null = null;
|
selectedSortOrder: AddonModForumSortOrder | null = null;
|
||||||
|
groupId = 0;
|
||||||
|
groupInfo?: CoreGroupInfo;
|
||||||
|
allPartsPermissions?: AddonModForumCanAddDiscussion;
|
||||||
|
canAddDiscussionToGroup = true;
|
||||||
|
|
||||||
constructor(courseId: number, cmId: number, discussionsPathPrefix: string) {
|
constructor(courseId: number, cmId: number, discussionsPathPrefix: string) {
|
||||||
super();
|
super();
|
||||||
|
@ -94,12 +102,20 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
getItemQueryParams(discussion: AddonModForumDiscussionItem): Params {
|
getItemQueryParams(discussion: AddonModForumDiscussionItem): Params {
|
||||||
return {
|
const params: Params = {
|
||||||
courseId: this.COURSE_ID,
|
courseId: this.COURSE_ID,
|
||||||
cmId: this.CM_ID,
|
cmId: this.CM_ID,
|
||||||
forumId: this.forum?.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
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -174,6 +226,7 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
|
||||||
cmId: this.forum.cmid,
|
cmId: this.forum.cmid,
|
||||||
sortOrder: this.selectedSortOrder.value,
|
sortOrder: this.selectedSortOrder.value,
|
||||||
page,
|
page,
|
||||||
|
groupId: this.groupId,
|
||||||
});
|
});
|
||||||
let discussions = response.discussions;
|
let discussions = response.discussions;
|
||||||
|
|
||||||
|
@ -252,6 +305,36 @@ export class AddonModForumDiscussionsSource extends CoreRoutedItemsManagerSource
|
||||||
return offlineDiscussions;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,6 +23,20 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</core-course-module-info>
|
</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 -->
|
<!-- Cut-off date or due date message -->
|
||||||
<ion-card class="core-info-card" *ngIf="availabilityMessage">
|
<ion-card class="core-info-card" *ngIf="availabilityMessage">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
|
@ -39,6 +53,17 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</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">
|
<ng-container *ngIf="forum">
|
||||||
<core-empty-box *ngIf="!discussions || !discussions.hasDiscussions" icon="far-comments"
|
<core-empty-box *ngIf="!discussions || !discussions.hasDiscussions" icon="far-comments"
|
||||||
[message]="'addon.mod_forum.forumnodiscussionsyet' | translate">
|
[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 collapsible-footer [hidden]="showLoading" [courseId]="courseId" [currentModuleId]="module.id">
|
||||||
</core-course-module-navigation>
|
</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-fab-button (click)="openNewDiscussion()" [attr.aria-label]="addDiscussionText">
|
||||||
<ion-icon name="fas-plus" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-plus" aria-hidden="true"></ion-icon>
|
||||||
<span class="sr-only">{{ addDiscussionText }}</span>
|
<span class="sr-only">{{ addDiscussionText }}</span>
|
||||||
|
|
|
@ -52,4 +52,8 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core-group-selector {
|
||||||
|
border-top: 1px solid var(--spacer-color);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { AddonModForumOffline } from '@addons/mod/forum/services/forum-offline';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
||||||
import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
|
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 { CoreEvents, CoreEventObserver } from '@singletons/events';
|
||||||
import {
|
import {
|
||||||
AddonModForumAutoSyncData,
|
AddonModForumAutoSyncData,
|
||||||
|
@ -42,7 +42,6 @@ import {
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUser } from '@features/user/services/user';
|
import { CoreUser } from '@features/user/services/user';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu';
|
import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu';
|
||||||
|
@ -82,9 +81,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
availabilityMessage: string | null = null;
|
availabilityMessage: string | null = null;
|
||||||
sortingAvailable!: boolean;
|
sortingAvailable!: boolean;
|
||||||
sortOrders: AddonModForumSortOrder[] = [];
|
sortOrders: AddonModForumSortOrder[] = [];
|
||||||
canPin = false;
|
|
||||||
hasOfflineRatings = false;
|
hasOfflineRatings = false;
|
||||||
showQAMessage = false;
|
showQAMessage = false;
|
||||||
|
isSetPinAvailable = false;
|
||||||
sortOrderSelectorModalOptions: ModalOptions = {
|
sortOrderSelectorModalOptions: ModalOptions = {
|
||||||
component: AddonModForumSortOrderSelectorComponent,
|
component: AddonModForumSortOrderSelectorComponent,
|
||||||
};
|
};
|
||||||
|
@ -123,6 +122,36 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
return this.discussions?.getSource().selectedSortOrder ?? undefined;
|
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.
|
* Check whether a discussion is online.
|
||||||
*
|
*
|
||||||
|
@ -150,6 +179,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewdiscussion');
|
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewdiscussion');
|
||||||
this.sortingAvailable = AddonModForum.isDiscussionListSortingAvailable();
|
this.sortingAvailable = AddonModForum.isDiscussionListSortingAvailable();
|
||||||
this.sortOrders = AddonModForum.getAvailableSortOrders();
|
this.sortOrders = AddonModForum.getAvailableSortOrders();
|
||||||
|
this.isSetPinAvailable = AddonModForum.isSetPinStateAvailableForSite();
|
||||||
|
|
||||||
this.sortOrderSelectorModalOptions.componentProps = {
|
this.sortOrderSelectorModalOptions.componentProps = {
|
||||||
sortOrders: this.sortOrders,
|
sortOrders: this.sortOrders,
|
||||||
|
@ -383,19 +413,10 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
// Check if the activity uses groups.
|
// Check if the activity uses groups.
|
||||||
promises.push(
|
promises.push(discussions.getSource().loadGroupInfo(forum.id));
|
||||||
CoreGroups.instance
|
|
||||||
.getActivityGroupMode(forum.cmid)
|
|
||||||
.then(async mode => {
|
|
||||||
discussions.getSource().usesGroups =
|
|
||||||
mode === CoreGroupsProvider.SEPARATEGROUPS || mode === CoreGroupsProvider.VISIBLEGROUPS;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
AddonModForum.instance
|
AddonModForum
|
||||||
.getAccessInformation(forum.id, { cmId: this.module.id })
|
.getAccessInformation(forum.id, { cmId: this.module.id })
|
||||||
.then(async accessInfo => {
|
.then(async accessInfo => {
|
||||||
// Disallow adding discussions if cut-off date is reached and the user has not the
|
// 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);
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,21 +462,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async fetchSortOrderPreference(): Promise<void> {
|
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 discussions = await this.promisedDiscussions;
|
||||||
const value = await getSortOrder();
|
const selectedOrder = await AddonModForum.getSelectedSortOrder();
|
||||||
const selectedOrder = this.sortOrders.find(sortOrder => sortOrder.value === value) || this.sortOrders[0];
|
|
||||||
|
|
||||||
discussions.getSource().selectedSortOrder = selectedOrder;
|
discussions.getSource().selectedSortOrder = selectedOrder;
|
||||||
|
|
||||||
|
@ -492,11 +480,11 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
protected async invalidateContent(): Promise<void> {
|
protected async invalidateContent(): Promise<void> {
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
promises.push(AddonModForum.invalidateForumData(this.courseId));
|
if (this.discussions) {
|
||||||
|
promises.push(this.discussions.getSource().invalidateCache());
|
||||||
|
}
|
||||||
|
|
||||||
if (this.forum) {
|
if (this.forum) {
|
||||||
promises.push(AddonModForum.invalidateDiscussionsList(this.forum.id));
|
|
||||||
promises.push(CoreGroups.invalidateActivityGroupMode(this.forum.cmid));
|
|
||||||
promises.push(AddonModForum.invalidateAccessInformation(this.forum.id));
|
promises.push(AddonModForum.invalidateAccessInformation(this.forum.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -545,16 +533,37 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
* @param isNewDiscussion Whether it's a new discussion event.
|
* @param isNewDiscussion Whether it's a new discussion event.
|
||||||
* @param data Event data.
|
* @param data Event data.
|
||||||
*/
|
*/
|
||||||
protected eventReceived(
|
protected async eventReceived(
|
||||||
isNewDiscussion: boolean,
|
isNewDiscussion: boolean,
|
||||||
data: AddonModForumNewDiscussionData | AddonModForumReplyDiscussionData,
|
data: AddonModForumNewDiscussionData | AddonModForumReplyDiscussionData,
|
||||||
): void {
|
): Promise<void> {
|
||||||
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) {
|
if ((!this.forum || this.forum.id !== data.forumId) && data.cmId !== this.module.id) {
|
||||||
this.showLoadingAndRefresh(false).finally(() => {
|
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 it's a new discussion in tablet mode, try to open it.
|
||||||
if (isNewDiscussion && CoreScreen.isTablet) {
|
if (isNewDiscussion && CoreScreen.isTablet && this.discussions) {
|
||||||
const newDiscussionData = data as AddonModForumNewDiscussionData;
|
const newDiscussionData = data as AddonModForumNewDiscussionData;
|
||||||
const discussion = this.discussions?.items.find(disc => {
|
const discussion = this.discussions.items.find(disc => {
|
||||||
if (this.discussions?.getSource().isOfflineDiscussion(disc)) {
|
if (this.discussions?.getSource().isOfflineDiscussion(disc)) {
|
||||||
return disc.timecreated === newDiscussionData.discTimecreated;
|
return disc.timecreated === newDiscussionData.discTimecreated;
|
||||||
}
|
}
|
||||||
|
@ -566,15 +575,10 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.discussions && (discussion || !this.discussions.empty)) {
|
this.discussions.select(discussion ?? null);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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 {
|
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);
|
CoreCourse.checkModuleCompletion(this.page.courseId, this.page.module.completiondata);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
"numreplies": "{{numreplies}} replies",
|
"numreplies": "{{numreplies}} replies",
|
||||||
"pindiscussion": "Pin this discussion",
|
"pindiscussion": "Pin this discussion",
|
||||||
"pinupdated": "The pin option has been updated.",
|
"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.",
|
"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",
|
"posttoforum": "Post to forum",
|
||||||
"posttomygroups": "Post a copy to all groups",
|
"posttomygroups": "Post a copy to all groups",
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
<ion-label id="addon-mod-forum-groupslabel">{{ 'addon.mod_forum.group' | translate }}</ion-label>
|
<ion-label id="addon-mod-forum-groupslabel">{{ 'addon.mod_forum.group' | translate }}</ion-label>
|
||||||
<ion-select [(ngModel)]="newDiscussion.groupId" [disabled]="newDiscussion.postToAllGroups"
|
<ion-select [(ngModel)]="newDiscussion.groupId" [disabled]="newDiscussion.postToAllGroups"
|
||||||
aria-labelledby="addon-mod-forum-groupslabel" interface="action-sheet" name="groupid"
|
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-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-select-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
@ -66,7 +66,11 @@
|
||||||
[allowOffline]="true" [courseId]="courseId">
|
[allowOffline]="true" [courseId]="courseId">
|
||||||
</core-attachments>
|
</core-attachments>
|
||||||
</div>
|
</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-label>
|
||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col *ngIf="hasOffline">
|
<ion-col *ngIf="hasOffline">
|
||||||
|
|
|
@ -61,6 +61,7 @@ type NewDiscussionData = {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-addon-mod-forum-new-discussion',
|
selector: 'page-addon-mod-forum-new-discussion',
|
||||||
templateUrl: 'new-discussion.html',
|
templateUrl: 'new-discussion.html',
|
||||||
|
styleUrls: ['new-discussion.scss'],
|
||||||
})
|
})
|
||||||
export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLeave {
|
export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLeave {
|
||||||
|
|
||||||
|
@ -91,6 +92,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
|
||||||
advanced = false; // Display all form fields.
|
advanced = false; // Display all form fields.
|
||||||
accessInfo: AddonModForumAccessInformation = {};
|
accessInfo: AddonModForumAccessInformation = {};
|
||||||
courseId!: number;
|
courseId!: number;
|
||||||
|
groupName?: string;
|
||||||
|
|
||||||
discussions?: AddonModForumNewDiscussionDiscussionsSwipeManager;
|
discussions?: AddonModForumNewDiscussionDiscussionsSwipeManager;
|
||||||
|
|
||||||
|
@ -102,6 +104,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
|
||||||
protected isDestroyed = false;
|
protected isDestroyed = false;
|
||||||
protected originalData?: Partial<NewDiscussionData>;
|
protected originalData?: Partial<NewDiscussionData>;
|
||||||
protected forceLeave = false;
|
protected forceLeave = false;
|
||||||
|
protected initialGroupId?: number;
|
||||||
|
|
||||||
constructor(protected route: ActivatedRoute, @Optional() protected splitView: CoreSplitViewComponent) {}
|
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.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
||||||
this.forumId = CoreNavigator.getRequiredRouteNumberParam('forumId');
|
this.forumId = CoreNavigator.getRequiredRouteNumberParam('forumId');
|
||||||
this.timeCreated = CoreNavigator.getRequiredRouteNumberParam('timeCreated');
|
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)) {
|
if (this.timeCreated !== 0 && (routeData.swipeEnabled ?? true)) {
|
||||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||||
|
@ -188,8 +195,9 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
|
||||||
this.groups = forumGroups;
|
this.groups = forumGroups;
|
||||||
this.groupIds = forumGroups.map((group) => group.id).filter((id) => id > 0);
|
this.groupIds = forumGroups.map((group) => group.id).filter((id) => id > 0);
|
||||||
// Do not override group id.
|
// 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.showGroups = true;
|
||||||
|
this.calculateGroupName();
|
||||||
if (this.groupIds.length <= 1) {
|
if (this.groupIds.length <= 1) {
|
||||||
this.newDiscussion.postToAllGroups = false;
|
this.newDiscussion.postToAllGroups = false;
|
||||||
}
|
}
|
||||||
|
@ -263,6 +271,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
|
||||||
this.newDiscussion.subscribe = !!discussion.options.discussionsubscribe;
|
this.newDiscussion.subscribe = !!discussion.options.discussionsubscribe;
|
||||||
this.newDiscussion.pin = !!discussion.options.discussionpinned;
|
this.newDiscussion.pin = !!discussion.options.discussionpinned;
|
||||||
this.messageControl.setValue(discussion.message);
|
this.messageControl.setValue(discussion.message);
|
||||||
|
this.calculateGroupName();
|
||||||
|
|
||||||
// Treat offline attachments if any.
|
// Treat offline attachments if any.
|
||||||
if (typeof discussion.options.attachmentsid === 'object' && discussion.options.attachmentsid.offline) {
|
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);
|
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.
|
* 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,
|
cmId: this.cmId,
|
||||||
discussionIds: discussionIds,
|
discussionIds: discussionIds,
|
||||||
discTimecreated: discTimecreated,
|
discTimecreated: discTimecreated,
|
||||||
|
groupId: this.showGroups && !this.newDiscussion.postToAllGroups ? this.newDiscussion.groupId : undefined,
|
||||||
},
|
},
|
||||||
CoreSites.getCurrentSiteId(),
|
CoreSites.getCurrentSiteId(),
|
||||||
);
|
);
|
||||||
|
@ -588,6 +608,17 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
|
||||||
this.advanced = !this.advanced;
|
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.
|
* Check if we can leave the page or not.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -159,19 +159,33 @@ export class AddonModForumProvider {
|
||||||
return ROOT_CACHE_KEY + 'discussion:' + discussionId;
|
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.
|
* Get cache key for forum discussions list WS calls.
|
||||||
*
|
*
|
||||||
* @param forumId Forum ID.
|
* @param forumId Forum ID.
|
||||||
* @param sortOrder Sort order.
|
* @param sortOrder Sort order.
|
||||||
|
* @param groupId Group ID.
|
||||||
* @return Cache key.
|
* @return Cache key.
|
||||||
*/
|
*/
|
||||||
protected getDiscussionsListCacheKey(forumId: number, sortOrder: number): string {
|
protected getDiscussionsListCacheKey(forumId: number, sortOrder: number, groupId?: number): string {
|
||||||
let key = ROOT_CACHE_KEY + 'discussions:' + forumId;
|
let key = this.getDiscussionsListCommonCacheKey(forumId);
|
||||||
|
|
||||||
if (sortOrder != AddonModForumProvider.SORTORDER_LASTPOST_DESC) {
|
if (sortOrder != AddonModForumProvider.SORTORDER_LASTPOST_DESC) {
|
||||||
key += ':' + sortOrder;
|
key += ':' + sortOrder;
|
||||||
}
|
}
|
||||||
|
if (groupId) {
|
||||||
|
key += `:group${groupId}`;
|
||||||
|
}
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
@ -700,6 +714,26 @@ export class AddonModForumProvider {
|
||||||
return sortOrders;
|
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.
|
* Get forum discussions.
|
||||||
*
|
*
|
||||||
|
@ -729,6 +763,7 @@ export class AddonModForumProvider {
|
||||||
// Since Moodle 3.7.
|
// Since Moodle 3.7.
|
||||||
method = 'mod_forum_get_forum_discussions';
|
method = 'mod_forum_get_forum_discussions';
|
||||||
(params as AddonModForumGetForumDiscussionsWSParams).sortorder = options.sortOrder;
|
(params as AddonModForumGetForumDiscussionsWSParams).sortorder = options.sortOrder;
|
||||||
|
(params as AddonModForumGetForumDiscussionsWSParams).groupid = options.groupId;
|
||||||
} else {
|
} else {
|
||||||
if (options.sortOrder !== AddonModForumProvider.SORTORDER_LASTPOST_DESC) {
|
if (options.sortOrder !== AddonModForumProvider.SORTORDER_LASTPOST_DESC) {
|
||||||
throw new Error('Sorting not supported with the old WS method.');
|
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> {
|
async invalidateDiscussionsList(forumId: number, siteId?: string): Promise<void> {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
await CoreUtils.allPromises(
|
await site.invalidateWsCacheForKeyStartingWith(this.getDiscussionsListCommonCacheKey(forumId));
|
||||||
this.getAvailableSortOrders()
|
|
||||||
.map(sortOrder => site.invalidateWsCacheForKey(this.getDiscussionsListCacheKey(forumId, sortOrder.value))),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1499,6 +1531,7 @@ export type AddonModForumLegacyPost = {
|
||||||
export type AddonModForumGetDiscussionsOptions = CoreCourseCommonModWSOptions & {
|
export type AddonModForumGetDiscussionsOptions = CoreCourseCommonModWSOptions & {
|
||||||
sortOrder?: number; // Sort order.
|
sortOrder?: number; // Sort order.
|
||||||
page?: number; // Page. Defaults to 0.
|
page?: number; // Page. Defaults to 0.
|
||||||
|
groupId?: number; // Group ID.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2079,6 +2112,7 @@ export type AddonModForumNewDiscussionData = {
|
||||||
cmId: number;
|
cmId: number;
|
||||||
discussionIds?: number[] | null;
|
discussionIds?: number[] | null;
|
||||||
discTimecreated?: number;
|
discTimecreated?: number;
|
||||||
|
groupId?: number; // The discussion group if it's created in a certain group, ALL_PARTICIPANTS for all participants.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -95,41 +95,41 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
|
||||||
* @param options Other options.
|
* @param options Other options.
|
||||||
* @return Promise resolved with array of posts.
|
* @return Promise resolved with array of posts.
|
||||||
*/
|
*/
|
||||||
protected getPostsForPrefetch(
|
protected async getPostsForPrefetch(
|
||||||
forum: AddonModForumData,
|
forum: AddonModForumData,
|
||||||
options: CoreCourseCommonModWSOptions = {},
|
options: CoreCourseCommonModWSOptions = {},
|
||||||
): Promise<AddonModForumPost[]> {
|
): 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.
|
// Get discussions in first 2 pages.
|
||||||
const discussionsOptions = {
|
const discussionsOptions = {
|
||||||
sortOrder: sortOrder.value,
|
sortOrder: sortOrder.value,
|
||||||
|
groupId: groupId,
|
||||||
numPages: 2,
|
numPages: 2,
|
||||||
...options, // Include all options.
|
...options, // Include all options.
|
||||||
};
|
};
|
||||||
|
|
||||||
return AddonModForum.getDiscussionsInPages(forum.id, discussionsOptions).then((response) => {
|
const response = await AddonModForum.getDiscussionsInPages(forum.id, discussionsOptions);
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error('Failed getting discussions');
|
throw new Error('Failed getting discussions');
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises: Promise<{ posts: AddonModForumPost[] }>[] = [];
|
return await Promise.all(
|
||||||
|
response.discussions.map((discussion) => AddonModForum.getDiscussionPosts(discussion.discussion, options)),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
response.discussions.forEach((discussion) => {
|
|
||||||
promises.push(AddonModForum.getDiscussionPosts(discussion.discussion, options));
|
|
||||||
});
|
|
||||||
|
|
||||||
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 posts: AddonModForumPost[] = [];
|
||||||
const postIds = {}; // To make the array unique.
|
const postIds: Record<number, boolean> = {}; // To make the array unique.
|
||||||
|
|
||||||
results.forEach((orderResults) => {
|
results.forEach((groupResults) => {
|
||||||
orderResults.forEach((orderResult) => {
|
groupResults.forEach((groupDiscussion) => {
|
||||||
orderResult.posts.forEach((post) => {
|
groupDiscussion.posts.forEach((post) => {
|
||||||
if (!postIds[post.id]) {
|
if (!postIds[post.id]) {
|
||||||
postIds[post.id] = true;
|
postIds[post.id] = true;
|
||||||
posts.push(post);
|
posts.push(post);
|
||||||
|
@ -139,7 +139,36 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
|
||||||
});
|
});
|
||||||
|
|
||||||
return posts;
|
return posts;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
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.
|
// Prefetch access information.
|
||||||
promises.push(AddonModForum.getAccessInformation(forum.id, modOptions));
|
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.
|
// 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)));
|
promises.push(CoreUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId)));
|
||||||
|
|
||||||
|
@ -269,34 +293,15 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
|
||||||
|
|
||||||
// Activity uses groups, prefetch allowed groups.
|
// Activity uses groups, prefetch allowed groups.
|
||||||
const result = await CoreGroups.getActivityAllowedGroups(forum.cmid, undefined, siteId);
|
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(
|
await Promise.all(
|
||||||
result.groups.map(
|
result.groups.map(
|
||||||
async (group) => CoreUtils.ignoreErrors(
|
async (group) => CoreUtils.ignoreErrors(
|
||||||
AddonModForum.canAddDiscussion(forum.id, group.id, options),
|
AddonModForum.canAddDiscussion(forum.id, group.id, options),
|
||||||
),
|
),
|
||||||
|
).concat(
|
||||||
|
CoreUtils.ignoreErrors(AddonModForum.canAddDiscussionToAll(forum.id, options)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore errors if cannot create discussions.
|
// Ignore errors if cannot create discussions.
|
||||||
if (canCreateDiscussions) {
|
if (canCreateDiscussions) {
|
||||||
|
|
|
@ -23,6 +23,9 @@ Feature: Test basic usage of forum activity in app
|
||||||
And the following "activities" exist:
|
And the following "activities" exist:
|
||||||
| activity | name | intro | course | idnumber | groupmode | assessed | scale |
|
| activity | name | intro | course | idnumber | groupmode | assessed | scale |
|
||||||
| forum | Test forum name | Test forum | C1 | forum | 0 | 1 | 1 |
|
| forum | Test forum name | Test forum | C1 | forum | 0 | 1 | 1 |
|
||||||
|
And the following "mod_forum > discussions" exist:
|
||||||
|
| forum | name | subject | message |
|
||||||
|
| forum | Initial discussion | Initial discussion | Initial discussion message |
|
||||||
|
|
||||||
Scenario: Create new discussion
|
Scenario: Create new discussion
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
||||||
|
@ -36,105 +39,66 @@ Feature: Test basic usage of forum activity in app
|
||||||
When I press "My happy subject" in the app
|
When I press "My happy subject" in the app
|
||||||
Then I should find "An awesome message" in the app
|
Then I should find "An awesome message" in the app
|
||||||
|
|
||||||
|
Scenario: New discussion automatically opened in tablet
|
||||||
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
||||||
|
And I change viewport size to "1200x640"
|
||||||
|
|
||||||
|
When I press "Add discussion topic" in the app
|
||||||
|
And I set the field "Subject" to "My happy subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "My happy subject" in the app
|
||||||
|
And I should find "An awesome message" inside the split-view content in the app
|
||||||
|
|
||||||
Scenario: Reply a post
|
Scenario: Reply a post
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
||||||
When I press "Add discussion topic" in the app
|
When I press "Initial discussion" in the app
|
||||||
And I set the following fields to these values in the app:
|
And I press "Reply" in the app
|
||||||
| Subject | DiscussionSubject |
|
|
||||||
| Message | DiscussionMessage |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
And I press "DiscussionSubject" in the app
|
|
||||||
Then I should find "Reply" in the app
|
|
||||||
|
|
||||||
When I press "Reply" in the app
|
|
||||||
And I set the field "Message" to "ReplyMessage" in the app
|
And I set the field "Message" to "ReplyMessage" in the app
|
||||||
And I press "Post to forum" in the app
|
And I press "Post to forum" in the app
|
||||||
Then I should find "DiscussionMessage" in the app
|
Then I should find "Initial discussion message" in the app
|
||||||
And I should find "ReplyMessage" in the app
|
And I should find "ReplyMessage" in the app
|
||||||
|
|
||||||
Scenario: Star and pin discussions (student)
|
Scenario: Star and pin discussions (student)
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
||||||
When I press "Add discussion topic" in the app
|
When I press "Display options" near "Initial discussion" in the app
|
||||||
And I set the following fields to these values in the app:
|
|
||||||
| Subject | starred subject |
|
|
||||||
| Message | starred message |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
And I press "Add discussion topic" in the app
|
|
||||||
And I set the following fields to these values in the app:
|
|
||||||
| Subject | normal subject |
|
|
||||||
| Message | normal message |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
And I press "starred subject" in the app
|
|
||||||
Then I should find "starred message" in the app
|
|
||||||
|
|
||||||
When I press the back button in the app
|
|
||||||
And I press "Display options" near "starred subject" in the app
|
|
||||||
And I press "Star this discussion" in the app
|
And I press "Star this discussion" in the app
|
||||||
And I press "starred subject" in the app
|
Then I should find "Your star option has been updated." in the app
|
||||||
Then I should find "starred message" in the app
|
|
||||||
|
|
||||||
When I press the back button in the app
|
When I press "Display options" near "Initial discussion" in the app
|
||||||
And I press "normal subject" in the app
|
|
||||||
Then I should find "normal message" in the app
|
|
||||||
|
|
||||||
When I press the back button in the app
|
|
||||||
And I press "Display options" near "starred subject" in the app
|
|
||||||
And I press "Unstar this discussion" in the app
|
And I press "Unstar this discussion" in the app
|
||||||
And I press "starred subject" in the app
|
Then I should find "Your star option has been updated." in the app
|
||||||
Then I should find "starred message" in the app
|
|
||||||
|
|
||||||
When I press the back button in the app
|
When I press "Display options" near "Initial discussion" in the app
|
||||||
And I press "normal subject" in the app
|
Then I should not find "Pin this discussion" in the app
|
||||||
Then I should find "normal message" in the app
|
|
||||||
|
|
||||||
Scenario: Star and pin discussions (teacher)
|
Scenario: Star and pin discussions (teacher)
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "teacher1" in the app
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "teacher1" in the app
|
||||||
When I press "Add discussion topic" in the app
|
When I press "Display options" near "Initial discussion" in the app
|
||||||
And I set the following fields to these values in the app:
|
|
||||||
| Subject | Auto-test star |
|
|
||||||
| Message | Auto-test star message |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
And I press "Add discussion topic" in the app
|
|
||||||
And I set the following fields to these values in the app:
|
|
||||||
| Subject | Auto-test pin |
|
|
||||||
| Message | Auto-test pin message |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
And I press "Add discussion topic" in the app
|
|
||||||
And I set the following fields to these values in the app:
|
|
||||||
| Subject | Auto-test plain |
|
|
||||||
| Message | Auto-test plain message |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
And I press "Display options" near "Auto-test star" in the app
|
|
||||||
And I press "Star this discussion" in the app
|
And I press "Star this discussion" in the app
|
||||||
And I press "Display options" near "Auto-test pin" in the app
|
Then I should find "Your star option has been updated." in the app
|
||||||
And I press "Pin this discussion" in the app
|
|
||||||
Then I should find "Auto-test pin" in the app
|
|
||||||
And I should find "Auto-test star" in the app
|
|
||||||
And I should find "Auto-test plain" in the app
|
|
||||||
|
|
||||||
When I press "Display options" near "Auto-test pin" in the app
|
When I press "Display options" near "Initial discussion" in the app
|
||||||
And I press "Unpin this discussion" in the app
|
And I press "Pin this discussion" in the app
|
||||||
And I press "Display options" near "Auto-test star" in the app
|
Then I should find "The pin option has been updated." in the app
|
||||||
|
|
||||||
|
When I press "Display options" near "Initial discussion" in the app
|
||||||
And I press "Unstar this discussion" in the app
|
And I press "Unstar this discussion" in the app
|
||||||
Then I should find "Auto-test star" in the app
|
Then I should find "Your star option has been updated." in the app
|
||||||
And I should find "Auto-test pin" in the app
|
|
||||||
|
When I press "Display options" near "Initial discussion" in the app
|
||||||
|
And I press "Unpin this discussion" in the app
|
||||||
|
Then I should find "The pin option has been updated." in the app
|
||||||
|
|
||||||
Scenario: Edit a not sent reply offline
|
Scenario: Edit a not sent reply offline
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
||||||
When I press "Add discussion topic" in the app
|
When I press "Initial discussion" in the app
|
||||||
And I set the following fields to these values in the app:
|
Then I should find "Reply" in the app
|
||||||
| Subject | Auto-test |
|
|
||||||
| Message | Auto-test message |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
And I press "Auto-test" near "Sort by last post creation date in descending order" in the app
|
|
||||||
And I should find "Reply" in the app
|
|
||||||
|
|
||||||
When I press the back button in the app
|
When I press the back button in the app
|
||||||
And I switch offline mode to "true"
|
And I switch offline mode to "true"
|
||||||
And I press "Auto-test" near "Sort by last post creation date in descending order" in the app
|
And I press "Initial discussion" in the app
|
||||||
Then I should find "Reply" in the app
|
And I press "Reply" in the app
|
||||||
|
|
||||||
When I press "Reply" in the app
|
|
||||||
And I set the field "Message" to "not sent reply" in the app
|
And I set the field "Message" to "not sent reply" in the app
|
||||||
And I press "Post to forum" in the app
|
And I press "Post to forum" in the app
|
||||||
And I press "Display options" within "not sent reply" "ion-card" in the app
|
And I press "Display options" within "not sent reply" "ion-card" in the app
|
||||||
|
@ -148,7 +112,7 @@ Feature: Test basic usage of forum activity in app
|
||||||
|
|
||||||
When I switch offline mode to "false"
|
When I switch offline mode to "false"
|
||||||
And I press the back button in the app
|
And I press the back button in the app
|
||||||
And I press "Auto-test" near "Sort by last post creation date in descending order" in the app
|
And I press "Initial discussion" in the app
|
||||||
Then I should not find "Not sent" in the app
|
Then I should not find "Not sent" in the app
|
||||||
And I should not find "This Discussion has offline data to be synchronised" in the app
|
And I should not find "This Discussion has offline data to be synchronised" in the app
|
||||||
|
|
||||||
|
@ -172,7 +136,7 @@ Feature: Test basic usage of forum activity in app
|
||||||
When I press "Post to forum" in the app
|
When I press "Post to forum" in the app
|
||||||
Then I should not find "This Forum has offline data to be synchronised." in the app
|
Then I should not find "This Forum has offline data to be synchronised." in the app
|
||||||
|
|
||||||
When I press "Auto-test" near "Sort by last post creation date in descending order" in the app
|
When I press "Auto-test" in the app
|
||||||
And I should find "Auto-test message edited" in the app
|
And I should find "Auto-test message edited" in the app
|
||||||
|
|
||||||
Scenario: Edit a forum post (only online)
|
Scenario: Edit a forum post (only online)
|
||||||
|
@ -182,17 +146,8 @@ Feature: Test basic usage of forum activity in app
|
||||||
| Subject | Auto-test |
|
| Subject | Auto-test |
|
||||||
| Message | Auto-test message |
|
| Message | Auto-test message |
|
||||||
And I press "Post to forum" in the app
|
And I press "Post to forum" in the app
|
||||||
Then I should find "Auto-test" in the app
|
And I press "Auto-test" in the app
|
||||||
|
And I press "Display options" near "Reply" in the app
|
||||||
When I press the back button in the app
|
|
||||||
And I press "Course downloads" in the app
|
|
||||||
And I press "Download" within "Test forum name" "ion-item" in the app
|
|
||||||
And I press the back button in the app
|
|
||||||
And I press "Test forum name" in the app
|
|
||||||
And I press "Auto-test" near "Sort by last post creation date in descending order" in the app
|
|
||||||
Then I should find "Reply" in the app
|
|
||||||
|
|
||||||
When I press "Display options" near "Reply" in the app
|
|
||||||
Then I should find "Edit" in the app
|
Then I should find "Edit" in the app
|
||||||
|
|
||||||
When I press "Edit" in the app
|
When I press "Edit" in the app
|
||||||
|
@ -201,6 +156,20 @@ Feature: Test basic usage of forum activity in app
|
||||||
And I press "Save changes" in the app
|
And I press "Save changes" in the app
|
||||||
Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app
|
Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app
|
||||||
|
|
||||||
|
When I press "OK" in the app
|
||||||
|
And I press "Cancel" in the app
|
||||||
|
And I press "OK" in the app
|
||||||
|
And I press "Display options" near "Reply" in the app
|
||||||
|
And I press "Edit" in the app
|
||||||
|
Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app
|
||||||
|
|
||||||
|
When I switch offline mode to "false"
|
||||||
|
And I press "OK" in the app
|
||||||
|
And I press "Edit" in the app
|
||||||
|
And I set the field "Message" to "Auto-test message edited" in the app
|
||||||
|
And I press "Save changes" in the app
|
||||||
|
Then I should find "Auto-test message edited" in the app
|
||||||
|
|
||||||
Scenario: Delete a forum post (only online)
|
Scenario: Delete a forum post (only online)
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
||||||
When I press "Add discussion topic" in the app
|
When I press "Add discussion topic" in the app
|
||||||
|
@ -208,17 +177,8 @@ Feature: Test basic usage of forum activity in app
|
||||||
| Subject | Auto-test |
|
| Subject | Auto-test |
|
||||||
| Message | Auto-test message |
|
| Message | Auto-test message |
|
||||||
And I press "Post to forum" in the app
|
And I press "Post to forum" in the app
|
||||||
Then I should find "Auto-test" in the app
|
And I press "Auto-test" in the app
|
||||||
|
And I press "Display options" near "Reply" in the app
|
||||||
When I press the back button in the app
|
|
||||||
And I press "Course downloads" in the app
|
|
||||||
And I press "Download" within "Test forum name" "ion-item" in the app
|
|
||||||
And I press the back button in the app
|
|
||||||
And I press "Test forum name" in the app
|
|
||||||
And I press "Auto-test" near "Sort by last post creation date in descending order" in the app
|
|
||||||
Then I should find "Reply" in the app
|
|
||||||
|
|
||||||
When I press "Display options" near "Reply" in the app
|
|
||||||
Then I should find "Delete" in the app
|
Then I should find "Delete" in the app
|
||||||
|
|
||||||
When I press "Delete" in the app
|
When I press "Delete" in the app
|
||||||
|
@ -246,18 +206,14 @@ Feature: Test basic usage of forum activity in app
|
||||||
| Message | Auto-test message |
|
| Message | Auto-test message |
|
||||||
And I press "Post to forum" in the app
|
And I press "Post to forum" in the app
|
||||||
And I press "Auto-test" in the app
|
And I press "Auto-test" in the app
|
||||||
Then I should find "Reply" in the app
|
And I press "Reply" in the app
|
||||||
|
|
||||||
When I press "Reply" in the app
|
|
||||||
And I set the field "Message" to "test2" in the app
|
And I set the field "Message" to "test2" in the app
|
||||||
And I press "Post to forum" in the app
|
And I press "Post to forum" in the app
|
||||||
Then I should find "test2" "ion-card" in the app
|
Then I should find "test2" "ion-card" in the app
|
||||||
|
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "teacher1" in the app
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "teacher1" in the app
|
||||||
When I press "Auto-test" in the app
|
When I press "Auto-test" in the app
|
||||||
Then I should find "Reply" in the app
|
And I press "None" near "Auto-test message" in the app
|
||||||
|
|
||||||
When I press "None" near "Auto-test message" in the app
|
|
||||||
And I press "1" near "Cancel" in the app
|
And I press "1" near "Cancel" in the app
|
||||||
And I switch offline mode to "true"
|
And I switch offline mode to "true"
|
||||||
And I press "None" near "test2" in the app
|
And I press "None" near "test2" in the app
|
||||||
|
@ -287,31 +243,21 @@ Feature: Test basic usage of forum activity in app
|
||||||
|
|
||||||
Scenario: Reply a post offline
|
Scenario: Reply a post offline
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
||||||
When I press "Add discussion topic" in the app
|
When I press "Initial discussion" in the app
|
||||||
And I set the following fields to these values in the app:
|
|
||||||
| Subject | DiscussionSubject |
|
|
||||||
| Message | DiscussionMessage |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
And I press the back button in the app
|
|
||||||
And I press "Course downloads" in the app
|
|
||||||
And I press "Download" within "Test forum name" "ion-item" in the app
|
|
||||||
And I press the back button in the app
|
|
||||||
And I press "Test forum name" in the app
|
|
||||||
And I press "DiscussionSubject" in the app
|
|
||||||
And I switch offline mode to "true"
|
And I switch offline mode to "true"
|
||||||
Then I should find "Reply" in the app
|
Then I should find "Reply" in the app
|
||||||
|
|
||||||
When I press "Reply" in the app
|
When I press "Reply" in the app
|
||||||
And I set the field "Message" to "ReplyMessage" in the app
|
And I set the field "Message" to "ReplyMessage" in the app
|
||||||
And I press "Post to forum" in the app
|
And I press "Post to forum" in the app
|
||||||
Then I should find "DiscussionMessage" in the app
|
Then I should find "Initial discussion message" in the app
|
||||||
And I should find "ReplyMessage" in the app
|
And I should find "ReplyMessage" in the app
|
||||||
And I should find "Not sent" in the app
|
And I should find "Not sent" in the app
|
||||||
|
|
||||||
When I press the back button in the app
|
When I press the back button in the app
|
||||||
And I switch offline mode to "false"
|
And I switch offline mode to "false"
|
||||||
And I press "DiscussionSubject" in the app
|
And I press "Initial discussion" in the app
|
||||||
Then I should find "DiscussionMessage" in the app
|
Then I should find "Initial discussion message" in the app
|
||||||
And I should find "ReplyMessage" in the app
|
And I should find "ReplyMessage" in the app
|
||||||
But I should not find "Not sent" in the app
|
But I should not find "Not sent" in the app
|
||||||
|
|
||||||
|
@ -332,7 +278,7 @@ Feature: Test basic usage of forum activity in app
|
||||||
And I press "Test forum name" in the app
|
And I press "Test forum name" in the app
|
||||||
And I press "Information" in the app
|
And I press "Information" in the app
|
||||||
And I press "Refresh" in the app
|
And I press "Refresh" in the app
|
||||||
And I press "DiscussionSubject" near "Sort by last post creation date in descending order" in the app
|
And I press "DiscussionSubject" in the app
|
||||||
Then I should find "DiscussionSubject" in the app
|
Then I should find "DiscussionSubject" in the app
|
||||||
And I should find "DiscussionMessage" in the app
|
And I should find "DiscussionMessage" in the app
|
||||||
But I should not find "Not sent" in the app
|
But I should not find "Not sent" in the app
|
||||||
|
@ -355,45 +301,32 @@ Feature: Test basic usage of forum activity in app
|
||||||
And I wait loading to finish in the app
|
And I wait loading to finish in the app
|
||||||
Then I should not find "Not sent" in the app
|
Then I should not find "Not sent" in the app
|
||||||
|
|
||||||
When I press "DiscussionSubject" near "Sort by last post creation date in descending order" in the app
|
When I press "DiscussionSubject" in the app
|
||||||
Then I should find "DiscussionSubject" in the app
|
Then I should find "DiscussionSubject" in the app
|
||||||
And I should find "DiscussionMessage" in the app
|
And I should find "DiscussionMessage" in the app
|
||||||
But I should not find "Not sent" in the app
|
But I should not find "Not sent" in the app
|
||||||
And I should not find "This Forum has offline data to be synchronised." in the app
|
And I should not find "This Forum has offline data to be synchronised." in the app
|
||||||
|
|
||||||
Scenario: Prefetch
|
Scenario: Prefetch
|
||||||
Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
|
Given I entered the course "Course 1" as "student1" in the app
|
||||||
When I press "Add discussion topic" in the app
|
When I press "Course downloads" in the app
|
||||||
And I set the following fields to these values in the app:
|
|
||||||
| Subject | DiscussionSubject 1 |
|
|
||||||
| Message | DiscussionMessage 1 |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
Then I should find "DiscussionSubject 1" in the app
|
|
||||||
|
|
||||||
When I press the back button in the app
|
|
||||||
And I press "Course downloads" in the app
|
|
||||||
And I press "Download" within "Test forum name" "ion-item" in the app
|
And I press "Download" within "Test forum name" "ion-item" in the app
|
||||||
Then I should find "Downloaded" within "Test forum name" "ion-item" in the app
|
Then I should find "Downloaded" within "Test forum name" "ion-item" in the app
|
||||||
And I press the back button in the app
|
|
||||||
|
|
||||||
When I press "Test forum name" in the app
|
|
||||||
And I press "Add discussion topic" in the app
|
|
||||||
And I set the following fields to these values in the app:
|
|
||||||
| Subject | DiscussionSubject 2 |
|
|
||||||
| Message | DiscussionMessage 2 |
|
|
||||||
And I press "Post to forum" in the app
|
|
||||||
Then I should find "DiscussionSubject 1" in the app
|
|
||||||
And I should find "DiscussionSubject 2" in the app
|
|
||||||
|
|
||||||
When I press the back button in the app
|
When I press the back button in the app
|
||||||
And I switch offline mode to "true"
|
And I switch offline mode to "true"
|
||||||
And I press "Test forum name" in the app
|
And I press "Test forum name" in the app
|
||||||
And I press "DiscussionSubject 2" in the app
|
Then I should find "Initial discussion" in the app
|
||||||
Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app
|
|
||||||
|
|
||||||
When I press "OK" in the app
|
When I press "Initial discussion" in the app
|
||||||
And I press the back button in the app
|
Then I should find "Initial discussion" in the app
|
||||||
And I press "DiscussionSubject 1" in the app
|
And I should find "Initial discussion message" in the app
|
||||||
Then I should find "DiscussionSubject 1" in the app
|
|
||||||
And I should find "DiscussionMessage 1" in the app
|
When I press the back button in the app
|
||||||
But I should not find "There was a problem connecting to the site. Please check your connection and try again." in the app
|
And I press "Add discussion topic" in the app
|
||||||
|
Then I should not find "There was a problem connecting to the site. Please check your connection and try again." in the app
|
||||||
|
|
||||||
|
When I press the back button in the app
|
||||||
|
And I press "Sort by last post creation date in descending order" in the app
|
||||||
|
And I press "Sort by last post creation date in ascending order" in the app
|
||||||
|
Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app
|
||||||
|
|
|
@ -0,0 +1,369 @@
|
||||||
|
@mod @mod_forum @app @javascript
|
||||||
|
Feature: Test usage of forum activity with groups in app
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given the following "courses" exist:
|
||||||
|
| fullname | shortname |
|
||||||
|
| Course 1 | C1 |
|
||||||
|
And the following "users" exist:
|
||||||
|
| username |
|
||||||
|
| student1 |
|
||||||
|
| teacher1 |
|
||||||
|
And the following "course enrolments" exist:
|
||||||
|
| user | course | role |
|
||||||
|
| student1 | C1 | student |
|
||||||
|
| teacher1 | C1 | editingteacher |
|
||||||
|
And the following "groups" exist:
|
||||||
|
| name | course | idnumber |
|
||||||
|
| Group 1 | C1 | G1 |
|
||||||
|
| Group 2 | C1 | G2 |
|
||||||
|
And the following "group members" exist:
|
||||||
|
| user | group |
|
||||||
|
| student1 | G1 |
|
||||||
|
And the following "activities" exist:
|
||||||
|
| activity | name | intro | course | idnumber | groupmode | assessed | scale |
|
||||||
|
| forum | Separate groups forum | Test forum | C1 | forum | 1 | 1 | 1 |
|
||||||
|
| forum | Visible groups forum | Test forum | C1 | forum2 | 2 | 1 | 1 |
|
||||||
|
And the following "mod_forum > discussions" exist:
|
||||||
|
| forum | name | subject | message | group |
|
||||||
|
| forum | Disc sep G1 | Disc sep G1 | Disc sep G1 content | G1 |
|
||||||
|
| forum | Disc sep G2 | Disc sep G2 | Disc sep G2 content | G2 |
|
||||||
|
| forum | Disc sep ALL | Disc sep ALL | Disc sep ALL content | All participants |
|
||||||
|
| forum2 | Disc vis G1 | Disc vis G1 | Disc vis G1 content | G1 |
|
||||||
|
| forum2 | Disc vis G2 | Disc vis G2 | Disc vis G2 content | G2 |
|
||||||
|
| forum2 | Disc vis ALL | Disc vis ALL | Disc vis ALL content | All participants |
|
||||||
|
|
||||||
|
Scenario: Student can only see the right groups
|
||||||
|
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "student1" in the app
|
||||||
|
Then I should find "Disc sep G1" in the app
|
||||||
|
And I should find "Disc sep ALL" in the app
|
||||||
|
But I should not find "Disc sep G2" in the app
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
Then I should find "Group 1" in the app
|
||||||
|
But I should not find "All participants" in the app
|
||||||
|
And I should not find "Group 2" in the app
|
||||||
|
|
||||||
|
When I press "Group 1" in the app
|
||||||
|
And I press the back button in the app
|
||||||
|
And I press "Visible groups forum" in the app
|
||||||
|
And I press "Visible groups" in the app
|
||||||
|
Then I should find "All participants" in the app
|
||||||
|
And I should find "Group 1" in the app
|
||||||
|
And I should find "Group 2" in the app
|
||||||
|
|
||||||
|
When I press "All participants" in the app
|
||||||
|
Then I should find "Disc vis G1" in the app
|
||||||
|
And I should find "Disc vis ALL" in the app
|
||||||
|
And I should find "Disc vis G2" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
Then I should find "Disc vis G1" in the app
|
||||||
|
And I should find "Disc vis ALL" in the app
|
||||||
|
But I should not find "Disc vis G2" in the app
|
||||||
|
|
||||||
|
Scenario: Teacher can see all groups
|
||||||
|
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
Then I should find "All participants" in the app
|
||||||
|
And I should find "Group 1" in the app
|
||||||
|
And I should find "Group 2" in the app
|
||||||
|
|
||||||
|
When I press "All participants" in the app
|
||||||
|
Then I should find "Disc sep G1" in the app
|
||||||
|
And I should find "Disc sep ALL" in the app
|
||||||
|
And I should find "Disc sep G2" in the app
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
Then I should find "Disc sep G1" in the app
|
||||||
|
And I should find "Disc sep ALL" in the app
|
||||||
|
But I should not find "Disc sep G2" in the app
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "Disc sep G2" in the app
|
||||||
|
And I should find "Disc sep ALL" in the app
|
||||||
|
But I should not find "Disc sep G1" in the app
|
||||||
|
|
||||||
|
When I press the back button in the app
|
||||||
|
And I press "Visible groups forum" in the app
|
||||||
|
And I press "Visible groups" in the app
|
||||||
|
Then I should find "All participants" in the app
|
||||||
|
And I should find "Group 1" in the app
|
||||||
|
And I should find "Group 2" in the app
|
||||||
|
|
||||||
|
When I press "All participants" in the app
|
||||||
|
Then I should find "Disc vis G1" in the app
|
||||||
|
And I should find "Disc vis ALL" in the app
|
||||||
|
And I should find "Disc vis G2" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
Then I should find "Disc vis G1" in the app
|
||||||
|
And I should find "Disc vis ALL" in the app
|
||||||
|
But I should not find "Disc vis G2" in the app
|
||||||
|
|
||||||
|
Scenario: Student can only add discussions in his groups
|
||||||
|
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "student1" in the app
|
||||||
|
When I press "Add discussion topic" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
Then I should not find "Post a copy to all groups" in the app
|
||||||
|
And I should find "Posting in group \"Group 1\"" in the app
|
||||||
|
|
||||||
|
When I press "Group" in the app
|
||||||
|
Then I should find "Group 1" in the app
|
||||||
|
But I should not find "All participants" in the app
|
||||||
|
And I should not find "Group 2" in the app
|
||||||
|
|
||||||
|
When I press "Group 1" in the app
|
||||||
|
And I set the field "Subject" to "My happy subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
And I should find "My happy subject" in the app
|
||||||
|
|
||||||
|
When I press the back button in the app
|
||||||
|
And I press "Visible groups forum" in the app
|
||||||
|
And I press "Visible groups" in the app
|
||||||
|
And I press "All participants" in the app
|
||||||
|
Then I should not find "Add discussion topic" in the app
|
||||||
|
But I should find "You do not have permission to add a new discussion topic for all participants" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should not find "Add discussion topic" in the app
|
||||||
|
But I should find "Adding discussions to this forum requires group membership" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
And I press "Add discussion topic" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
Then I should not find "Post a copy to all groups" in the app
|
||||||
|
And I should find "Posting in group \"Group 1\"" in the app
|
||||||
|
|
||||||
|
When I press "Group" in the app
|
||||||
|
Then I should find "Group 1" in the app
|
||||||
|
But I should not find "All participants" in the app
|
||||||
|
And I should not find "Group 2" in the app
|
||||||
|
|
||||||
|
When I press "Group 1" in the app
|
||||||
|
And I set the field "Subject" to "My happy subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
And I should find "My happy subject" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should not find "My happy subject" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "All participants" in the app
|
||||||
|
Then I should find "My happy subject" in the app
|
||||||
|
|
||||||
|
Scenario: Teacher can add discussion to any group
|
||||||
|
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
|
||||||
|
And I press "Separate groups" in the app
|
||||||
|
And I press "All participants" in the app
|
||||||
|
And I press "Add discussion topic" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
Then I should find "Post a copy to all groups" in the app
|
||||||
|
And I should find "All participants" in the app
|
||||||
|
But I should not find "Posting in group" in the app
|
||||||
|
|
||||||
|
When I set the field "Subject" to "My first subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
And I should find "My first subject" in the app
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
Then I should find "My first subject" in the app
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "My first subject" in the app
|
||||||
|
|
||||||
|
When I press "Add discussion topic" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
Then I should find "Post a copy to all groups" in the app
|
||||||
|
And I should find "Posting in group \"Group 2\"" in the app
|
||||||
|
|
||||||
|
When I set the field "Subject" to "My second subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
And I should find "My second subject" in the app
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
Then I should not find "My second subject" in the app
|
||||||
|
|
||||||
|
When I press "Add discussion topic" in the app
|
||||||
|
Then I should find "Posting in group \"Group 1\"" in the app
|
||||||
|
|
||||||
|
When I press "Advanced" in the app
|
||||||
|
And I press "Group" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "Posting in group \"Group 2\"" in the app
|
||||||
|
|
||||||
|
When I set the field "Subject" to "My third subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
But I should not find "My third subject" in the app
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "My third subject" in the app
|
||||||
|
|
||||||
|
When I press the back button in the app
|
||||||
|
And I press "Visible groups forum" in the app
|
||||||
|
And I press "Visible groups" in the app
|
||||||
|
And I press "All participants" in the app
|
||||||
|
And I press "Add discussion topic" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
Then I should find "Post a copy to all groups" in the app
|
||||||
|
And I should find "All participants" in the app
|
||||||
|
But I should not find "Posting in group" in the app
|
||||||
|
|
||||||
|
When I set the field "Subject" to "My first subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
And I should find "My first subject" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
Then I should find "My first subject" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "My first subject" in the app
|
||||||
|
|
||||||
|
When I press "Add discussion topic" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
Then I should find "Post a copy to all groups" in the app
|
||||||
|
And I should find "Posting in group \"Group 2\"" in the app
|
||||||
|
|
||||||
|
When I set the field "Subject" to "My second subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
And I should find "My second subject" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
Then I should not find "My second subject" in the app
|
||||||
|
|
||||||
|
When I press "Add discussion topic" in the app
|
||||||
|
Then I should find "Posting in group \"Group 1\"" in the app
|
||||||
|
|
||||||
|
When I press "Advanced" in the app
|
||||||
|
And I press "Group" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "Posting in group \"Group 2\"" in the app
|
||||||
|
|
||||||
|
When I set the field "Subject" to "My third subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
But I should not find "My third subject" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "My third subject" in the app
|
||||||
|
|
||||||
|
Scenario: Teacher can post a copy in all groups
|
||||||
|
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
|
||||||
|
And I press "Separate groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
And I press "Add discussion topic" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
Then I should find "Post a copy to all groups" in the app
|
||||||
|
And I should find "Posting in group \"Group 1\"" in the app
|
||||||
|
|
||||||
|
When I press "Post a copy to all groups" in the app
|
||||||
|
Then I should not find "Posting in group \"Group 1\"" in the app
|
||||||
|
|
||||||
|
When I set the field "Subject" to "My happy subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
And I should find "My happy subject" in the app
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "My happy subject" in the app
|
||||||
|
|
||||||
|
When I press the back button in the app
|
||||||
|
And I press "Visible groups forum" in the app
|
||||||
|
And I press "Visible groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
And I press "Add discussion topic" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
Then I should find "Post a copy to all groups" in the app
|
||||||
|
And I should find "Posting in group \"Group 1\"" in the app
|
||||||
|
|
||||||
|
When I press "Post a copy to all groups" in the app
|
||||||
|
Then I should not find "Posting in group \"Group 1\"" in the app
|
||||||
|
|
||||||
|
When I set the field "Subject" to "My happy subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should find "Your post was successfully added" in the app
|
||||||
|
And I should find "My happy subject" in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
Then I should find "My happy subject" in the app
|
||||||
|
|
||||||
|
Scenario: New discussion not opened in tablet if not visible
|
||||||
|
Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
|
||||||
|
And I change viewport size to "1200x640"
|
||||||
|
|
||||||
|
When I press "Separate groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
And I press "Add discussion topic" in the app
|
||||||
|
And I set the field "Subject" to "My happy subject" in the app
|
||||||
|
And I set the field "Message" to "An awesome message" in the app
|
||||||
|
And I press "Advanced" in the app
|
||||||
|
And I press "Group" near "Advanced" in the app
|
||||||
|
And I press "Group 2" in the app
|
||||||
|
And I press "Post to forum" in the app
|
||||||
|
Then I should not find "My happy subject" in the app
|
||||||
|
And I should not find "An awesome message" inside the split-view content in the app
|
||||||
|
|
||||||
|
Scenario: Prefetch
|
||||||
|
Given I entered the course "Course 1" as "student1" in the app
|
||||||
|
When I press "Course downloads" in the app
|
||||||
|
And I press "Download" within "Separate groups" "ion-item" in the app
|
||||||
|
And I press "Download" within "Visible groups" "ion-item" in the app
|
||||||
|
Then I should find "Downloaded" within "Separate groups" "ion-item" in the app
|
||||||
|
And I should find "Downloaded" within "Visible groups" "ion-item" in the app
|
||||||
|
|
||||||
|
When I press the back button in the app
|
||||||
|
And I switch offline mode to "true"
|
||||||
|
And I press "Separate groups forum" in the app
|
||||||
|
Then I should find "Disc sep G1" in the app
|
||||||
|
And I should be able to press "Add discussion topic" in the app
|
||||||
|
|
||||||
|
When I press "Disc sep G1" in the app
|
||||||
|
Then I should find "Disc sep G1" in the app
|
||||||
|
And I should find "Disc sep G1 content" in the app
|
||||||
|
|
||||||
|
When I press the back button in the app
|
||||||
|
And I press the back button in the app
|
||||||
|
And I press "Visible groups forum" in the app
|
||||||
|
Then I should find "Disc vis ALL" in the app
|
||||||
|
And I should find "Disc vis G1" in the app
|
||||||
|
And I should find "Disc vis G2" in the app
|
||||||
|
And I should not be able to press "Add discussion topic" in the app
|
||||||
|
And I should find "You do not have permission to add a new discussion topic for all participants." in the app
|
||||||
|
|
||||||
|
When I press "Visible groups" in the app
|
||||||
|
And I press "Group 1" in the app
|
||||||
|
Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app
|
|
@ -16,7 +16,7 @@ import { AddonNotes, AddonNotesPublishState } from '@addons/notes/services/notes
|
||||||
import { Component, ViewChild, ElementRef, Input } from '@angular/core';
|
import { Component, ViewChild, ElementRef, Input } from '@angular/core';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { CoreForms } from '@singletons/form';
|
import { CoreForms } from '@singletons/form';
|
||||||
import { ModalController } from '@singletons';
|
import { ModalController } from '@singletons';
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ export class AddonNotesAddComponent {
|
||||||
CoreForms.triggerFormSubmittedEvent(this.formElement, sent, CoreSites.getCurrentSiteId());
|
CoreForms.triggerFormSubmittedEvent(this.formElement, sent, CoreSites.getCurrentSiteId());
|
||||||
|
|
||||||
ModalController.dismiss(<AddonNotesAddModalReturn>{ type: this.type, sent: true }).finally(() => {
|
ModalController.dismiss(<AddonNotesAddModalReturn>{ type: this.type, sent: true }).finally(() => {
|
||||||
CoreDomUtils.showToast(sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', true, 3000);
|
CoreDomUtils.showToast(sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', true, ToastDuration.LONG);
|
||||||
});
|
});
|
||||||
} catch (error){
|
} catch (error){
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||||
import { IonContent, IonRefresher } from '@ionic/angular';
|
import { IonContent, IonRefresher } from '@ionic/angular';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
@ -232,7 +232,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.refreshNotes(false);
|
this.refreshNotes(false);
|
||||||
|
|
||||||
CoreDomUtils.showToast('addon.notes.eventnotedeleted', true, 3000);
|
CoreDomUtils.showToast('addon.notes.eventnotedeleted', true, ToastDuration.LONG);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Delete note failed.');
|
CoreDomUtils.showErrorModalDefault(error, 'Delete note failed.');
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
CoreWSUploadFileResult,
|
CoreWSUploadFileResult,
|
||||||
CoreWSPreSetsSplitRequest,
|
CoreWSPreSetsSplitRequest,
|
||||||
} from '@services/ws';
|
} from '@services/ws';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreTimeUtils } from '@services/utils/time';
|
import { CoreTimeUtils } from '@services/utils/time';
|
||||||
import { CoreUrlUtils, CoreUrlParams } from '@services/utils/url';
|
import { CoreUrlUtils, CoreUrlParams } from '@services/utils/url';
|
||||||
|
@ -572,7 +572,7 @@ export class CoreSite {
|
||||||
|
|
||||||
if (wsPreSets.cleanUnicode && CoreTextUtils.hasUnicodeData(data)) {
|
if (wsPreSets.cleanUnicode && CoreTextUtils.hasUnicodeData(data)) {
|
||||||
// Data will be cleaned, notify the user.
|
// Data will be cleaned, notify the user.
|
||||||
CoreDomUtils.showToast('core.unicodenotsupported', true, 3000);
|
CoreDomUtils.showToast('core.unicodenotsupported', true, ToastDuration.LONG);
|
||||||
} else {
|
} else {
|
||||||
// No need to clean data in this call.
|
// No need to clean data in this call.
|
||||||
wsPreSets.cleanUnicode = false;
|
wsPreSets.cleanUnicode = false;
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { ContextLevel, CoreConstants } from '@/core/constants';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { NgZone, Translate } from '@singletons';
|
import { NgZone, Translate } from '@singletons';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { CoreUser } from '@features/user/services/user';
|
import { CoreUser } from '@features/user/services/user';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
@ -319,7 +319,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy {
|
||||||
CoreDomUtils.showToast(
|
CoreDomUtils.showToast(
|
||||||
commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline',
|
commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline',
|
||||||
true,
|
true,
|
||||||
3000,
|
ToastDuration.LONG,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (commentsResponse) {
|
if (commentsResponse) {
|
||||||
|
@ -417,7 +417,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.invalidateComments();
|
this.invalidateComments();
|
||||||
|
|
||||||
CoreDomUtils.showToast('core.comments.eventcommentdeleted', true, 3000);
|
CoreDomUtils.showToast('core.comments.eventcommentdeleted', true, ToastDuration.LONG);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Delete comment failed.');
|
CoreDomUtils.showErrorModalDefault(error, 'Delete comment failed.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
} from '@features/rating/services/rating';
|
} from '@features/rating/services/rating';
|
||||||
import { CoreRatingOffline } from '@features/rating/services/rating-offline';
|
import { CoreRatingOffline } from '@features/rating/services/rating-offline';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ export class CoreRatingRateComponent implements OnChanges, OnDestroy {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response === undefined) {
|
if (response === undefined) {
|
||||||
CoreDomUtils.showToast('core.datastoredoffline', true, 3000);
|
CoreDomUtils.showToast('core.datastoredoffline', true, ToastDuration.LONG);
|
||||||
} else {
|
} else {
|
||||||
this.onUpdate.emit();
|
this.onUpdate.emit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,5 +86,5 @@ export type CoreUserDBRecord = CoreUserBasicData;
|
||||||
export type CoreUserPreferenceDBRecord = {
|
export type CoreUserPreferenceDBRecord = {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
onlinevalue: string;
|
onlinevalue: string | null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,19 +58,12 @@ export class CoreUserOfflineProvider {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async setPreference(name: string, value: string, onlineValue?: string, siteId?: string): Promise<void> {
|
async setPreference(name: string, value: string, onlineValue?: string | null , siteId?: string): Promise<void> {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
if (onlineValue === undefined) {
|
const record: Partial<CoreUserPreferenceDBRecord> = {
|
||||||
const preference = await this.getPreference(name, site.id);
|
|
||||||
|
|
||||||
onlineValue = preference.onlinevalue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const record: CoreUserPreferenceDBRecord = {
|
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
onlinevalue: onlineValue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await site.getDb().insertRecord(PREFERENCES_TABLE_NAME, record);
|
await site.getDb().insertRecord(PREFERENCES_TABLE_NAME, record);
|
||||||
|
|
|
@ -1106,7 +1106,7 @@ type CoreUserGetUserPreferencesWSParams = {
|
||||||
type CoreUserGetUserPreferencesWSResponse = {
|
type CoreUserGetUserPreferencesWSResponse = {
|
||||||
preferences: { // User custom fields (also known as user profile fields).
|
preferences: { // User custom fields (also known as user profile fields).
|
||||||
name: string; // The name of the preference.
|
name: string; // The name of the preference.
|
||||||
value: string; // The value of the preference.
|
value: string | null; // The value of the preference.
|
||||||
}[];
|
}[];
|
||||||
warnings?: CoreWSExternalWarning[];
|
warnings?: CoreWSExternalWarning[];
|
||||||
};
|
};
|
||||||
|
|
|
@ -1614,23 +1614,19 @@ export class CoreDomUtilsProvider {
|
||||||
async showToast(
|
async showToast(
|
||||||
text: string,
|
text: string,
|
||||||
needsTranslate?: boolean,
|
needsTranslate?: boolean,
|
||||||
duration: number = 2000,
|
duration: ToastDuration | number = ToastDuration.SHORT,
|
||||||
cssClass: string = '',
|
cssClass: string = '',
|
||||||
): Promise<HTMLIonToastElement> {
|
): Promise<HTMLIonToastElement> {
|
||||||
if (needsTranslate) {
|
if (needsTranslate) {
|
||||||
text = Translate.instant(text);
|
text = Translate.instant(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loader = await ToastController.create({
|
return this.showToastWithOptions({
|
||||||
message: text,
|
message: text,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
cssClass: cssClass,
|
cssClass: cssClass,
|
||||||
});
|
});
|
||||||
|
|
||||||
await loader.present();
|
|
||||||
|
|
||||||
return loader;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1639,12 +1635,15 @@ export class CoreDomUtilsProvider {
|
||||||
* @param options Options.
|
* @param options Options.
|
||||||
* @return Promise resolved with Toast instance.
|
* @return Promise resolved with Toast instance.
|
||||||
*/
|
*/
|
||||||
async showToastWithOptions(options: ToastOptions): Promise<HTMLIonToastElement> {
|
async showToastWithOptions(options: ShowToastOptions): Promise<HTMLIonToastElement> {
|
||||||
// Set some default values.
|
// Convert some values and set default values.
|
||||||
options.duration = options.duration ?? 2000;
|
const toastOptions: ToastOptions = {
|
||||||
options.position = options.position ?? 'bottom';
|
...options,
|
||||||
|
duration: CoreConstants.CONFIG.toastDurations[options.duration] ?? options.duration ?? 2000,
|
||||||
|
position: options.position ?? 'bottom',
|
||||||
|
};
|
||||||
|
|
||||||
const loader = await ToastController.create(options);
|
const loader = await ToastController.create(toastOptions);
|
||||||
|
|
||||||
await loader.present();
|
await loader.present();
|
||||||
|
|
||||||
|
@ -2130,3 +2129,19 @@ export enum VerticalPoint {
|
||||||
MID = 'mid',
|
MID = 'mid',
|
||||||
BOTTOM = 'bottom',
|
BOTTOM = 'bottom',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toast duration.
|
||||||
|
*/
|
||||||
|
export enum ToastDuration {
|
||||||
|
LONG = 'long',
|
||||||
|
SHORT = 'short',
|
||||||
|
STICKY = 'sticky',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for showToastWithOptions.
|
||||||
|
*/
|
||||||
|
export type ShowToastOptions = Omit<ToastOptions, 'duration'> & {
|
||||||
|
duration: ToastDuration | number;
|
||||||
|
};
|
||||||
|
|
|
@ -89,7 +89,10 @@ export class TestingBehatDomUtilsService {
|
||||||
text: string,
|
text: string,
|
||||||
options: TestingBehatFindOptions,
|
options: TestingBehatFindOptions,
|
||||||
): ElementsWithExact[] {
|
): ElementsWithExact[] {
|
||||||
const attributesSelector = `[aria-label*="${text}"], a[title*="${text}"], img[alt*="${text}"], [placeholder*="${text}"]`;
|
// Escape double quotes to prevent breaking the query selector.
|
||||||
|
const escapedText = text.replace(/"/g, '\\"');
|
||||||
|
const attributesSelector = `[aria-label*="${escapedText}"], a[title*="${escapedText}"], ` +
|
||||||
|
`img[alt*="${escapedText}"], [placeholder*="${escapedText}"]`;
|
||||||
|
|
||||||
const elements = Array.from(container.querySelectorAll<HTMLElement>(attributesSelector))
|
const elements = Array.from(container.querySelectorAll<HTMLElement>(attributesSelector))
|
||||||
.filter((element => this.isElementVisible(element, container)))
|
.filter((element => this.isElementVisible(element, container)))
|
||||||
|
@ -397,12 +400,13 @@ export class TestingBehatDomUtilsService {
|
||||||
const withinElements = this.findElementsBasedOnTextInContainer(locator.within, topContainer, options);
|
const withinElements = this.findElementsBasedOnTextInContainer(locator.within, topContainer, options);
|
||||||
|
|
||||||
if (withinElements.length === 0) {
|
if (withinElements.length === 0) {
|
||||||
throw new Error('There was no match for within text');
|
return [];
|
||||||
} else if (withinElements.length > 1) {
|
} else if (withinElements.length > 1) {
|
||||||
const withinElementsAncestors = this.getTopAncestors(withinElements);
|
const withinElementsAncestors = this.getTopAncestors(withinElements);
|
||||||
|
|
||||||
if (withinElementsAncestors.length > 1) {
|
if (withinElementsAncestors.length > 1) {
|
||||||
throw new Error('Too many matches for within text ('+withinElementsAncestors.length+')');
|
// Too many matches for within text.
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
topContainer = container = withinElementsAncestors[0];
|
topContainer = container = withinElementsAncestors[0];
|
||||||
|
@ -418,12 +422,13 @@ export class TestingBehatDomUtilsService {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (nearElements.length === 0) {
|
if (nearElements.length === 0) {
|
||||||
throw new Error('There was no match for near text');
|
return [];
|
||||||
} else if (nearElements.length > 1) {
|
} else if (nearElements.length > 1) {
|
||||||
const nearElementsAncestors = this.getTopAncestors(nearElements);
|
const nearElementsAncestors = this.getTopAncestors(nearElements);
|
||||||
|
|
||||||
if (nearElementsAncestors.length > 1) {
|
if (nearElementsAncestors.length > 1) {
|
||||||
throw new Error('Too many matches for near text ('+nearElementsAncestors.length+')');
|
// Too many matches for near text.
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
container = this.getParentElement(nearElementsAncestors[0]);
|
container = this.getParentElement(nearElementsAncestors[0]);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreSitesDemoSiteData } from '@services/sites';
|
||||||
import { OpenFileAction } from '@services/utils/utils';
|
import { OpenFileAction } from '@services/utils/utils';
|
||||||
import { CoreLoginSiteSelectorListMethod } from '@features/login/services/login-helper';
|
import { CoreLoginSiteSelectorListMethod } from '@features/login/services/login-helper';
|
||||||
import { CoreDatabaseConfiguration } from '@classes/database/database-table';
|
import { CoreDatabaseConfiguration } from '@classes/database/database-table';
|
||||||
|
import { ToastDuration } from '@services/utils/dom';
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
|
@ -69,4 +70,5 @@ export interface EnvironmentConfig {
|
||||||
calendarreminderdefaultvalue: number; // Initial value for default reminders (in seconds). User can change it later.
|
calendarreminderdefaultvalue: number; // Initial value for default reminders (in seconds). User can change it later.
|
||||||
removeaccountonlogout?: boolean; // True to remove the account when the user clicks logout. Doesn't affect switch account.
|
removeaccountonlogout?: boolean; // True to remove the account when the user clicks logout. Doesn't affect switch account.
|
||||||
uselegacycompletion?: boolean; // Whether to use legacy completion by default in all course formats.
|
uselegacycompletion?: boolean; // Whether to use legacy completion by default in all course formats.
|
||||||
|
toastDurations: Record<ToastDuration, number>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue