commit
5dd2f2c93d
|
@ -21,7 +21,6 @@ import { CoreTagComponentsModule } from '@features/tag/components/components.mod
|
|||
import { CoreRatingComponentsModule } from '@features/rating/components/components.module';
|
||||
|
||||
import { AddonModForumDiscussionOptionsMenuComponent } from './discussion-options-menu/discussion-options-menu';
|
||||
import { AddonModForumEditPostComponent } from './edit-post/edit-post';
|
||||
import { AddonModForumIndexComponent } from './index/index';
|
||||
import { AddonModForumPostComponent } from './post/post';
|
||||
import { AddonModForumPostOptionsMenuComponent } from './post-options-menu/post-options-menu';
|
||||
|
@ -30,7 +29,6 @@ import { AddonModForumSortOrderSelectorComponent } from './sort-order-selector/s
|
|||
@NgModule({
|
||||
declarations: [
|
||||
AddonModForumDiscussionOptionsMenuComponent,
|
||||
AddonModForumEditPostComponent,
|
||||
AddonModForumIndexComponent,
|
||||
AddonModForumPostComponent,
|
||||
AddonModForumPostOptionsMenuComponent,
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<h2>{{ 'addon.mod_forum.yourreply' | translate }}</h2>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||
<ion-icon name="fas-times" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<form #editFormEl>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.subject' | translate }}</ion-label>
|
||||
<ion-input type="text" [placeholder]="'addon.mod_forum.subject' | translate" [(ngModel)]="replyData.subject" name="subject">
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
<core-rich-text-editor elementId="message"
|
||||
[name]="'mod_forum_reply_' + replyData.id" [control]="messageControl"
|
||||
[placeholder]="'addon.mod_forum.replyplaceholder' | translate" [autoSave]="true"
|
||||
[component]="component" [componentId]="componentId" [draftExtraParams]="{edit: replyData.id}"
|
||||
contextLevel="module" [contextInstanceId]="forum.cmid"
|
||||
(contentChanged)="onMessageChange($event)">
|
||||
</core-rich-text-editor>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
button class="divider ion-text-wrap"
|
||||
(click)="toggleAdvanced()"
|
||||
role="heading"
|
||||
detail="false"
|
||||
[attr.aria-expanded]="advanced"
|
||||
aria-controls="addon-mod-forum-advanced"
|
||||
[attr.aria-label]="(advanced ? 'core.hideadvanced' : 'core.showadvanced') | translate"
|
||||
>
|
||||
<ion-icon *ngIf="!advanced" name="fas-caret-right" flip-rtl slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-icon *ngIf="advanced" name="fas-caret-down" slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-label><h2>{{ 'addon.mod_forum.advanced' | translate }}</h2></ion-label>
|
||||
</ion-item>
|
||||
<div *ngIf="advanced" id="addon-mod-forum-advanced">
|
||||
<core-attachments *ngIf="forum.id && forum.maxattachments > 0"
|
||||
[maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [allowOffline]="true" [files]="replyData.files"
|
||||
[component]="component" [componentId]="forum.cmid" [courseId]="forum.course">
|
||||
</core-attachments>
|
||||
</div>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<ion-button expand="block" (click)="reply($event)" [disabled]="replyData.subject == '' || replyData.message == null">
|
||||
{{ 'core.savechanges' | translate }}
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<ion-button expand="block" color="light" (click)="closeModal()">{{ 'core.cancel' | translate }}</ion-button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</form>
|
||||
</ion-content>
|
|
@ -1,150 +0,0 @@
|
|||
// (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, ViewChild, ElementRef, Input, OnInit } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { ModalController, Translate } from '@singletons';
|
||||
import { AddonModForumData, AddonModForumPost, AddonModForumReply } from '@addons/mod/forum/services/forum';
|
||||
import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreFileEntry } from '@services/file-helper';
|
||||
|
||||
/**
|
||||
* Page that displays a form to edit discussion post.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-forum-edit-post',
|
||||
templateUrl: 'edit-post.html',
|
||||
})
|
||||
export class AddonModForumEditPostComponent implements OnInit {
|
||||
|
||||
@ViewChild('editFormEl') formElement!: ElementRef;
|
||||
|
||||
@Input() component!: string; // Component this post belong to.
|
||||
@Input() componentId!: number; // Component ID.
|
||||
@Input() forum!: AddonModForumData; // The forum the post belongs to. Required for attachments and offline posts.
|
||||
@Input() post!: AddonModForumPost;
|
||||
|
||||
messageControl = new FormControl();
|
||||
advanced = false; // Display all form fields.
|
||||
replyData!: AddonModForumReply;
|
||||
originalData!: Omit<AddonModForumReply, 'id'>; // Object with the original post data. Usually shared between posts.
|
||||
|
||||
protected forceLeave = false; // To allow leaving the page without checking for changes.
|
||||
|
||||
ngOnInit(): void {
|
||||
// @todo Override android back button to show confirmation before dismiss.
|
||||
|
||||
this.replyData = {
|
||||
id: this.post.id,
|
||||
subject: this.post.subject,
|
||||
message: this.post.message,
|
||||
files: this.post.attachments || [],
|
||||
};
|
||||
|
||||
// Delete the local files from the tmp folder if any.
|
||||
CoreFileUploader.clearTmpFiles(this.replyData.files as CoreFileEntry[]);
|
||||
|
||||
// Update rich text editor.
|
||||
this.messageControl.setValue(this.replyData.message);
|
||||
|
||||
// Update original data.
|
||||
this.originalData = {
|
||||
subject: this.replyData.subject,
|
||||
message: this.replyData.message,
|
||||
files: this.replyData.files.slice(),
|
||||
};
|
||||
|
||||
// Show advanced fields if any of them has not the default value.
|
||||
this.advanced = this.replyData.files.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message changed.
|
||||
*
|
||||
* @param text The new text.
|
||||
*/
|
||||
onMessageChange(text: string): void {
|
||||
this.replyData.message = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close modal.
|
||||
*
|
||||
* @param data Data to return to the page.
|
||||
*/
|
||||
async closeModal(data?: AddonModForumReply): Promise<void> {
|
||||
const confirmDismiss = await this.confirmDismiss();
|
||||
|
||||
if (!confirmDismiss) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, false, CoreSites.getCurrentSiteId());
|
||||
} else {
|
||||
CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
|
||||
}
|
||||
|
||||
ModalController.dismiss(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to this post.
|
||||
*
|
||||
* @param e Click event.
|
||||
*/
|
||||
reply(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Close the modal, sending the input data.
|
||||
this.forceLeave = true;
|
||||
this.closeModal(this.replyData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private async confirmDismiss(): Promise<boolean> {
|
||||
if (this.forceLeave || !AddonModForumHelper.hasPostDataChanged(this.replyData, this.originalData)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Show confirmation if some data has been modified.
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit'));
|
||||
|
||||
// Delete the local files from the tmp folder.
|
||||
CoreFileUploader.clearTmpFiles(this.replyData.files as CoreFileEntry[]);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -122,11 +122,7 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy
|
|||
* Edit a post.
|
||||
*/
|
||||
editPost(): void {
|
||||
if (!this.offlinePost) {
|
||||
PopoverController.dismiss({ action: 'edit' });
|
||||
} else {
|
||||
PopoverController.dismiss({ action: 'editoffline' });
|
||||
}
|
||||
PopoverController.dismiss({ action: 'edit' });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,111 +1,115 @@
|
|||
<div class="addon-mod_forum-post">
|
||||
<ion-card-header class="ion-text-wrap ion-no-padding" id="addon-mod_forum-post-{{post.id}}">
|
||||
<ion-item class="ion-text-wrap" [class.highlight]="highlight" lines="none">
|
||||
<ion-label>
|
||||
<div class="addon-mod-forum-post-title" *ngIf="displaySubject">
|
||||
<h2 class="ion-text-wrap">
|
||||
<ion-icon name="fas-map-pin" *ngIf="discussion && !post.parentid && discussion.pinned"
|
||||
[attr.aria-label]="'addon.mod_forum.discussionpinned' | translate">
|
||||
</ion-icon>
|
||||
<ion-icon name="fas-star" class="addon-forum-star"
|
||||
[attr.aria-label]="'addon.mod_forum.favourites' | translate"
|
||||
*ngIf="discussion && !post.parentid && !discussion.pinned && discussion.starred">
|
||||
</ion-icon>
|
||||
<core-format-text
|
||||
[text]="post.subject"
|
||||
contextLevel="module" [contextInstanceId]="forum && forum.cmid" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
<ion-note *ngIf="trackPosts && post.unread"
|
||||
class="ion-float-end ion-padding-start ion-text-end" [attr.aria-label]="'addon.mod_forum.unread' | translate">
|
||||
<ion-icon name="fas-circle" color="primary" aria-hidden="true"></ion-icon>
|
||||
</ion-note>
|
||||
<ion-button *ngIf="optionsMenuEnabled"
|
||||
fill="clear" color="dark" [attr.aria-label]="('core.displayoptions' | translate)"
|
||||
(click)="showOptionsMenu($event)">
|
||||
<ion-icon name="ellipsis-vertical" slot="icon-only" aria-hidden="true">
|
||||
</ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<div class="addon-mod-forum-post-info">
|
||||
<core-user-avatar *ngIf="post.author && post.author.fullname" [user]="post.author" slot="start" [courseId]="courseId">
|
||||
</core-user-avatar>
|
||||
<div class="addon-mod-forum-post-author">
|
||||
<span *ngIf="post.author && post.author.fullname">{{post.author.fullname}}</span>
|
||||
<p *ngIf="post.author && post.author.groups">
|
||||
<ng-container *ngFor="let group of post.author.groups">
|
||||
<ion-icon name="fas-users" [attr.aria-label]="'addon.mod_forum.group' | translate">
|
||||
</ion-icon> {{ group.name }}
|
||||
</ng-container>
|
||||
</p>
|
||||
<p *ngIf="post.timecreated">{{post.timecreated * 1000 | coreFormatDate: "strftimerecentfull"}}</p>
|
||||
<p *ngIf="!post.timecreated">
|
||||
<ion-icon name="fas-clock" aria-hidden="true"></ion-icon> {{ 'core.notsent' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<ng-container *ngIf="!displaySubject">
|
||||
<ion-note *ngIf="trackPosts && post.unread"
|
||||
class="ion-float-end ion-padding-start ion-text-end" [attr.aria-label]="'addon.mod_forum.unread' | translate">
|
||||
<ng-container *ngIf="!formData.isEditing || !showForm">
|
||||
<ion-card-header class="ion-text-wrap ion-no-padding" id="addon-mod_forum-post-{{post.id}}">
|
||||
<ion-item class="ion-text-wrap" [class.highlight]="highlight" lines="none">
|
||||
<ion-label>
|
||||
<div class="addon-mod-forum-post-title" *ngIf="displaySubject">
|
||||
<h2 class="ion-text-wrap">
|
||||
<ion-icon name="fas-map-pin" *ngIf="discussion && !post.parentid && discussion.pinned"
|
||||
[attr.aria-label]="'addon.mod_forum.discussionpinned' | translate">
|
||||
</ion-icon>
|
||||
<ion-icon name="fas-star" class="addon-forum-star"
|
||||
[attr.aria-label]="'addon.mod_forum.favourites' | translate"
|
||||
*ngIf="discussion && !post.parentid && !discussion.pinned && discussion.starred">
|
||||
</ion-icon>
|
||||
<core-format-text
|
||||
[text]="post.subject"
|
||||
contextLevel="module" [contextInstanceId]="forum && forum.cmid" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
<ion-note *ngIf="trackPosts && post.unread" class="ion-float-end ion-padding-start ion-text-end"
|
||||
[attr.aria-label]="'addon.mod_forum.unread' | translate">
|
||||
<ion-icon name="fas-circle" color="primary" aria-hidden="true"></ion-icon>
|
||||
</ion-note>
|
||||
<ion-button *ngIf="optionsMenuEnabled"
|
||||
fill="clear" color="dark" [attr.aria-label]="('core.displayoptions' | translate)"
|
||||
(click)="showOptionsMenu($event)">
|
||||
<ion-icon name="ellipsis-vertical" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
<ion-icon name="ellipsis-vertical" slot="icon-only" aria-hidden="true">
|
||||
</ion-icon>
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card-header>
|
||||
<ion-card-content [class]="post.parentid == 0 ? 'ion-padding-top' : ''">
|
||||
<div class="ion-padding-bottom" *ngIf="post.isprivatereply">
|
||||
<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 lines="none" *ngIf="post.attachments && post.attachments.length > 0">
|
||||
<core-files [files]="post.attachments" [component]="component" [componentId]="componentId" showInline="true">
|
||||
</core-files>
|
||||
</div>
|
||||
</ion-card-content>
|
||||
<div class="addon-mod-forum-post-more-info">
|
||||
<ion-item class="ion-text-wrap" *ngIf="tagsEnabled && post.tags && post.tags.length > 0" lines="none">
|
||||
<div slot="start">{{ 'core.tag.tags' | translate }}:</div>
|
||||
<ion-label>
|
||||
<core-tag-list [tags]="post.tags"></core-tag-list>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<core-rating-rate *ngIf="forum && ratingInfo"
|
||||
[ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id"
|
||||
[itemSetId]="discussionId" [courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale"
|
||||
[userId]="post.author.id" (onUpdate)="ratingUpdated()">
|
||||
</core-rating-rate>
|
||||
<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>
|
||||
</div>
|
||||
<div class="addon-mod-forum-post-info">
|
||||
<core-user-avatar *ngIf="post.author && post.author.fullname" [user]="post.author" slot="start"
|
||||
[courseId]="courseId">
|
||||
</core-user-avatar>
|
||||
<div class="addon-mod-forum-post-author">
|
||||
<span *ngIf="post.author && post.author.fullname">{{post.author.fullname}}</span>
|
||||
<p *ngIf="post.author && post.author.groups">
|
||||
<ng-container *ngFor="let group of post.author.groups">
|
||||
<ion-icon name="fas-users" [attr.aria-label]="'addon.mod_forum.group' | translate">
|
||||
</ion-icon> {{ group.name }}
|
||||
</ng-container>
|
||||
</p>
|
||||
<p *ngIf="post.timecreated">{{post.timecreated * 1000 | coreFormatDate: "strftimerecentfull"}}</p>
|
||||
<p *ngIf="!post.timecreated">
|
||||
<ion-icon name="fas-clock" aria-hidden="true"></ion-icon> {{ 'core.notsent' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<ng-container *ngIf="!displaySubject">
|
||||
<ion-note *ngIf="trackPosts && post.unread" class="ion-float-end ion-padding-start ion-text-end"
|
||||
[attr.aria-label]="'addon.mod_forum.unread' | translate">
|
||||
<ion-icon name="fas-circle" color="primary" aria-hidden="true"></ion-icon>
|
||||
</ion-note>
|
||||
<ion-button *ngIf="optionsMenuEnabled"
|
||||
fill="clear" color="dark" [attr.aria-label]="('core.displayoptions' | translate)"
|
||||
(click)="showOptionsMenu($event)">
|
||||
<ion-icon name="ellipsis-vertical" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card-header>
|
||||
<ion-card-content [class]="post.parentid == 0 ? 'ion-padding-top' : ''">
|
||||
<div class="ion-padding-bottom" *ngIf="post.isprivatereply">
|
||||
<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 lines="none" *ngIf="post.attachments && post.attachments.length > 0">
|
||||
<core-files [files]="post.attachments" [component]="component" [componentId]="componentId" showInline="true">
|
||||
</core-files>
|
||||
</div>
|
||||
</ion-card-content>
|
||||
<div class="addon-mod-forum-post-more-info">
|
||||
<ion-item class="ion-text-wrap" *ngIf="tagsEnabled && post.tags && post.tags.length > 0" lines="none">
|
||||
<div slot="start">{{ 'core.tag.tags' | translate }}:</div>
|
||||
<ion-label>
|
||||
<core-tag-list [tags]="post.tags"></core-tag-list>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<core-rating-rate *ngIf="forum && ratingInfo"
|
||||
[ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id"
|
||||
[itemSetId]="discussionId" [courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale"
|
||||
[userId]="post.author.id" (onUpdate)="ratingUpdated()">
|
||||
</core-rating-rate>
|
||||
<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 *ngIf="post.id > 0 && post.capabilities.reply && !post.isprivatereply"
|
||||
class="ion-no-padding ion-text-end addon-forum-reply-button">
|
||||
<ion-label>
|
||||
<ion-button fill="clear" size="small"
|
||||
[attr.aria-controls]="'addon-forum-reply-edit-form-' + uniqueId"
|
||||
[attr.aria-expanded]="replyData.replyingTo === post.id"
|
||||
(click)="showReplyForm($event)">
|
||||
<ion-icon name="fas-reply" slot="start" aria-hidden="true"></ion-icon>
|
||||
{{ 'addon.mod_forum.reply' | translate }}
|
||||
</ion-button>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
<ion-item *ngIf="post.id > 0 && post.capabilities.reply && !post.isprivatereply"
|
||||
class="ion-no-padding ion-text-end addon-forum-reply-button">
|
||||
<ion-label>
|
||||
<ion-button fill="clear" size="small"
|
||||
[attr.aria-controls]="'addon-forum-reply-edit-form-' + uniqueId"
|
||||
[attr.aria-expanded]="formData.replyingTo === post.id"
|
||||
(click)="showReplyForm($event)">
|
||||
<ion-icon name="fas-reply" slot="start" aria-hidden="true"></ion-icon>
|
||||
{{ 'addon.mod_forum.reply' | translate }}
|
||||
</ion-button>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<form *ngIf="showForm"
|
||||
[id]="'addon-forum-reply-edit-form-' + uniqueId" #replyFormEl>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_forum.subject' | translate }}</ion-label>
|
||||
<ion-input type="text" [placeholder]="'addon.mod_forum.subject' | translate" [(ngModel)]="replyData.subject" name="subject">
|
||||
<ion-input type="text" [placeholder]="'addon.mod_forum.subject' | translate" [(ngModel)]="formData.subject"
|
||||
name="subject">
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
|
@ -119,7 +123,7 @@
|
|||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="accessInfo.canpostprivatereply">
|
||||
<ion-label>{{ 'addon.mod_forum.privatereply' | translate }}</ion-label>
|
||||
<ion-checkbox slot="end" [(ngModel)]="replyData.isprivatereply" name="isprivatereply"></ion-checkbox>
|
||||
<ion-checkbox slot="end" [(ngModel)]="formData.isprivatereply" name="isprivatereply"></ion-checkbox>
|
||||
</ion-item>
|
||||
<ng-container *ngIf="forum.id && forum.maxattachments > 0">
|
||||
<ion-item
|
||||
|
@ -138,7 +142,7 @@
|
|||
</ion-item>
|
||||
<div *ngIf="advanced" [id]="'addon-forum-reply-edit-form-advanced-' + uniqueId">
|
||||
<core-attachments
|
||||
[files]="replyData.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments"
|
||||
[files]="formData.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments"
|
||||
[component]="component" [componentId]="forum.cmid" [allowOffline]="true" [courseId]="courseId">
|
||||
</core-attachments>
|
||||
</div>
|
||||
|
@ -146,7 +150,7 @@
|
|||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<ion-button expand="block" (click)="reply()" [disabled]="replyData.subject == '' || replyData.message == null">
|
||||
<ion-button expand="block" (click)="send()" [disabled]="formData.subject == '' || formData.message == null">
|
||||
{{ 'addon.mod_forum.posttoforum' | translate }}
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
|
|
|
@ -36,8 +36,7 @@ import {
|
|||
AddonModForumDiscussion,
|
||||
AddonModForumPost,
|
||||
AddonModForumProvider,
|
||||
AddonModForumReply,
|
||||
AddonModForumUpdateDiscussionPostWSOptionsObject,
|
||||
AddonModForumPostFormData,
|
||||
} from '../../services/forum';
|
||||
import { CoreTag } from '@features/tag/services/tag';
|
||||
import { Translate } from '@singletons';
|
||||
|
@ -47,13 +46,13 @@ import { AddonModForumSync } from '../../services/forum-sync';
|
|||
import { CoreSync } from '@services/sync';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { AddonModForumHelper } from '../../services/forum-helper';
|
||||
import { AddonModForumOffline, AddonModForumReplyOptions } from '../../services/forum-offline';
|
||||
import { AddonModForumOffline } from '../../services/forum-offline';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { AddonModForumPostOptionsMenuComponent } from '../post-options-menu/post-options-menu';
|
||||
import { AddonModForumEditPostComponent } from '../edit-post/edit-post';
|
||||
import { CoreRatingInfo } from '@features/rating/services/rating';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreFileEntry } from '@services/file-helper';
|
||||
import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussion.page';
|
||||
|
||||
/**
|
||||
* Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
|
||||
|
@ -71,8 +70,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
@Input() discussion?: AddonModForumDiscussion; // Post's' discussion, only for starting posts.
|
||||
@Input() component!: string; // Component this post belong to.
|
||||
@Input() componentId!: number; // Component ID.
|
||||
@Input() replyData!: AddonModForumReply; // Object with the new post data. Usually shared between posts.
|
||||
@Input() originalData!: Omit<AddonModForumReply, 'id'>; // Object with the original post data. Usually shared between posts.
|
||||
@Input() formData!: AddonModForumSharedPostFormData; // Object with the new post data. Usually shared between posts.
|
||||
@Input() originalData!: Omit<AddonModForumPostFormData, 'id'>; // Original post data. Usually shared between posts.
|
||||
@Input() trackPosts!: boolean; // True if post is being tracked.
|
||||
@Input() forum!: AddonModForumData; // The forum the post belongs to. Required for attachments and offline posts.
|
||||
@Input() accessInfo!: AddonModForumAccessInformation; // Forum access information.
|
||||
|
@ -93,8 +92,6 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
displaySubject = true;
|
||||
optionsMenuEnabled = false;
|
||||
|
||||
protected syncId!: string;
|
||||
|
||||
constructor(
|
||||
protected elementRef: ElementRef,
|
||||
@Optional() protected content?: IonContent,
|
||||
|
@ -102,8 +99,9 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
|
||||
get showForm(): boolean {
|
||||
return this.post.id > 0
|
||||
? !this.replyData.isEditing && this.replyData.replyingTo === this.post.id
|
||||
: !!this.replyData.isEditing && this.replyData.replyingTo === this.post.parentid;
|
||||
? (!this.formData.isEditing && this.formData.replyingTo === this.post.id) ||
|
||||
(!!this.formData.isEditing && this.formData.id === this.post.id)
|
||||
: !!this.formData.isEditing && this.formData.replyingTo === this.post.parentid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -172,44 +170,47 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
}
|
||||
|
||||
/**
|
||||
* Set data to new reply post, clearing temporary files and updating original data.
|
||||
* Set data to new/edit 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.
|
||||
* @param subject Subject of the reply.
|
||||
* @param message Message of the reply.
|
||||
* @param isPrivate True if it's private reply.
|
||||
* @param files Reply attachments.
|
||||
* @param isPrivate True if it's private reply.
|
||||
* @param postId The post ID if user is editing an online post.
|
||||
*/
|
||||
protected setReplyFormData(
|
||||
protected setFormData(
|
||||
replyingTo?: number,
|
||||
isEditing?: boolean,
|
||||
subject?: string,
|
||||
message?: string,
|
||||
files?: CoreFileEntry[],
|
||||
isPrivate?: boolean,
|
||||
postId?: number,
|
||||
): void {
|
||||
// Delete the local files from the tmp folder if any.
|
||||
CoreFileUploader.clearTmpFiles(this.replyData.files);
|
||||
CoreFileUploader.clearTmpFiles(this.formData.files);
|
||||
|
||||
this.replyData.replyingTo = replyingTo || 0;
|
||||
this.replyData.isEditing = !!isEditing;
|
||||
this.replyData.subject = subject || this.defaultReplySubject || '';
|
||||
this.replyData.message = message || null;
|
||||
this.replyData.files = files || [];
|
||||
this.replyData.isprivatereply = !!isPrivate;
|
||||
this.formData.replyingTo = replyingTo || 0;
|
||||
this.formData.isEditing = !!isEditing;
|
||||
this.formData.subject = subject || this.defaultReplySubject || '';
|
||||
this.formData.message = message || null;
|
||||
this.formData.files = files || [];
|
||||
this.formData.isprivatereply = !!isPrivate;
|
||||
this.formData.id = postId;
|
||||
|
||||
// Update rich text editor.
|
||||
this.messageControl.setValue(this.replyData.message);
|
||||
this.messageControl.setValue(this.formData.message);
|
||||
|
||||
// Update original data.
|
||||
this.originalData.subject = this.replyData.subject;
|
||||
this.originalData.message = this.replyData.message;
|
||||
this.originalData.files = this.replyData.files.slice();
|
||||
this.originalData.isprivatereply = this.replyData.isprivatereply;
|
||||
this.originalData.subject = this.formData.subject;
|
||||
this.originalData.message = this.formData.message;
|
||||
this.originalData.files = this.formData.files.slice();
|
||||
this.originalData.isprivatereply = this.formData.isprivatereply;
|
||||
|
||||
// Show advanced fields if any of them has not the default value.
|
||||
this.advanced = this.replyData.files.length > 0;
|
||||
this.advanced = this.formData.files.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,6 +227,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
cmId: this.forum.cmid,
|
||||
},
|
||||
event,
|
||||
waitForDismissCompleted: true,
|
||||
});
|
||||
|
||||
if (popoverData && popoverData.action) {
|
||||
|
@ -233,9 +235,6 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
case 'edit':
|
||||
this.editPost();
|
||||
break;
|
||||
case 'editoffline':
|
||||
this.editOfflineReply();
|
||||
break;
|
||||
case 'delete':
|
||||
this.deletePost();
|
||||
break;
|
||||
|
@ -246,65 +245,6 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a form modal to edit an online post.
|
||||
*/
|
||||
async editPost(): Promise<void> {
|
||||
const modalData = await CoreDomUtils.openModal<AddonModForumReply>({
|
||||
component: AddonModForumEditPostComponent,
|
||||
componentProps: {
|
||||
post: this.post,
|
||||
component: this.component,
|
||||
componentId: this.componentId,
|
||||
forum: this.forum,
|
||||
},
|
||||
backdropDismiss: false,
|
||||
cssClass: 'core-modal-fullscreen',
|
||||
});
|
||||
|
||||
if (!modalData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add some HTML to the message if needed.
|
||||
const message = CoreTextUtils.formatHtmlLines(modalData.message!);
|
||||
const files = modalData.files;
|
||||
const options: AddonModForumUpdateDiscussionPostWSOptionsObject = {};
|
||||
|
||||
const sendingModal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
|
||||
try {
|
||||
// Upload attachments first if any.
|
||||
if (files.length) {
|
||||
const attachment = await AddonModForumHelper.uploadOrStoreReplyFiles(
|
||||
this.forum.id,
|
||||
this.post.id,
|
||||
files as CoreFileEntry[],
|
||||
false,
|
||||
);
|
||||
|
||||
options.attachmentsid = attachment;
|
||||
}
|
||||
|
||||
// Try to send it to server.
|
||||
const sent = await AddonModForum.updatePost(this.post.id, modalData.subject!, message, options);
|
||||
|
||||
if (sent && this.forum.id) {
|
||||
// Data sent to server, delete stored files (if any).
|
||||
AddonModForumHelper.deleteReplyStoredFiles(this.forum.id, this.post.id);
|
||||
|
||||
this.onPostChange.emit();
|
||||
this.post.subject = modalData.subject!;
|
||||
this.post.message = message;
|
||||
this.post.attachments = modalData.files;
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_forum.couldnotupdate', true);
|
||||
} finally {
|
||||
sendingModal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this post as being replied to.
|
||||
*
|
||||
|
@ -314,21 +254,13 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.replyData.isEditing) {
|
||||
if (this.formData.isEditing) {
|
||||
// User is editing a post, data needs to be resetted. Ask confirm if there is unsaved data.
|
||||
try {
|
||||
await this.confirmDiscard();
|
||||
this.setReplyFormData(this.post.id);
|
||||
this.setFormData(this.post.id);
|
||||
|
||||
if (this.content) {
|
||||
setTimeout(() => {
|
||||
CoreDomUtils.scrollToElementBySelector(
|
||||
this.elementRef.nativeElement,
|
||||
this.content,
|
||||
'#addon-forum-reply-edit-form-' + this.uniqueId,
|
||||
);
|
||||
});
|
||||
}
|
||||
this.scrollToForm();
|
||||
} catch {
|
||||
// Cancelled.
|
||||
}
|
||||
|
@ -336,53 +268,47 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
return;
|
||||
}
|
||||
|
||||
if (!this.replyData.replyingTo) {
|
||||
if (!this.formData.replyingTo) {
|
||||
// User isn't replying, it's a brand new reply. Initialize the data.
|
||||
this.setReplyFormData(this.post.id);
|
||||
this.setFormData(this.post.id);
|
||||
} else {
|
||||
// The post being replied has changed but the data will be kept.
|
||||
this.replyData.replyingTo = this.post.id;
|
||||
this.formData.replyingTo = this.post.id;
|
||||
|
||||
if (this.replyData.subject == this.originalData.subject) {
|
||||
if (this.formData.subject == this.originalData.subject) {
|
||||
// Update subject only if it hadn't been modified
|
||||
this.replyData.subject = this.defaultReplySubject;
|
||||
this.formData.subject = this.defaultReplySubject;
|
||||
this.originalData.subject = this.defaultReplySubject;
|
||||
}
|
||||
|
||||
this.messageControl.setValue(this.replyData.message);
|
||||
}
|
||||
|
||||
if (this.content) {
|
||||
setTimeout(() => {
|
||||
CoreDomUtils.scrollToElementBySelector(
|
||||
this.elementRef.nativeElement,
|
||||
this.content,
|
||||
'#addon-forum-reply-edit-form-' + this.uniqueId,
|
||||
);
|
||||
});
|
||||
this.messageControl.setValue(this.formData.message);
|
||||
}
|
||||
|
||||
this.scrollToForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this post as being edited to.
|
||||
*/
|
||||
async editOfflineReply(): Promise<void> {
|
||||
async editPost(): Promise<void> {
|
||||
// Ask confirm if there is unsaved data.
|
||||
try {
|
||||
await this.confirmDiscard();
|
||||
|
||||
this.syncId = AddonModForumSync.getDiscussionSyncId(this.discussionId);
|
||||
CoreSync.blockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
this.formData.syncId = AddonModForumSync.getDiscussionSyncId(this.discussionId);
|
||||
CoreSync.blockOperation(AddonModForumProvider.COMPONENT, this.formData.syncId);
|
||||
|
||||
this.setReplyFormData(
|
||||
this.setFormData(
|
||||
this.post.parentid,
|
||||
true,
|
||||
this.post.subject,
|
||||
this.post.message,
|
||||
this.post.attachments,
|
||||
this.post.isprivatereply,
|
||||
this.post.id > 0 ? this.post.id : undefined,
|
||||
);
|
||||
|
||||
this.scrollToForm(5);
|
||||
} catch (error) {
|
||||
// Cancelled.
|
||||
}
|
||||
|
@ -394,67 +320,67 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
* @param text The new text.
|
||||
*/
|
||||
onMessageChange(text: string): void {
|
||||
this.replyData.message = text;
|
||||
this.formData.message = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to this post.
|
||||
* Reply to this post or edit post data.
|
||||
*/
|
||||
async reply(): Promise<void> {
|
||||
if (!this.replyData.subject) {
|
||||
async send(): Promise<void> {
|
||||
if (!this.formData.subject) {
|
||||
CoreDomUtils.showErrorModal('addon.mod_forum.erroremptysubject', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.replyData.message) {
|
||||
if (!this.formData.message) {
|
||||
CoreDomUtils.showErrorModal('addon.mod_forum.erroremptymessage', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let saveOffline = false;
|
||||
let message = this.replyData.message;
|
||||
const subject = this.replyData.subject;
|
||||
const replyingTo = this.replyData.replyingTo!;
|
||||
const files = this.replyData.files || [];
|
||||
const options: AddonModForumReplyOptions = {};
|
||||
let message = this.formData.message;
|
||||
const subject = this.formData.subject;
|
||||
const replyingTo = this.formData.replyingTo!;
|
||||
const files = this.formData.files || [];
|
||||
const isEditOnline = this.formData.id && this.formData.id > 0;
|
||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
|
||||
// Add some HTML to the message if needed.
|
||||
message = CoreTextUtils.formatHtmlLines(message);
|
||||
|
||||
// Set private option if checked.
|
||||
if (this.replyData.isprivatereply) {
|
||||
options.private = true;
|
||||
}
|
||||
|
||||
// Upload attachments first if any.
|
||||
let attachments;
|
||||
|
||||
if (files.length) {
|
||||
try {
|
||||
attachments = await AddonModForumHelper.uploadOrStoreReplyFiles(this.forum.id, replyingTo, files, false);
|
||||
} catch (error) {
|
||||
|
||||
// Cannot upload them in online, save them in offline.
|
||||
if (!this.forum.id) {
|
||||
// Cannot store them in offline without the forum ID. Reject.
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
saveOffline = true;
|
||||
attachments = await AddonModForumHelper.uploadOrStoreReplyFiles(this.forum.id, replyingTo, files, true);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (attachments) {
|
||||
options.attachmentsid = attachments;
|
||||
if (files.length) {
|
||||
try {
|
||||
attachments = await AddonModForumHelper.uploadOrStoreReplyFiles(
|
||||
this.forum.id,
|
||||
isEditOnline ? this.formData.id! : replyingTo,
|
||||
files,
|
||||
false,
|
||||
);
|
||||
} catch (error) {
|
||||
// Cannot upload them in online, save them in offline.
|
||||
if (!this.forum.id || isEditOnline) {
|
||||
// Cannot store them in offline. Reject.
|
||||
throw error;
|
||||
}
|
||||
|
||||
saveOffline = true;
|
||||
attachments = await AddonModForumHelper.uploadOrStoreReplyFiles(this.forum.id, replyingTo, files, true);
|
||||
}
|
||||
}
|
||||
|
||||
let sent;
|
||||
if (saveOffline) {
|
||||
let sent = false;
|
||||
|
||||
if (isEditOnline) {
|
||||
sent = await AddonModForum.updatePost(this.formData.id!, subject, message, {
|
||||
attachmentsid: attachments,
|
||||
});
|
||||
} else if (saveOffline) {
|
||||
// Save post in offline.
|
||||
await AddonModForumOffline.replyPost(
|
||||
replyingTo,
|
||||
|
@ -464,7 +390,10 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
this.courseId,
|
||||
subject,
|
||||
message,
|
||||
options,
|
||||
{
|
||||
attachmentsid: attachments,
|
||||
private: !!this.formData.isprivatereply,
|
||||
},
|
||||
);
|
||||
|
||||
// Set sent to false since it wasn't sent to server.
|
||||
|
@ -480,7 +409,10 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
this.courseId,
|
||||
subject,
|
||||
message,
|
||||
options,
|
||||
{
|
||||
attachmentsid: attachments,
|
||||
private: !!this.formData.isprivatereply,
|
||||
},
|
||||
undefined,
|
||||
!files.length,
|
||||
);
|
||||
|
@ -492,17 +424,19 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
}
|
||||
|
||||
// Reset data.
|
||||
this.setReplyFormData();
|
||||
this.setFormData();
|
||||
|
||||
this.onPostChange.emit();
|
||||
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, sent, CoreSites.getCurrentSiteId());
|
||||
|
||||
if (this.syncId) {
|
||||
CoreSync.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
}
|
||||
this.unblockOperation();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_forum.couldnotadd', true);
|
||||
CoreDomUtils.showErrorModalDefault(
|
||||
error,
|
||||
isEditOnline ? 'addon.mod_forum.couldnotupdate' : 'addon.mod_forum.couldnotadd',
|
||||
true,
|
||||
);
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
|
@ -516,13 +450,11 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
await this.confirmDiscard();
|
||||
|
||||
// Reset data.
|
||||
this.setReplyFormData();
|
||||
this.setFormData();
|
||||
|
||||
CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
|
||||
|
||||
if (this.syncId) {
|
||||
CoreSync.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
}
|
||||
this.unblockOperation();
|
||||
} catch (error) {
|
||||
// Cancelled.
|
||||
}
|
||||
|
@ -548,13 +480,11 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
await CoreUtils.ignoreErrors(Promise.all(promises));
|
||||
|
||||
// Reset data.
|
||||
this.setReplyFormData();
|
||||
this.setFormData();
|
||||
|
||||
this.onPostChange.emit();
|
||||
|
||||
if (this.syncId) {
|
||||
CoreSync.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
}
|
||||
this.unblockOperation();
|
||||
} catch (error) {
|
||||
// Cancelled.
|
||||
}
|
||||
|
@ -578,9 +508,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (this.syncId) {
|
||||
CoreSync.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId);
|
||||
}
|
||||
this.unblockOperation();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -588,13 +516,45 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
|
|||
*
|
||||
* @return Promise resolved if the user confirms or data was not changed and rejected otherwise.
|
||||
*/
|
||||
protected confirmDiscard(): Promise<void> {
|
||||
if (AddonModForumHelper.hasPostDataChanged(this.replyData, this.originalData)) {
|
||||
protected async confirmDiscard(): Promise<void> {
|
||||
if (AddonModForumHelper.hasPostDataChanged(this.formData, this.originalData)) {
|
||||
// Show confirmation if some data has been modified.
|
||||
return CoreDomUtils.showConfirm(Translate.instant('core.confirmloss'));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.confirmloss'));
|
||||
}
|
||||
|
||||
this.unblockOperation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblock operation if there's any blocked operation.
|
||||
*/
|
||||
protected unblockOperation(): void {
|
||||
if (!this.formData.syncId) {
|
||||
return;
|
||||
}
|
||||
|
||||
CoreSync.unblockOperation(AddonModForumProvider.COMPONENT, this.formData.syncId);
|
||||
delete this.formData.syncId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to reply/edit form.
|
||||
*
|
||||
* @param ticksToWait Number of ticks to wait before scrolling.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async scrollToForm(ticksToWait = 1): Promise<void> {
|
||||
if (!this.content) {
|
||||
return;
|
||||
}
|
||||
|
||||
await CoreUtils.nextTicks(ticksToWait);
|
||||
|
||||
CoreDomUtils.scrollToElementBySelector(
|
||||
this.elementRef.nativeElement,
|
||||
this.content,
|
||||
'#addon-forum-reply-edit-form-' + this.uniqueId,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
<addon-mod-forum-post
|
||||
[post]="startingPost" [discussion]="discussion" [courseId]="courseId" [highlight]="true"
|
||||
[discussionId]="discussionId" [component]="component" [componentId]="cmId"
|
||||
[replyData]="replyData" [originalData]="originalData" [forum]="forum" [accessInfo]="accessInfo"
|
||||
[formData]="formData" [originalData]="originalData" [forum]="forum" [accessInfo]="accessInfo"
|
||||
[trackPosts]="trackPosts" [ratingInfo]="ratingInfo" [leavingPage]="leavingPage"
|
||||
(onPostChange)="postListChanged()">
|
||||
</addon-mod-forum-post>
|
||||
|
@ -104,7 +104,7 @@
|
|||
<core-spacer *ngIf="!first"></core-spacer>
|
||||
<addon-mod-forum-post
|
||||
[post]="post" [courseId]="courseId" [discussionId]="discussionId"
|
||||
[component]="component" [componentId]="cmId" [replyData]="replyData"
|
||||
[component]="component" [componentId]="cmId" [formData]="formData"
|
||||
[originalData]="originalData" [parentSubject]="postSubjects[post.parentid]"
|
||||
[forum]="forum" [accessInfo]="accessInfo" [trackPosts]="trackPosts" [ratingInfo]="ratingInfo"
|
||||
[leavingPage]="leavingPage"
|
||||
|
@ -123,7 +123,7 @@
|
|||
<ion-card>
|
||||
<addon-mod-forum-post
|
||||
[post]="post" [courseId]="courseId" [discussionId]="discussionId" [component]="component"
|
||||
[componentId]="cmId" [replyData]="replyData" [originalData]="originalData"
|
||||
[componentId]="cmId" [formData]="formData" [originalData]="originalData"
|
||||
[parentSubject]="postSubjects[post.parentid]" [forum]="forum" [accessInfo]="accessInfo"
|
||||
[trackPosts]="trackPosts" [ratingInfo]="ratingInfo" [leavingPage]="leavingPage"
|
||||
(onPostChange)="postListChanged()">
|
||||
|
|
|
@ -39,7 +39,7 @@ import {
|
|||
AddonModForumDiscussion,
|
||||
AddonModForumPost,
|
||||
AddonModForumProvider,
|
||||
AddonModForumReply,
|
||||
AddonModForumPostFormData,
|
||||
} from '../../services/forum';
|
||||
import { AddonModForumHelper } from '../../services/forum-helper';
|
||||
import { AddonModForumOffline } from '../../services/forum-offline';
|
||||
|
@ -74,7 +74,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
|||
postHasOffline!: boolean;
|
||||
sort: SortType = 'nested';
|
||||
trackPosts!: boolean;
|
||||
replyData: Omit<AddonModForumReply, 'id'> = {
|
||||
formData: AddonModForumSharedPostFormData = {
|
||||
replyingTo: 0,
|
||||
isEditing: false,
|
||||
subject: '',
|
||||
|
@ -83,7 +83,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
|||
isprivatereply: false,
|
||||
};
|
||||
|
||||
originalData: Omit<AddonModForumReply, 'id'> = {
|
||||
originalData: Omit<AddonModForumPostFormData, 'id'> = {
|
||||
subject: null,
|
||||
message: null,
|
||||
files: [],
|
||||
|
@ -259,13 +259,13 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
|||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
async canLeave(): Promise<boolean> {
|
||||
if (AddonModForumHelper.hasPostDataChanged(this.replyData, this.originalData)) {
|
||||
if (AddonModForumHelper.hasPostDataChanged(this.formData, this.originalData)) {
|
||||
// Show confirmation if some data has been modified.
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
// Delete the local files from the tmp folder.
|
||||
CoreFileUploader.clearTmpFiles(this.replyData.files);
|
||||
CoreFileUploader.clearTmpFiles(this.formData.files);
|
||||
|
||||
this.leavingPage = true;
|
||||
|
||||
|
@ -795,3 +795,11 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply data shared by post.
|
||||
*/
|
||||
export type AddonModForumSharedPostFormData = Omit<AddonModForumPostFormData, 'id'> & {
|
||||
id?: number; // ID when editing an online reply.
|
||||
syncId?: string; // Sync ID if some post has blocked synchronization.
|
||||
};
|
||||
|
|
|
@ -1559,9 +1559,9 @@ export type AddonModForumAccessInformation = {
|
|||
};
|
||||
|
||||
/**
|
||||
* Reply info.
|
||||
* Post creation or edition data.
|
||||
*/
|
||||
export type AddonModForumReply = {
|
||||
export type AddonModForumPostFormData = {
|
||||
id: number;
|
||||
subject: string | null; // Null means original data is not set.
|
||||
message: string | null; // Null means empty or just white space.
|
||||
|
|
|
@ -1728,12 +1728,12 @@ export class CoreDomUtilsProvider {
|
|||
/**
|
||||
* Opens a popover.
|
||||
*
|
||||
* @param popoverOptions Modal Options.
|
||||
* @param options Options.
|
||||
* @return Promise resolved when the popover is dismissed or will be dismissed.
|
||||
*/
|
||||
async openPopover<T = void>(
|
||||
popoverOptions: PopoverOptions,
|
||||
): Promise<T | undefined> {
|
||||
async openPopover<T = void>(options: OpenPopoverOptions): Promise<T | undefined> {
|
||||
|
||||
const { waitForDismissCompleted, ...popoverOptions } = options;
|
||||
const popover = await PopoverController.create(popoverOptions);
|
||||
const zoomLevel = await CoreConfig.get(CoreConstants.SETTINGS_ZOOM_LEVEL, CoreZoomLevel.NORMAL);
|
||||
|
||||
|
@ -1743,16 +1743,15 @@ export class CoreDomUtilsProvider {
|
|||
if (zoomLevel !== CoreZoomLevel.NORMAL) {
|
||||
switch (getMode()) {
|
||||
case 'ios':
|
||||
fixIOSPopoverPosition(popover, popoverOptions.event);
|
||||
fixIOSPopoverPosition(popover, options.event);
|
||||
break;
|
||||
case 'md':
|
||||
fixMDPopoverPosition(popover, popoverOptions.event);
|
||||
fixMDPopoverPosition(popover, options.event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If onDidDismiss is nedded we can add a new param to the function to wait one function or the other.
|
||||
const result = await popover.onWillDismiss<T>();
|
||||
const result = waitForDismissCompleted ? await popover.onDidDismiss<T>() : await popover.onWillDismiss<T>();
|
||||
if (result?.data) {
|
||||
return result?.data;
|
||||
}
|
||||
|
@ -2046,3 +2045,10 @@ export const CoreDomUtils = makeSingleton(CoreDomUtilsProvider);
|
|||
|
||||
type AnchorOrMediaElement =
|
||||
HTMLAnchorElement | HTMLImageElement | HTMLAudioElement | HTMLVideoElement | HTMLSourceElement | HTMLTrackElement;
|
||||
|
||||
/**
|
||||
* Options for the openPopover function.
|
||||
*/
|
||||
export type OpenPopoverOptions = PopoverOptions & {
|
||||
waitForDismissCompleted?: boolean;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue