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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user