MOBILE-3643 forum: Migrate new discussion form
parent
fbad712136
commit
d4bc77b386
|
@ -40,6 +40,14 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
|||
import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu';
|
||||
import { AddonModForumSortOrderSelectorComponent } from '../sort-order-selector/sort-order-selector';
|
||||
|
||||
/**
|
||||
* Type to use for selecting new discussion form in the discussions manager.
|
||||
*/
|
||||
type NewDiscussionForm = {
|
||||
newDiscussion: true;
|
||||
timeCreated: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that displays a forum entry page.
|
||||
*/
|
||||
|
@ -193,17 +201,16 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
.getForum(this.courseId, this.module.id)
|
||||
.then(async (forum) => {
|
||||
this.forum = forum;
|
||||
|
||||
this.description = forum.intro || this.description;
|
||||
this.availabilityMessage = AddonModForumHelper.instance.getAvailabilityMessage(forum);
|
||||
this.descriptionNote = Translate.instant('addon.mod_forum.numdiscussions', {
|
||||
numdiscussions: forum.numdiscussions,
|
||||
});
|
||||
|
||||
if (typeof forum.istracked != 'undefined') {
|
||||
this.trackPosts = forum.istracked;
|
||||
}
|
||||
|
||||
this.availabilityMessage = AddonModForumHelper.instance.getAvailabilityMessage(forum);
|
||||
|
||||
this.dataRetrieved.emit(forum);
|
||||
|
||||
switch (forum.type) {
|
||||
|
@ -218,23 +225,19 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewdiscussion');
|
||||
}
|
||||
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
// Try to synchronize the forum.
|
||||
const updated = await this.syncActivity(showErrors);
|
||||
|
||||
if (!updated) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
// Sync successful, send event.
|
||||
CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, {
|
||||
forumId: forum.id,
|
||||
userId: CoreSites.instance.getCurrentSiteUserId(),
|
||||
source: 'index',
|
||||
}, CoreSites.instance.getCurrentSiteId());
|
||||
}
|
||||
}
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
|
@ -507,18 +510,10 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
* @param timeCreated Creation time of the offline discussion.
|
||||
*/
|
||||
openNewDiscussion(timeCreated: number = 0): void {
|
||||
alert(`Open new discussion at ${timeCreated} not implemented!`);
|
||||
|
||||
// @todo
|
||||
// const params = {
|
||||
// courseId: this.courseId,
|
||||
// cmId: this.module.id,
|
||||
// forumId: this.forum.id,
|
||||
// timeCreated: timeCreated,
|
||||
// };
|
||||
// this.splitviewCtrl.push('AddonModForumNewDiscussionPage', params);
|
||||
|
||||
this.selectedDiscussion = 0;
|
||||
this.discussions.select({
|
||||
newDiscussion: true,
|
||||
timeCreated,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -598,7 +593,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
|
||||
}
|
||||
|
||||
class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModForumDiscussion> {
|
||||
class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModForumDiscussion | NewDiscussionForm> {
|
||||
|
||||
private discussionsPathPrefix: string;
|
||||
private component: AddonModForumIndexComponent;
|
||||
|
@ -610,18 +605,21 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModF
|
|||
this.discussionsPathPrefix = discussionsPathPrefix;
|
||||
}
|
||||
|
||||
getItemQueryParams(discussion: AddonModForumDiscussion): Params {
|
||||
getItemQueryParams(discussion: AddonModForumDiscussion | NewDiscussionForm): Params {
|
||||
return {
|
||||
discussion,
|
||||
courseId: this.component.courseId,
|
||||
cmId: this.component.module!.id,
|
||||
forumId: this.component.forum!.id,
|
||||
trackPosts: this.component.trackPosts,
|
||||
...(
|
||||
this.isNewDiscussionForm(discussion)
|
||||
? { timeCreated: discussion.timeCreated }
|
||||
: { discussion, trackPosts: this.component.trackPosts }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
protected getItemPath(discussion: AddonModForumDiscussion): string {
|
||||
const discussionId = discussion.id;
|
||||
protected getItemPath(discussion: AddonModForumDiscussion | NewDiscussionForm): string {
|
||||
const discussionId = this.isNewDiscussionForm(discussion) ? 'new' : discussion.id;
|
||||
|
||||
return this.discussionsPathPrefix + discussionId;
|
||||
}
|
||||
|
@ -632,4 +630,8 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModF
|
|||
return discussionId ? this.discussionsPathPrefix + discussionId : null;
|
||||
}
|
||||
|
||||
private isNewDiscussionForm(discussion: AddonModForumDiscussion | NewDiscussionForm): discussion is NewDiscussionForm {
|
||||
return 'newDiscussion' in discussion;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,21 +16,26 @@ import { NgModule } from '@angular/core';
|
|||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
|
||||
|
||||
import { AddonModForumComponentsModule } from './components/components.module';
|
||||
import { AddonModForumIndexPage } from './pages/index';
|
||||
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { AddonModForumNewDiscussionPage } from './pages/new-discussion/new-discussion';
|
||||
|
||||
const mobileRoutes: Routes = [
|
||||
{
|
||||
path: ':courseId/:cmId',
|
||||
component: AddonModForumIndexPage,
|
||||
},
|
||||
{
|
||||
path: ':courseId/:cmId/new',
|
||||
component: AddonModForumNewDiscussionPage,
|
||||
},
|
||||
{
|
||||
path: ':courseId/:cmId/:discussionId',
|
||||
loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule),
|
||||
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -39,6 +44,10 @@ const tabletRoutes: Routes = [
|
|||
path: ':courseId/:cmId',
|
||||
component: AddonModForumIndexPage,
|
||||
children: [
|
||||
{
|
||||
path: 'new',
|
||||
component: AddonModForumNewDiscussionPage,
|
||||
},
|
||||
{
|
||||
path: ':discussionId',
|
||||
loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule),
|
||||
|
@ -58,9 +67,11 @@ const routes: Routes = [
|
|||
RouterModule.forChild(routes),
|
||||
CoreSharedModule,
|
||||
AddonModForumComponentsModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonModForumIndexPage,
|
||||
AddonModForumNewDiscussionPage,
|
||||
],
|
||||
})
|
||||
export class AddonModForumLazyModule {}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'addon.mod_forum.addanewdiscussion' | translate }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<!-- The context menu will be added in here. -->
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!groupsLoaded" (ionRefresh)="refreshGroups($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-loading [hideUntil]="groupsLoaded">
|
||||
<form ion-list *ngIf="showForm" #newDiscFormEl>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.subject' | translate }}</ion-label>
|
||||
<ion-input type="text" [placeholder]="'addon.mod_forum.subject' | translate" [(ngModel)]="newDiscussion.subject" name="subject"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content name="addon_mod_forum_new_discussion" contextLevel="module" elementId="message"
|
||||
[control]="messageControl" [placeholder]="'addon.mod_forum.message' | translate" [component]="component"
|
||||
[componentId]="forum.cmid" [autoSave]="true" [contextInstanceId]="forum.cmid"
|
||||
(contentChanged)="onMessageChange($event)">
|
||||
</core-rich-text-editor>
|
||||
</ion-item>
|
||||
<ion-item-divider class="ion-text-wrap core-expandable" (click)="toggleAdvanced()">
|
||||
<ion-icon *ngIf="!advanced" name="fa-caret-right" slot="start">
|
||||
</ion-icon>
|
||||
<ion-icon *ngIf="advanced" name="fa-caret-down" slot="start">
|
||||
</ion-icon>
|
||||
<ion-label>{{ 'addon.mod_forum.advanced' | translate }}</ion-label>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngIf="advanced">
|
||||
<ion-item *ngIf="showGroups && groupIds.length > 1 && accessInfo.cancanposttomygroups">
|
||||
<ion-label>{{ 'addon.mod_forum.posttomygroups' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="newDiscussion.postToAllGroups" name="postallgroups"></ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="showGroups">
|
||||
<ion-label id="addon-mod-forum-groupslabel">{{ 'addon.mod_forum.group' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="newDiscussion.groupId" [disabled]="newDiscussion.postToAllGroups"
|
||||
aria-labelledby="addon-mod-forum-groupslabel" interface="action-sheet" name="groupid">
|
||||
<ion-select-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.mod_forum.discussionsubscription' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="newDiscussion.subscribe" name="subscribe"></ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="canPin">
|
||||
<ion-label>{{ 'addon.mod_forum.discussionpinned' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="newDiscussion.pin" name="pin"></ion-toggle>
|
||||
</ion-item>
|
||||
<core-attachments *ngIf="canCreateAttachments && forum && forum.maxattachments > 0"
|
||||
[files]="newDiscussion.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments"
|
||||
[component]="component" [componentId]="forum.cmid" [allowOffline]="true">
|
||||
</core-attachments>
|
||||
</ng-container>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<ion-button expand="block" (click)="add()" [disabled]="newDiscussion.subject == '' || newDiscussion.message == null">
|
||||
{{ 'addon.mod_forum.posttoforum' | translate }}
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
<ion-col *ngIf="hasOffline">
|
||||
<ion-button expand="block" color="light" (click)="discard()">{{ 'core.discard' | translate }}</ion-button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</form>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,594 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, ViewChild, ElementRef, OnInit } from '@angular/core';
|
||||
import { FileEntry } from '@ionic-native/file/ngx';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { CoreEvents, CoreEventObserver } from '@singletons/events';
|
||||
import { CoreGroup, CoreGroups, CoreGroupsProvider } from '@services/groups';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import {
|
||||
AddonModForum,
|
||||
AddonModForumAccessInformation,
|
||||
AddonModForumCanAddDiscussion,
|
||||
AddonModForumData,
|
||||
AddonModForumProvider,
|
||||
} from '@addons/mod/forum/services/forum.service';
|
||||
import { CoreEditorRichTextEditorComponent } from '@features/editor/components/rich-text-editor/rich-text-editor';
|
||||
import { AddonModForumSync, AddonModForumSyncProvider } from '@addons/mod/forum/services/sync.service';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreSync } from '@services/sync';
|
||||
import { AddonModForumDiscussionOptions, AddonModForumOffline } from '@addons/mod/forum/services/offline.service';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { AddonModForumHelper } from '@addons/mod/forum/services/helper.service';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
|
||||
type NewDiscussionData = {
|
||||
subject: string;
|
||||
message: string | null; // Null means empty or just white space.
|
||||
postToAllGroups: boolean;
|
||||
groupId: number;
|
||||
subscribe: boolean;
|
||||
pin: boolean;
|
||||
files: FileEntry[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Page that displays the new discussion form.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-mod-forum-new-discussion',
|
||||
templateUrl: 'new-discussion.html',
|
||||
})
|
||||
export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('newDiscFormEl') formElement!: ElementRef;
|
||||
@ViewChild(CoreEditorRichTextEditorComponent) messageEditor!: CoreEditorRichTextEditorComponent;
|
||||
|
||||
component = AddonModForumProvider.COMPONENT;
|
||||
messageControl = new FormControl();
|
||||
groupsLoaded = false;
|
||||
showGroups = false;
|
||||
hasOffline = false;
|
||||
canCreateAttachments = true; // Assume we can by default.
|
||||
canPin = false;
|
||||
forum!: AddonModForumData;
|
||||
showForm = false;
|
||||
groups: CoreGroup[] = [];
|
||||
groupIds: number[] = [];
|
||||
newDiscussion: NewDiscussionData = {
|
||||
subject: '',
|
||||
message: null,
|
||||
postToAllGroups: false,
|
||||
groupId: 0,
|
||||
subscribe: true,
|
||||
pin: false,
|
||||
files: [],
|
||||
};
|
||||
|
||||
advanced = false; // Display all form fields.
|
||||
accessInfo: AddonModForumAccessInformation = {};
|
||||
|
||||
protected courseId!: number;
|
||||
protected cmId!: number;
|
||||
protected forumId!: number;
|
||||
protected timeCreated!: number;
|
||||
protected syncId!: string;
|
||||
protected syncObserver?: CoreEventObserver;
|
||||
protected isDestroyed = false;
|
||||
protected originalData?: Partial<NewDiscussionData>;
|
||||
protected forceLeave = false;
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId')!;
|
||||
this.cmId = CoreNavigator.instance.getRouteNumberParam('cmId')!;
|
||||
this.forumId = CoreNavigator.instance.getRouteNumberParam('forumId')!;
|
||||
this.timeCreated = CoreNavigator.instance.getRouteNumberParam('timeCreated')!;
|
||||
|
||||
this.fetchDiscussionData().finally(() => {
|
||||
this.groupsLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* User entered the page that contains the component.
|
||||
*/
|
||||
ionViewDidEnter(): void {
|
||||
if (this.syncObserver) {
|
||||
// Already setup.
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh data if this discussion is synchronized automatically.
|
||||
this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, (data: any) => {
|
||||
if (data.forumId == this.forumId && data.userId == CoreSites.instance.getCurrentSiteUserId()) {
|
||||
CoreDomUtils.instance.showAlertTranslated('core.notice', 'core.contenteditingsynced');
|
||||
this.returnToDiscussions();
|
||||
}
|
||||
}, CoreSites.instance.getCurrentSiteId());
|
||||
|
||||
// Trigger view event, to highlight the current opened discussion in the split view.
|
||||
CoreEvents.trigger(AddonModForumProvider.VIEW_DISCUSSION_EVENT, {
|
||||
forumId: this.forumId,
|
||||
discussion: -this.timeCreated,
|
||||
}, CoreSites.instance.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch if forum uses groups and the groups it uses.
|
||||
*
|
||||
* @param refresh Whether we're refreshing data.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchDiscussionData(refresh?: boolean): Promise<void> {
|
||||
try {
|
||||
const mode = await CoreGroups.instance.getActivityGroupMode(this.cmId);
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
if (mode === CoreGroupsProvider.SEPARATEGROUPS || mode === CoreGroupsProvider.VISIBLEGROUPS) {
|
||||
promises.push(
|
||||
CoreGroups.instance
|
||||
.getActivityAllowedGroups(this.cmId)
|
||||
.then((result) => {
|
||||
let promise;
|
||||
if (mode === CoreGroupsProvider.VISIBLEGROUPS) {
|
||||
// We need to check which of the returned groups the user can post to.
|
||||
promise = this.validateVisibleGroups(result.groups);
|
||||
} else {
|
||||
// WS already filters groups, no need to do it ourselves. Add "All participants" if needed.
|
||||
promise = this.addAllParticipantsOption(result.groups, true);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
return promise.then((forumGroups) => {
|
||||
if (forumGroups.length > 0) {
|
||||
this.groups = forumGroups;
|
||||
this.groupIds = forumGroups.map((group) => group.id).filter((id) => id > 0);
|
||||
// Do not override group id.
|
||||
this.newDiscussion.groupId = this.newDiscussion.groupId || forumGroups[0].id;
|
||||
this.showGroups = true;
|
||||
if (this.groupIds.length <= 1) {
|
||||
this.newDiscussion.postToAllGroups = false;
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
const message = mode === CoreGroupsProvider.SEPARATEGROUPS ?
|
||||
'addon.mod_forum.cannotadddiscussionall' : 'addon.mod_forum.cannotadddiscussion';
|
||||
|
||||
throw new Error(Translate.instant(message));
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.showGroups = false;
|
||||
this.newDiscussion.postToAllGroups = false;
|
||||
|
||||
// Use the canAddDiscussion WS to check if the user can add attachments and pin discussions.
|
||||
promises.push(
|
||||
CoreUtils.instance.ignoreErrors(
|
||||
AddonModForum.instance
|
||||
.canAddDiscussionToAll(this.forumId, { cmId: this.cmId })
|
||||
.then((response) => {
|
||||
this.canPin = !!response.canpindiscussions;
|
||||
this.canCreateAttachments = !!response.cancreateattachment;
|
||||
|
||||
return;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Get forum.
|
||||
promises.push(AddonModForum.instance.getForum(this.courseId, this.cmId).then((forum) => this.forum = forum));
|
||||
|
||||
// Get access information.
|
||||
promises.push(
|
||||
AddonModForum.instance
|
||||
.getAccessInformation(this.forumId, { cmId: this.cmId })
|
||||
.then((accessInfo) => this.accessInfo = accessInfo),
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// If editing a discussion, get offline data.
|
||||
if (this.timeCreated && !refresh) {
|
||||
this.syncId = AddonModForumSync.instance.getForumSyncId(this.forumId);
|
||||
|
||||
await AddonModForumSync.instance.waitForSync(this.syncId).then(() => {
|
||||
// Do not block if the scope is already destroyed.
|
||||
if (!this.isDestroyed) {
|
||||
CoreSync.instance.blockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
return AddonModForumOffline.instance
|
||||
.getNewDiscussion(this.forumId, this.timeCreated)
|
||||
.then(async (discussion) => {
|
||||
this.hasOffline = true;
|
||||
discussion.options = discussion.options || {};
|
||||
if (discussion.groupid == AddonModForumProvider.ALL_GROUPS) {
|
||||
this.newDiscussion.groupId = this.groups[0].id;
|
||||
this.newDiscussion.postToAllGroups = true;
|
||||
} else {
|
||||
this.newDiscussion.groupId = discussion.groupid;
|
||||
this.newDiscussion.postToAllGroups = false;
|
||||
}
|
||||
this.newDiscussion.subject = discussion.subject;
|
||||
this.newDiscussion.message = discussion.message;
|
||||
this.newDiscussion.subscribe = !!discussion.options.discussionsubscribe;
|
||||
this.newDiscussion.pin = !!discussion.options.discussionpinned;
|
||||
this.messageControl.setValue(discussion.message);
|
||||
|
||||
// Treat offline attachments if any.
|
||||
if (typeof discussion.options.attachmentsid === 'object' && discussion.options.attachmentsid.offline) {
|
||||
const files = await AddonModForumHelper.instance.getNewDiscussionStoredFiles(
|
||||
this.forumId,
|
||||
this.timeCreated,
|
||||
);
|
||||
|
||||
this.newDiscussion.files = files;
|
||||
}
|
||||
|
||||
// Show advanced fields by default if any of them has not the default value.
|
||||
if (
|
||||
!this.newDiscussion.subscribe ||
|
||||
this.newDiscussion.pin ||
|
||||
this.newDiscussion.files.length ||
|
||||
this.groups.length > 0 && this.newDiscussion.groupId != this.groups[0].id ||
|
||||
this.newDiscussion.postToAllGroups
|
||||
) {
|
||||
this.advanced = true;
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.originalData) {
|
||||
// Initialize original data.
|
||||
this.originalData = {
|
||||
subject: this.newDiscussion.subject,
|
||||
message: this.newDiscussion.message,
|
||||
files: this.newDiscussion.files.slice(),
|
||||
};
|
||||
}
|
||||
|
||||
this.showForm = true;
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_forum.errorgetgroups', true);
|
||||
|
||||
this.showForm = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate which of the groups returned by getActivityAllowedGroups in visible groups should be shown to post to.
|
||||
*
|
||||
* @param forumGroups Forum groups.
|
||||
* @return Promise resolved with the list of groups.
|
||||
*/
|
||||
protected async validateVisibleGroups(forumGroups: CoreGroup[]): Promise<CoreGroup[]> {
|
||||
let response: AddonModForumCanAddDiscussion;
|
||||
|
||||
// We first check if the user can post to all the groups.
|
||||
try {
|
||||
response = await AddonModForum.instance.canAddDiscussionToAll(this.forumId, { cmId: this.cmId });
|
||||
} catch (error) {
|
||||
// The call failed, let's assume he can't.
|
||||
response = {
|
||||
status: false,
|
||||
canpindiscussions: false,
|
||||
cancreateattachment: true,
|
||||
};
|
||||
}
|
||||
|
||||
this.canPin = !!response.canpindiscussions;
|
||||
this.canCreateAttachments = !!response.cancreateattachment;
|
||||
|
||||
// The user can post to all groups, add the "All participants" option and return them all.
|
||||
if (response.status) {
|
||||
return this.addAllParticipantsOption(forumGroups, false);
|
||||
}
|
||||
|
||||
// The user can't post to all groups, let's check which groups he can post to.
|
||||
const promises: Promise<unknown>[] = [];
|
||||
const filtered: CoreGroup[] = [];
|
||||
|
||||
forumGroups.forEach((group) => {
|
||||
promises.push(
|
||||
AddonModForum.instance
|
||||
.canAddDiscussion(this.forumId, group.id, { cmId: this.cmId })
|
||||
|
||||
// The call failed, let's return true so the group is shown.
|
||||
// If the user can't post to it an error will be shown when he tries to add the discussion.
|
||||
.catch(() =>({ status: true }))
|
||||
|
||||
.then((response) => {
|
||||
if (response.status) {
|
||||
filtered.push(group);
|
||||
}
|
||||
|
||||
return;
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter forum groups, returning only those that are inside user groups.
|
||||
*
|
||||
* @param forumGroups Forum groups.
|
||||
* @param userGroups User groups.
|
||||
* @return Filtered groups.
|
||||
*/
|
||||
protected filterGroups(forumGroups: CoreGroup[], userGroups: CoreGroup[]): CoreGroup[] {
|
||||
const userGroupsIds = userGroups.map(group => group.id);
|
||||
|
||||
return forumGroups.filter(forumGroup => userGroupsIds.indexOf(forumGroup.id) > -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the "All participants" option to a list of groups if the user can add a discussion to all participants.
|
||||
*
|
||||
* @param groups Groups.
|
||||
* @param check True to check if the user can add a discussion to all participants.
|
||||
* @return Promise resolved with the list of groups.
|
||||
*/
|
||||
protected addAllParticipantsOption(groups: CoreGroup[], check: boolean): Promise<CoreGroup[]> {
|
||||
if (!AddonModForum.instance.isAllParticipantsFixed()) {
|
||||
// All participants has a bug, don't add it.
|
||||
return Promise.resolve(groups);
|
||||
}
|
||||
|
||||
let promise;
|
||||
|
||||
if (check) {
|
||||
// We need to check if the user can add a discussion to all participants.
|
||||
promise = AddonModForum.instance.canAddDiscussionToAll(this.forumId, { cmId: this.cmId }).then((response) => {
|
||||
this.canPin = !!response.canpindiscussions;
|
||||
this.canCreateAttachments = !!response.cancreateattachment;
|
||||
|
||||
return response.status;
|
||||
}).catch(() =>
|
||||
// The call failed, let's assume he can't.
|
||||
false);
|
||||
} else {
|
||||
// No need to check, assume the user can.
|
||||
promise = Promise.resolve(true);
|
||||
}
|
||||
|
||||
return promise.then((canAdd) => {
|
||||
if (canAdd) {
|
||||
groups.unshift({
|
||||
courseid: this.courseId,
|
||||
id: AddonModForumProvider.ALL_PARTICIPANTS,
|
||||
name: Translate.instant('core.allparticipants'),
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull to refresh.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshGroups(refresher?: IonRefresher): void {
|
||||
const promises = [
|
||||
CoreGroups.instance.invalidateActivityGroupMode(this.cmId),
|
||||
CoreGroups.instance.invalidateActivityAllowedGroups(this.cmId),
|
||||
AddonModForum.instance.invalidateCanAddDiscussion(this.forumId),
|
||||
];
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.fetchDiscussionData(true).finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to update or return to discussions depending on device.
|
||||
*
|
||||
* @param discussionIds Ids of the new discussions.
|
||||
* @param discTimecreated The time created of the discussion (if offline).
|
||||
*/
|
||||
protected returnToDiscussions(discussionIds?: number[] | null, discTimecreated?: number): void {
|
||||
const data: any = {
|
||||
forumId: this.forumId,
|
||||
cmId: this.cmId,
|
||||
discussionIds: discussionIds,
|
||||
discTimecreated: discTimecreated,
|
||||
};
|
||||
CoreEvents.trigger(AddonModForumProvider.NEW_DISCUSSION_EVENT, data, CoreSites.instance.getCurrentSiteId());
|
||||
|
||||
// Delete the local files from the tmp folder.
|
||||
CoreFileUploader.instance.clearTmpFiles(this.newDiscussion.files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message changed.
|
||||
*
|
||||
* @param text The new text.
|
||||
*/
|
||||
onMessageChange(text: string): void {
|
||||
this.newDiscussion.message = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new discussion.
|
||||
*/
|
||||
async add(): Promise<void> {
|
||||
const forumName = this.forum.name;
|
||||
const subject = this.newDiscussion.subject;
|
||||
let message = this.newDiscussion.message || '';
|
||||
const pin = this.newDiscussion.pin;
|
||||
const attachments = this.newDiscussion.files;
|
||||
const discTimecreated = this.timeCreated || Date.now();
|
||||
const options: AddonModForumDiscussionOptions = {
|
||||
discussionsubscribe: !!this.newDiscussion.subscribe,
|
||||
};
|
||||
|
||||
if (!subject) {
|
||||
CoreDomUtils.instance.showErrorModal('addon.mod_forum.erroremptysubject', true);
|
||||
|
||||
return;
|
||||
}
|
||||
if (!message) {
|
||||
CoreDomUtils.instance.showErrorModal('addon.mod_forum.erroremptymessage', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
|
||||
|
||||
// Add some HTML to the message if needed.
|
||||
message = CoreTextUtils.instance.formatHtmlLines(message);
|
||||
|
||||
if (pin) {
|
||||
options.discussionpinned = true;
|
||||
}
|
||||
|
||||
const groupIds = this.newDiscussion.postToAllGroups ? this.groupIds : [this.newDiscussion.groupId];
|
||||
|
||||
try {
|
||||
const discussionIds = await AddonModForumHelper.instance.addNewDiscussion(
|
||||
this.forumId,
|
||||
forumName,
|
||||
this.courseId,
|
||||
subject,
|
||||
message,
|
||||
attachments,
|
||||
options,
|
||||
groupIds,
|
||||
discTimecreated,
|
||||
);
|
||||
|
||||
if (discussionIds) {
|
||||
// Data sent to server, delete stored files (if any).
|
||||
AddonModForumHelper.instance.deleteNewDiscussionStoredFiles(this.forumId, discTimecreated);
|
||||
|
||||
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'forum' });
|
||||
}
|
||||
|
||||
if (discussionIds && discussionIds.length < groupIds.length) {
|
||||
// Some discussions could not be created.
|
||||
CoreDomUtils.instance.showErrorModalDefault(null, 'addon.mod_forum.errorposttoallgroups', true);
|
||||
}
|
||||
|
||||
CoreDomUtils.instance.triggerFormSubmittedEvent(
|
||||
this.formElement,
|
||||
!!discussionIds,
|
||||
CoreSites.instance.getCurrentSiteId(),
|
||||
);
|
||||
|
||||
this.returnToDiscussions(discussionIds, discTimecreated);
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_forum.cannotcreatediscussion', true);
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard an offline saved discussion.
|
||||
*/
|
||||
async discard(): Promise<void> {
|
||||
try {
|
||||
await CoreDomUtils.instance.showConfirm(Translate.instant('core.areyousure'));
|
||||
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
promises.push(AddonModForumOffline.instance.deleteNewDiscussion(this.forumId, this.timeCreated));
|
||||
promises.push(
|
||||
CoreUtils.instance.ignoreErrors(
|
||||
AddonModForumHelper.instance.deleteNewDiscussionStoredFiles(this.forumId, this.timeCreated),
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
CoreDomUtils.instance.triggerFormCancelledEvent(this.formElement, CoreSites.instance.getCurrentSiteId());
|
||||
|
||||
this.returnToDiscussions();
|
||||
} catch (error) {
|
||||
// Cancelled.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide advanced form fields.
|
||||
*/
|
||||
toggleAdvanced(): void {
|
||||
this.advanced = !this.advanced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can leave the page or not.
|
||||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AddonModForumHelper.instance.hasPostDataChanged(this.newDiscussion, this.originalData)) {
|
||||
// Show confirmation if some data has been modified.
|
||||
await CoreDomUtils.instance.showConfirm(Translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
// Delete the local files from the tmp folder.
|
||||
CoreFileUploader.instance.clearTmpFiles(this.newDiscussion.files);
|
||||
|
||||
if (this.formElement) {
|
||||
CoreDomUtils.instance.triggerFormCancelledEvent(this.formElement, CoreSites.instance.getCurrentSiteId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the page is about to leave and no longer be the active page.
|
||||
*/
|
||||
ionViewWillLeave(): void {
|
||||
this.syncObserver && this.syncObserver.off();
|
||||
delete this.syncObserver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (this.syncId) {
|
||||
CoreSync.instance.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
}
|
||||
this.isDestroyed = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FileEntry } from '@ionic-native/file/ngx';
|
||||
import { CoreFileEntry, CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||
import { CoreUser } from '@features/user/services/user';
|
||||
import { CoreApp } from '@services/app';
|
||||
|
|
|
@ -403,7 +403,9 @@ export class AddonModForumOfflineProvider {
|
|||
export class AddonModForumOffline extends makeSingleton(AddonModForumOfflineProvider) {}
|
||||
|
||||
export type AddonModForumDiscussionOptions = {
|
||||
attachmentsid: number | CoreFileUploaderStoreFilesResult;
|
||||
attachmentsid?: number | CoreFileUploaderStoreFilesResult;
|
||||
discussionsubscribe?: boolean;
|
||||
discussionpinned?: boolean;
|
||||
};
|
||||
|
||||
export type AddonModForumReplyOptions = {
|
||||
|
|
|
@ -743,7 +743,7 @@ export class CoreDomUtilsProvider {
|
|||
* @param error Error to check.
|
||||
* @return Whether it's a canceled error.
|
||||
*/
|
||||
isCanceledError(error: CoreError | CoreTextErrorObject | string): boolean {
|
||||
isCanceledError(error: CoreError | CoreTextErrorObject | string | null): boolean {
|
||||
return error instanceof CoreCanceledError;
|
||||
}
|
||||
|
||||
|
@ -1393,7 +1393,7 @@ export class CoreDomUtilsProvider {
|
|||
* @return Promise resolved with the alert modal.
|
||||
*/
|
||||
async showErrorModalDefault(
|
||||
error: CoreError | CoreTextErrorObject | string,
|
||||
error: CoreError | CoreTextErrorObject | string | null,
|
||||
defaultError: string,
|
||||
needsTranslate?: boolean,
|
||||
autocloseTime?: number,
|
||||
|
@ -1409,7 +1409,7 @@ export class CoreDomUtilsProvider {
|
|||
errorMessage = CoreTextUtils.instance.getErrorMessageFromError(error);
|
||||
}
|
||||
|
||||
return this.showErrorModal(typeof errorMessage == 'string' ? error : defaultError, needsTranslate, autocloseTime);
|
||||
return this.showErrorModal(typeof errorMessage == 'string' ? error! : defaultError, needsTranslate, autocloseTime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue