MOBILE-3157 forum: Add delete post option
parent
e3e559da6f
commit
f96b5e9b1c
|
@ -572,6 +572,9 @@
|
|||
"addon.mod_forum.cannotcreatediscussion": "forum",
|
||||
"addon.mod_forum.couldnotadd": "forum",
|
||||
"addon.mod_forum.cutoffdatereached": "forum",
|
||||
"addon.mod_forum.delete": "forum",
|
||||
"addon.mod_forum.deletedpost": "forum",
|
||||
"addon.mod_forum.deletesure": "forum",
|
||||
"addon.mod_forum.discussion": "forum",
|
||||
"addon.mod_forum.discussionlistsortbycreatedasc": "forum",
|
||||
"addon.mod_forum.discussionlistsortbycreateddesc": "forum",
|
||||
|
@ -1718,6 +1721,7 @@
|
|||
"core.nocomments": "moodle",
|
||||
"core.nograde": "moodle",
|
||||
"core.none": "moodle",
|
||||
"core.nooptionavailable": "local_moodlemobileapp",
|
||||
"core.nopasswordchangeforced": "local_moodlemobileapp",
|
||||
"core.nopermissionerror": "local_moodlemobileapp",
|
||||
"core.nopermissions": "error",
|
||||
|
|
|
@ -25,12 +25,14 @@ import { CoreTagComponentsModule } from '@core/tag/components/components.module'
|
|||
import { AddonModForumIndexComponent } from './index/index';
|
||||
import { AddonModForumPostComponent } from './post/post';
|
||||
import { AddonForumDiscussionOptionsMenuComponent } from './discussion-options-menu/discussion-options-menu';
|
||||
import { AddonForumPostOptionsMenuComponent } from './post-options-menu/post-options-menu';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModForumIndexComponent,
|
||||
AddonModForumPostComponent,
|
||||
AddonForumDiscussionOptionsMenuComponent
|
||||
AddonForumDiscussionOptionsMenuComponent,
|
||||
AddonForumPostOptionsMenuComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -48,11 +50,13 @@ import { AddonForumDiscussionOptionsMenuComponent } from './discussion-options-m
|
|||
exports: [
|
||||
AddonModForumIndexComponent,
|
||||
AddonModForumPostComponent,
|
||||
AddonForumDiscussionOptionsMenuComponent
|
||||
AddonForumDiscussionOptionsMenuComponent,
|
||||
AddonForumPostOptionsMenuComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonModForumIndexComponent,
|
||||
AddonForumDiscussionOptionsMenuComponent
|
||||
AddonForumDiscussionOptionsMenuComponent,
|
||||
AddonForumPostOptionsMenuComponent
|
||||
]
|
||||
})
|
||||
export class AddonModForumComponentsModule {}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, Optional, Injector, ViewChild } from '@angular/core';
|
||||
import { Content, ModalController, NavController, PopoverController } from 'ionic-angular';
|
||||
import { Content, ModalController, PopoverController } from 'ionic-angular';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
|
||||
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
|
||||
|
@ -75,7 +75,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
|
||||
constructor(injector: Injector,
|
||||
@Optional() protected content: Content,
|
||||
protected navCtrl: NavController,
|
||||
protected modalCtrl: ModalController,
|
||||
protected groupsProvider: CoreGroupsProvider,
|
||||
protected userProvider: CoreUserProvider,
|
||||
|
@ -110,28 +109,36 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
this.replyObserver = this.eventsProvider.on(AddonModForumProvider.REPLY_DISCUSSION_EVENT,
|
||||
this.eventReceived.bind(this, false));
|
||||
this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data) => {
|
||||
this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => {
|
||||
// If it's a new discussion in tablet mode, try to open it.
|
||||
if (data.discussionId) {
|
||||
// Discussion sent to server, search it in the list of discussions.
|
||||
const discussion = this.discussions.find((disc) => {
|
||||
return data.discussionId = disc.discussion;
|
||||
});
|
||||
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) {
|
||||
this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => {
|
||||
if (data.discussionId) {
|
||||
// Discussion changed, search it in the list of discussions.
|
||||
const discussion = this.discussions.find((disc) => {
|
||||
return data.discussionId = disc.discussion;
|
||||
});
|
||||
|
||||
if (discussion) {
|
||||
if (typeof data.locked != 'undefined') {
|
||||
discussion.locked = data.locked;
|
||||
}
|
||||
if (typeof data.pinned != 'undefined') {
|
||||
discussion.pinned = data.pinned;
|
||||
}
|
||||
if (typeof data.starred != 'undefined') {
|
||||
discussion.starred = data.starred;
|
||||
if (discussion) {
|
||||
if (typeof data.locked != 'undefined') {
|
||||
discussion.locked = data.locked;
|
||||
}
|
||||
if (typeof data.pinned != 'undefined') {
|
||||
discussion.pinned = data.pinned;
|
||||
}
|
||||
if (typeof data.starred != 'undefined') {
|
||||
discussion.starred = data.starred;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
if (typeof data.deleted != 'undefined' && data.deleted) {
|
||||
if (data.post.parent == 0 && this.splitviewCtrl && this.splitviewCtrl.isOn()) {
|
||||
// Discussion deleted, clear details page.
|
||||
this.splitviewCtrl.emptyDetails();
|
||||
}
|
||||
this.showLoadingAndRefresh(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Select the current opened discussion.
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
<ion-item text-wrap (click)="editPost()" *ngIf="canEdit">
|
||||
<ion-icon name="create" item-start></ion-icon>
|
||||
<h2>{{ 'addon.mod_forum.edit' | translate }}</h2>
|
||||
</ion-item>
|
||||
<ion-item text-wrap (click)="deletePost()" *ngIf="canDelete">
|
||||
<ion-icon name="trash" item-start></ion-icon>
|
||||
<h2 *ngIf="post.id">{{ 'addon.mod_forum.delete' | translate }}</h2>
|
||||
<h2 *ngIf="!post.id">{{ 'core.discard' | translate }}</h2>
|
||||
</ion-item>
|
||||
<ion-item text-wrap (click)="dismiss()" *ngIf="wordCount">
|
||||
<h2>{{ 'core.numwords' | translate: {'$a': wordCount} }}</h2>
|
||||
</ion-item>
|
||||
<ion-item text-wrap (click)="dismiss()" *ngIf="!canEdit && !canDelete && !wordCount">
|
||||
<h2>{{ 'core.nooptionavailable' | translate }}</h2>
|
||||
</ion-item>
|
||||
</core-loading>
|
|
@ -0,0 +1,97 @@
|
|||
// (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, OnInit } from '@angular/core';
|
||||
import { NavParams, ViewController } from 'ionic-angular';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { AddonModForumProvider } from '../../providers/forum';
|
||||
|
||||
/**
|
||||
* This component is meant to display a popover with the post options.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-forum-post-options-menu',
|
||||
templateUrl: 'addon-forum-post-options-menu.html'
|
||||
})
|
||||
export class AddonForumPostOptionsMenuComponent implements OnInit {
|
||||
post: any; // The post.
|
||||
forumId: number; // The forum Id.
|
||||
wordCount: number; // Number of words when available.
|
||||
canEdit = false;
|
||||
canDelete = false;
|
||||
loaded = false;
|
||||
|
||||
constructor(navParams: NavParams,
|
||||
protected viewCtrl: ViewController,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected forumProvider: AddonModForumProvider) {
|
||||
this.post = navParams.get('post');
|
||||
this.forumId = navParams.get('forumId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (this.forumId) {
|
||||
if (this.post.id) {
|
||||
this.forumProvider.getDiscussionPost(this.forumId, this.post.discussion, this.post.id).then((post) => {
|
||||
this.canDelete = post.capabilities.delete && this.forumProvider.isDeletePostAvailable();
|
||||
this.canEdit = false;
|
||||
this.wordCount = post.wordcount;
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error getting discussion post.');
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
} else {
|
||||
// Offline post, you can edit or discard the post.
|
||||
this.canEdit = true;
|
||||
this.canDelete = true;
|
||||
this.loaded = true;
|
||||
}
|
||||
} else {
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the popover.
|
||||
*/
|
||||
dismiss(): void {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a post.
|
||||
*/
|
||||
deletePost(): void {
|
||||
if (this.post.id) {
|
||||
this.viewCtrl.dismiss({action: 'delete'});
|
||||
} else {
|
||||
this.viewCtrl.dismiss({action: 'deleteoffline'});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a post.
|
||||
*/
|
||||
editPost(): void {
|
||||
if (this.post.id) {
|
||||
this.viewCtrl.dismiss({action: 'edit'});
|
||||
} else {
|
||||
this.viewCtrl.dismiss({action: 'editoffline'});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,9 @@
|
|||
<core-icon name="fa-star" class="addon-forum-star" *ngIf="post.parent == 0 && !post.pinned && post.starred"></core-icon>
|
||||
<core-format-text [text]="post.subject" contextLevel="module" [contextInstanceId]="forum && forum.cmid" [courseId]="courseId"></core-format-text>
|
||||
</h2>
|
||||
<button ion-button icon-only clear color="dark" (click)="showOptionsMenu($event)" *ngIf="optionsMenuEnabled">
|
||||
<core-icon name="more"></core-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="addon-mod-forum-post-info">
|
||||
<ion-avatar *ngIf="post.userfullname" core-user-avatar [user]="post" item-start [courseId]="courseId"></ion-avatar>
|
||||
|
@ -19,12 +22,15 @@
|
|||
<ion-note float-end padding-left text-end *ngIf="trackPosts && !post.postread" [attr.aria-label]="'addon.mod_forum.unread' | translate">
|
||||
<core-icon name="fa-circle" color="primary"></core-icon>
|
||||
</ion-note>
|
||||
<button ion-button icon-only clear color="dark" (click)="showOptionsMenu($event)" *ngIf="!displaySubject && optionsMenuEnabled">
|
||||
<core-icon name="more"></core-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ion-item>
|
||||
</ion-card-header>
|
||||
<ion-card-content [attr.padding-top]="post.parent == 0 || null">
|
||||
<div padding-bottom *ngIf="post.isprivatereply">
|
||||
<ion-note>{{ 'addon.mod_forum.postisprivatereply' | translate }}</ion-note>
|
||||
<ion-note color="danger">{{ 'addon.mod_forum.postisprivatereply' | translate }}</ion-note>
|
||||
</div>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="post.message" contextLevel="module" [contextInstanceId]="forum && forum.cmid" [courseId]="courseId"></core-format-text>
|
||||
<div no-lines *ngIf="post.attachments && post.attachments.length > 0">
|
||||
|
@ -40,16 +46,11 @@
|
|||
<core-rating-aggregate *ngIf="forum && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id" [courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale"></core-rating-aggregate>
|
||||
|
||||
<ion-item no-padding text-end *ngIf="post.id && post.canreply && !post.isprivatereply" class="addon-forum-reply-button">
|
||||
<button ion-button icon-left clear small (click)="showReply()" [attr.aria-controls]="'addon-forum-reply-edit-form-' + uniqueId" [attr.aria-expanded]="replyData.replyingTo === post.id">
|
||||
<ion-icon name="undo"></ion-icon> {{ 'addon.mod_forum.reply' | translate }}
|
||||
<button ion-button icon-left clear small (click)="showReplyForm()" [attr.aria-controls]="'addon-forum-reply-edit-form-' + uniqueId" [attr.aria-expanded]="replyData.replyingTo === post.id">
|
||||
<core-icon name="fa-reply"></core-icon> {{ 'addon.mod_forum.reply' | translate }}
|
||||
</button>
|
||||
</ion-item>
|
||||
</div>
|
||||
<ion-item text-end *ngIf="!post.id && (!replyData.isEditing || replyData.replyingTo != post.parent)">
|
||||
<button ion-button icon-left clear small (click)="editReply()" [attr.aria-controls]="'addon-forum-reply-edit-form-' + uniqueId" [attr.aria-expanded]="replyData.replyingTo === post.parent">
|
||||
<ion-icon name="create"></ion-icon> {{ 'addon.mod_forum.edit' | translate }}
|
||||
</button>
|
||||
</ion-item>
|
||||
|
||||
<ion-list [id]="'addon-forum-reply-edit-form-' + uniqueId" *ngIf="(post.id && !replyData.isEditing && replyData.replyingTo == post.id) || (!post.id && replyData.isEditing && replyData.replyingTo == post.parent)">
|
||||
<ion-item>
|
||||
|
@ -81,11 +82,6 @@
|
|||
<button ion-button block color="light" (click)="cancel()">{{ 'core.cancel' | translate }}</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row *ngIf="replyData.isEditing">
|
||||
<ion-col>
|
||||
<button ion-button block color="light" (click)="discard()">{{ 'core.discard' | translate }}</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
|
|
@ -14,18 +14,21 @@
|
|||
|
||||
import { Component, Input, Output, Optional, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Content } from 'ionic-angular';
|
||||
import { Content, PopoverController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
|
||||
import { CoreSyncProvider } from '@providers/sync';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonModForumProvider } from '../../providers/forum';
|
||||
import { AddonModForumHelperProvider } from '../../providers/helper';
|
||||
import { AddonModForumOfflineProvider } from '../../providers/offline';
|
||||
import { AddonModForumSyncProvider } from '../../providers/sync';
|
||||
import { CoreRatingInfo } from '@core/rating/providers/rating';
|
||||
import { CoreTagProvider } from '@core/tag/providers/tag';
|
||||
import { AddonForumPostOptionsMenuComponent } from '../post-options-menu/post-options-menu';
|
||||
|
||||
/**
|
||||
* Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
|
||||
|
@ -55,6 +58,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
advanced = false; // Display all form fields.
|
||||
tagsEnabled: boolean;
|
||||
displaySubject = true;
|
||||
optionsMenuEnabled = false;
|
||||
|
||||
protected syncId: string;
|
||||
|
||||
|
@ -69,7 +73,10 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
private forumOffline: AddonModForumOfflineProvider,
|
||||
private forumSync: AddonModForumSyncProvider,
|
||||
private tagProvider: CoreTagProvider,
|
||||
@Optional() private content: Content) {
|
||||
@Optional() private content: Content,
|
||||
protected popoverCtrl: PopoverController,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider) {
|
||||
this.onPostChange = new EventEmitter<void>();
|
||||
this.tagsEnabled = this.tagProvider.areTagsAvailableInSite();
|
||||
}
|
||||
|
@ -84,10 +91,44 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
this.displaySubject = this.post.parent == 0 ||
|
||||
(this.post.subject != this.defaultSubject && this.post.subject != 'Re: ' + this.defaultSubject &&
|
||||
this.post.subject != reTranslated + this.defaultSubject);
|
||||
|
||||
this.optionsMenuEnabled = !this.post.id || (this.forumProvider.isGetDiscussionPostAvailable() &&
|
||||
(this.forumProvider.isDeletePostAvailable()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data to new post, clearing temporary files and updating original data.
|
||||
* Deletes an online post.
|
||||
*/
|
||||
deletePost(): void {
|
||||
this.domUtils.showDeleteConfirm('addon.mod_forum.deletesure').then(() => {
|
||||
const modal = this.domUtils.showModalLoading('core.deleting', true);
|
||||
|
||||
this.forumProvider.deletePost(this.post.id).then((response) => {
|
||||
|
||||
const data = {
|
||||
forumId: this.forum.id,
|
||||
discussionId: this.discussionId,
|
||||
cmId: this.forum.cmid,
|
||||
deleted: response.status,
|
||||
post: this.post
|
||||
};
|
||||
|
||||
this.eventsProvider.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.domUtils.showToast('addon.mod_forum.deletedpost', true);
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModal(error);
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}).catch((error) => {
|
||||
// Do nothing.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data to new reply post, clearing temporary files and updating original data.
|
||||
*
|
||||
* @param replyingTo Id of post beeing replied.
|
||||
* @param isEditing True it's an offline reply beeing edited, false otherwise.
|
||||
|
@ -96,7 +137,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
* @param isPrivate True if it's private reply.
|
||||
* @param files Reply attachments.
|
||||
*/
|
||||
protected setReplyData(replyingTo?: number, isEditing?: boolean, subject?: string, message?: string, files?: any[],
|
||||
protected setReplyFormData(replyingTo?: number, isEditing?: boolean, subject?: string, message?: string, files?: any[],
|
||||
isPrivate?: boolean): void {
|
||||
// Delete the local files from the tmp folder if any.
|
||||
this.uploaderProvider.clearTmpFiles(this.replyData.files);
|
||||
|
@ -121,14 +162,52 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
this.advanced = this.replyData.files.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the context menu.
|
||||
*
|
||||
* @param e Click Event.
|
||||
*/
|
||||
showOptionsMenu(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const popover = this.popoverCtrl.create(AddonForumPostOptionsMenuComponent, {
|
||||
post: this.post,
|
||||
forumId: this.forum.id
|
||||
});
|
||||
popover.onDidDismiss((data) => {
|
||||
if (data && data.action) {
|
||||
switch (data.action) {
|
||||
case 'edit':
|
||||
// Not implemented.
|
||||
break;
|
||||
case 'editoffline':
|
||||
this.editOfflineReply();
|
||||
break;
|
||||
case 'delete':
|
||||
this.deletePost();
|
||||
break;
|
||||
case 'deleteoffline':
|
||||
this.discardOfflineReply();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
popover.present({
|
||||
ev: e
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this post as being replied to.
|
||||
*/
|
||||
showReply(): void {
|
||||
showReplyForm(): void {
|
||||
if (this.replyData.isEditing) {
|
||||
// User is editing a post, data needs to be resetted. Ask confirm if there is unsaved data.
|
||||
this.confirmDiscard().then(() => {
|
||||
this.setReplyData(this.post.id);
|
||||
this.setReplyFormData(this.post.id);
|
||||
|
||||
if (this.content) {
|
||||
setTimeout(() => {
|
||||
|
@ -143,7 +222,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
return;
|
||||
} else if (!this.replyData.replyingTo) {
|
||||
// User isn't replying, it's a brand new reply. Initialize the data.
|
||||
this.setReplyData(this.post.id);
|
||||
this.setReplyFormData(this.post.id);
|
||||
} else {
|
||||
// The post being replied has changed but the data will be kept.
|
||||
this.replyData.replyingTo = this.post.id;
|
||||
|
@ -162,13 +241,13 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Set this post as being edited to.
|
||||
*/
|
||||
editReply(): void {
|
||||
editOfflineReply(): void {
|
||||
// Ask confirm if there is unsaved data.
|
||||
this.confirmDiscard().then(() => {
|
||||
this.syncId = this.forumSync.getDiscussionSyncId(this.discussionId);
|
||||
this.syncProvider.blockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
|
||||
this.setReplyData(this.post.parent, true, this.post.subject, this.post.message, this.post.attachments,
|
||||
this.setReplyFormData(this.post.parent, true, this.post.subject, this.post.message, this.post.attachments,
|
||||
this.post.isprivatereply);
|
||||
}).catch(() => {
|
||||
// Cancelled.
|
||||
|
@ -259,7 +338,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
// Reset data.
|
||||
this.setReplyData();
|
||||
this.setReplyFormData();
|
||||
|
||||
this.onPostChange.emit();
|
||||
|
||||
|
@ -279,7 +358,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
cancel(): void {
|
||||
this.confirmDiscard().then(() => {
|
||||
// Reset data.
|
||||
this.setReplyData();
|
||||
this.setReplyFormData();
|
||||
|
||||
if (this.syncId) {
|
||||
this.syncProvider.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
|
@ -292,8 +371,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Discard offline reply.
|
||||
*/
|
||||
discard(): void {
|
||||
this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => {
|
||||
discardOfflineReply(): void {
|
||||
this.domUtils.showDeleteConfirm().then(() => {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.forumOffline.deleteReply(this.post.parent));
|
||||
|
@ -305,7 +384,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
|
||||
return Promise.all(promises).finally(() => {
|
||||
// Reset data.
|
||||
this.setReplyData();
|
||||
this.setReplyFormData();
|
||||
|
||||
this.onPostChange.emit();
|
||||
|
||||
|
@ -322,7 +401,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy {
|
|||
* Function called when rating is updated online.
|
||||
*/
|
||||
ratingUpdated(): void {
|
||||
this.forumProvider.invalidateDiscussionPosts(this.discussionId);
|
||||
this.forumProvider.invalidateDiscussionPosts(this.discussionId, this.forum.id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
"cannotcreatediscussion": "Could not create new discussion",
|
||||
"couldnotadd": "Could not add your post due to an unknown error",
|
||||
"cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.",
|
||||
"delete": "Delete",
|
||||
"deletedpost": "The post has been deleted",
|
||||
"deletesure": "Are you sure you want to delete this post?",
|
||||
"discussion": "Discussion",
|
||||
"discussionlistsortbycreatedasc": "Sort by creation date in ascending order",
|
||||
"discussionlistsortbycreateddesc": "Sort by creation date in descending order",
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, Optional, OnDestroy, ViewChild, NgZone } from '@angular/core';
|
||||
import { IonicPage, NavParams, Content } from 'ionic-angular';
|
||||
import { IonicPage, NavParams, Content, NavController } from 'ionic-angular';
|
||||
import { Network } from '@ionic-native/network';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
|
@ -46,8 +46,8 @@ export class AddonModForumDiscussionPage implements OnDestroy {
|
|||
|
||||
courseId: number;
|
||||
discussionId: number;
|
||||
forum: any;
|
||||
accessInfo: any;
|
||||
forum: any = {};
|
||||
accessInfo: any = {};
|
||||
discussion: any;
|
||||
posts: any[];
|
||||
discussionLoaded = false;
|
||||
|
@ -106,7 +106,8 @@ export class AddonModForumDiscussionPage implements OnDestroy {
|
|||
private forumHelper: AddonModForumHelperProvider,
|
||||
private forumSync: AddonModForumSyncProvider,
|
||||
private ratingOffline: CoreRatingOfflineProvider,
|
||||
@Optional() private svComponent: CoreSplitViewComponent) {
|
||||
@Optional() private svComponent: CoreSplitViewComponent,
|
||||
protected navCtrl: NavController) {
|
||||
this.courseId = navParams.get('courseId');
|
||||
this.cmId = navParams.get('cmId');
|
||||
this.forumId = navParams.get('forumId');
|
||||
|
@ -190,17 +191,32 @@ export class AddonModForumDiscussionPage implements OnDestroy {
|
|||
});
|
||||
|
||||
this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data) => {
|
||||
this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => {
|
||||
if (typeof data.locked != 'undefined') {
|
||||
this.discussion.locked = data.locked;
|
||||
}
|
||||
if (typeof data.pinned != 'undefined') {
|
||||
this.discussion.pinned = data.pinned;
|
||||
}
|
||||
if (typeof data.starred != 'undefined') {
|
||||
this.discussion.starred = data.starred;
|
||||
}
|
||||
});
|
||||
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.cmId) {
|
||||
this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => {
|
||||
if (typeof data.locked != 'undefined') {
|
||||
this.discussion.locked = data.locked;
|
||||
}
|
||||
if (typeof data.pinned != 'undefined') {
|
||||
this.discussion.pinned = data.pinned;
|
||||
}
|
||||
if (typeof data.starred != 'undefined') {
|
||||
this.discussion.starred = data.starred;
|
||||
}
|
||||
|
||||
if (typeof data.deleted != 'undefined' && data.deleted) {
|
||||
if (data.post.parent == 0) {
|
||||
if (this.svComponent && this.svComponent.isOn()) {
|
||||
this.svComponent.emptyDetails();
|
||||
} else {
|
||||
this.navCtrl.pop();
|
||||
}
|
||||
} else {
|
||||
this.discussionLoaded = false;
|
||||
this.refreshPosts();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -358,8 +374,6 @@ export class AddonModForumDiscussionPage implements OnDestroy {
|
|||
return Promise.all(promises);
|
||||
}).catch(() => {
|
||||
// Ignore errors.
|
||||
this.forum = {};
|
||||
this.accessInfo = {};
|
||||
}).then(() => {
|
||||
this.defaultSubject = this.translate.instant('addon.mod_forum.re') + ' ' +
|
||||
(this.discussion ? this.discussion.subject : '');
|
||||
|
@ -496,7 +510,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
|
|||
|
||||
const promises = [
|
||||
this.forumProvider.invalidateForumData(this.courseId),
|
||||
this.forumProvider.invalidateDiscussionPosts(this.discussionId),
|
||||
this.forumProvider.invalidateDiscussionPosts(this.discussionId, this.forumId),
|
||||
this.forumProvider.invalidateAccessInformation(this.forumId),
|
||||
this.forumProvider.invalidateCanAddDiscussion(this.forumId)
|
||||
];
|
||||
|
|
|
@ -74,6 +74,7 @@ export class AddonModForumProvider {
|
|||
|
||||
/**
|
||||
* Get common part of cache key for can add discussion WS calls.
|
||||
* TODO: Use getForumDataCacheKey as a prefix.
|
||||
*
|
||||
* @param forumId Forum ID.
|
||||
* @return Cache key.
|
||||
|
@ -82,6 +83,38 @@ export class AddonModForumProvider {
|
|||
return this.ROOT_CACHE_KEY + 'canadddiscussion:' + forumId + ':';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prefix cache key for all forum activity data WS calls.
|
||||
*
|
||||
* @param forumId Forum ID.
|
||||
* @return Cache key.
|
||||
*/
|
||||
protected getForumDataPrefixCacheKey(forumId: number): string {
|
||||
return this.ROOT_CACHE_KEY + forumId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for discussion post data WS calls.
|
||||
*
|
||||
* @param forumId Forum ID.
|
||||
* @param discussionId Discussion ID.
|
||||
* @param postId Course ID.
|
||||
* @return Cache key.
|
||||
*/
|
||||
protected getDiscussionPostDataCacheKey(forumId: number, discussionId: number, postId: number): string {
|
||||
return this.getForumDiscussionDataCacheKey(forumId, discussionId) + ':post:' + postId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for forum data WS calls.
|
||||
*
|
||||
* @param courseId Course ID.
|
||||
* @return Cache key.
|
||||
*/
|
||||
protected getForumDiscussionDataCacheKey(forumId: number, discussionId: number): string {
|
||||
return this.getForumDataPrefixCacheKey(forumId) + ':discussion:' + discussionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for forum data WS calls.
|
||||
*
|
||||
|
@ -94,6 +127,7 @@ export class AddonModForumProvider {
|
|||
|
||||
/**
|
||||
* Get cache key for forum access information WS calls.
|
||||
* TODO: Use getForumDataCacheKey as a prefix.
|
||||
*
|
||||
* @param forumId Forum ID.
|
||||
* @return Cache key.
|
||||
|
@ -104,6 +138,7 @@ export class AddonModForumProvider {
|
|||
|
||||
/**
|
||||
* Get cache key for forum discussion posts WS calls.
|
||||
* TODO: Use getForumDiscussionDataCacheKey instead.
|
||||
*
|
||||
* @param discussionId Discussion ID.
|
||||
* @return Cache key.
|
||||
|
@ -218,6 +253,24 @@ export class AddonModForumProvider {
|
|||
return this.canAddDiscussion(forumId, AddonModForumProvider.ALL_PARTICIPANTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a post.
|
||||
*
|
||||
* @param postId Post id.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
* @since 3.8
|
||||
*/
|
||||
deletePost(postId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
postid: postId
|
||||
};
|
||||
|
||||
return site.write('mod_forum_delete_post', params);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the starting post of a discussion from a list of posts. The post is removed from the array passed as a parameter.
|
||||
*
|
||||
|
@ -244,6 +297,26 @@ export class AddonModForumProvider {
|
|||
return this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan(['3.1.5', '3.2.2']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not getDiscussionPost WS available or not.
|
||||
*
|
||||
* @return If WS is avalaible.
|
||||
* @since 3.8
|
||||
*/
|
||||
isGetDiscussionPostAvailable(): boolean {
|
||||
return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_get_discussion_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not deletePost WS available or not.
|
||||
*
|
||||
* @return If WS is avalaible.
|
||||
* @since 3.8
|
||||
*/
|
||||
isDeletePostAvailable(): boolean {
|
||||
return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_delete_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format discussions, setting groupname if the discussion group is valid.
|
||||
*
|
||||
|
@ -306,6 +379,35 @@ export class AddonModForumProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a particular discussion post.
|
||||
*
|
||||
* @param forumId Forum ID.
|
||||
* @param discussionId Discussion ID.
|
||||
* @param postId Post ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the post is retrieved.
|
||||
*/
|
||||
getDiscussionPost(forumId: number, discussionId: number, postId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
postid: postId
|
||||
};
|
||||
const preSets = {
|
||||
cacheKey: this.getDiscussionPostDataCacheKey(forumId, discussionId, postId),
|
||||
updateFrequency: CoreSite.FREQUENCY_RARELY
|
||||
};
|
||||
|
||||
return site.read('mod_forum_get_discussion_post', params, preSets).then((response) => {
|
||||
if (response.post) {
|
||||
return response.post;
|
||||
} else {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a forum by course module ID.
|
||||
*
|
||||
|
@ -641,7 +743,7 @@ export class AddonModForumProvider {
|
|||
const promises = [];
|
||||
|
||||
response.discussions.forEach((discussion) => {
|
||||
promises.push(this.invalidateDiscussionPosts(discussion.discussion));
|
||||
promises.push(this.invalidateDiscussionPosts(discussion.discussion, forum.id));
|
||||
});
|
||||
|
||||
return this.utils.allPromises(promises);
|
||||
|
@ -673,12 +775,19 @@ export class AddonModForumProvider {
|
|||
* Invalidates forum discussion posts.
|
||||
*
|
||||
* @param discussionId Discussion ID.
|
||||
* @param forumId Forum ID. If not set, we can't invalidate individual post information.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateDiscussionPosts(discussionId: number, siteId?: string): Promise<any> {
|
||||
invalidateDiscussionPosts(discussionId: number, forumId?: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getDiscussionPostsCacheKey(discussionId));
|
||||
const promises = [site.invalidateWsCacheForKey(this.getDiscussionPostsCacheKey(discussionId))];
|
||||
|
||||
if (forumId) {
|
||||
promises.push(site.invalidateWsCacheForKeyStartingWith(this.getForumDiscussionDataCacheKey(forumId, discussionId)));
|
||||
}
|
||||
|
||||
return this.utils.allPromises(promises);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -844,7 +953,7 @@ export class AddonModForumProvider {
|
|||
* @param discussionId DIscussion id.
|
||||
* @param locked True to lock, false to unlock.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resvoled when done.
|
||||
* @return Promise resolved when done.
|
||||
* @since 3.7
|
||||
*/
|
||||
setLockState(forumId: number, discussionId: number, locked: boolean, siteId?: string): Promise<any> {
|
||||
|
@ -878,7 +987,7 @@ export class AddonModForumProvider {
|
|||
* @param discussionId Discussion id.
|
||||
* @param locked True to pin, false to unpin.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resvoled when done.
|
||||
* @return Promise resolved when done.
|
||||
* @since 3.7
|
||||
*/
|
||||
setPinState(discussionId: number, pinned: boolean, siteId?: string): Promise<any> {
|
||||
|
@ -898,7 +1007,7 @@ export class AddonModForumProvider {
|
|||
* @param discussionId Discussion id.
|
||||
* @param starred True to star, false to unstar.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resvoled when done.
|
||||
* @return Promise resolved when done.
|
||||
* @since 3.7
|
||||
*/
|
||||
toggleFavouriteState(discussionId: number, starred: boolean, siteId?: string): Promise<any> {
|
||||
|
|
|
@ -62,7 +62,7 @@ export class AddonModForumPushClickHandler implements CorePushNotificationsClick
|
|||
pageParams.postId = Number(data.postid || contextUrlParams.urlHash.replace('p', ''));
|
||||
}
|
||||
|
||||
return this.forumProvider.invalidateDiscussionPosts(pageParams.discussionId, notification.site).catch(() => {
|
||||
return this.forumProvider.invalidateDiscussionPosts(pageParams.discussionId, undefined, notification.site).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return this.linkHelper.goInSite(undefined, 'AddonModForumDiscussionPage', pageParams, notification.site);
|
||||
|
|
|
@ -326,7 +326,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider {
|
|||
updated = true;
|
||||
|
||||
// Invalidate discussions of updated ratings.
|
||||
promises.push(this.forumProvider.invalidateDiscussionPosts(result.itemSet.itemSetId, siteId));
|
||||
promises.push(this.forumProvider.invalidateDiscussionPosts(result.itemSet.itemSetId, undefined, siteId));
|
||||
}
|
||||
if (result.warnings.length) {
|
||||
// Fetch forum to construct the warning message.
|
||||
|
@ -502,7 +502,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider {
|
|||
if (forumId) {
|
||||
promises.push(this.forumProvider.invalidateDiscussionsList(forumId, siteId));
|
||||
}
|
||||
promises.push(this.forumProvider.invalidateDiscussionPosts(discussionId, siteId));
|
||||
promises.push(this.forumProvider.invalidateDiscussionPosts(discussionId, forumId, siteId));
|
||||
|
||||
return this.utils.allPromises(promises).catch(() => {
|
||||
// Ignore errors.
|
||||
|
|
|
@ -571,6 +571,9 @@
|
|||
"addon.mod_forum.cannotcreatediscussion": "Could not create new discussion",
|
||||
"addon.mod_forum.couldnotadd": "Could not add your post due to an unknown error",
|
||||
"addon.mod_forum.cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.",
|
||||
"addon.mod_forum.delete": "Delete",
|
||||
"addon.mod_forum.deletedpost": "The post has been deleted",
|
||||
"addon.mod_forum.deletesure": "Are you sure you want to delete this post?",
|
||||
"addon.mod_forum.discussion": "Discussion",
|
||||
"addon.mod_forum.discussionlistsortbycreatedasc": "Sort by creation date in ascending order",
|
||||
"addon.mod_forum.discussionlistsortbycreateddesc": "Sort by creation date in descending order",
|
||||
|
@ -1713,6 +1716,7 @@
|
|||
"core.nocomments": "No comments",
|
||||
"core.nograde": "No grade",
|
||||
"core.none": "None",
|
||||
"core.nooptionavailable": "No option available",
|
||||
"core.nopasswordchangeforced": "You cannot proceed without changing your password.",
|
||||
"core.nopermissionerror": "Sorry, but you do not currently have permissions to do that",
|
||||
"core.nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).",
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
|
@ -48,9 +47,12 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
editMode: boolean;
|
||||
relativePath: string;
|
||||
|
||||
constructor(private mimeUtils: CoreMimetypeUtilsProvider, private utils: CoreUtilsProvider, private translate: TranslateService,
|
||||
private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private timeUtils: CoreTimeUtilsProvider) {
|
||||
constructor(private mimeUtils: CoreMimetypeUtilsProvider,
|
||||
private utils: CoreUtilsProvider,
|
||||
private textUtils: CoreTextUtilsProvider,
|
||||
private fileProvider: CoreFileProvider,
|
||||
private domUtils: CoreDomUtilsProvider,
|
||||
private timeUtils: CoreTimeUtilsProvider) {
|
||||
this.onDelete = new EventEmitter();
|
||||
this.onRename = new EventEmitter();
|
||||
this.onClick = new EventEmitter();
|
||||
|
|
|
@ -182,6 +182,7 @@
|
|||
"notapplicable": "n/a",
|
||||
"notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
||||
"notice": "Notice",
|
||||
"nooptionavailable": "No option available",
|
||||
"notingroup": "Sorry, but you need to be part of a group to see this page.",
|
||||
"notsent": "Not sent",
|
||||
"now": "now",
|
||||
|
|
Loading…
Reference in New Issue