@@ -35,10 +35,11 @@ import {
3535 AddonModForumDiscussion ,
3636 AddonModForumPost ,
3737 AddonModForumPostFormData ,
38+ AddonModForumPrepareDraftAreaForPostWSResponse ,
3839} from '../../services/forum' ;
3940import { CoreTag } from '@features/tag/services/tag' ;
4041import { Translate } from '@singletons' ;
41- import { CoreFileUploader } from '@features/fileuploader/services/fileuploader' ;
42+ import { CoreFileUploader , CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader' ;
4243import { AddonModForumSync } from '../../services/forum-sync' ;
4344import { CoreSync } from '@services/sync' ;
4445import { CoreText } from '@singletons/text' ;
@@ -47,7 +48,7 @@ import { AddonModForumOffline } from '../../services/forum-offline';
4748import { CoreUtils } from '@services/utils/utils' ;
4849import { CoreRatingInfo } from '@features/rating/services/rating' ;
4950import { CoreForms } from '@singletons/form' ;
50- import { CoreFileEntry } from '@services/file-helper' ;
51+ import { CoreFileEntry , CoreFileHelper } from '@services/file-helper' ;
5152import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussion' ;
5253import { CoreDom } from '@singletons/dom' ;
5354import { CoreAnalytics , CoreAnalyticsEventType } from '@services/analytics' ;
@@ -56,6 +57,7 @@ import { CoreToasts } from '@services/toasts';
5657import { toBoolean } from '@/core/transforms/boolean' ;
5758import { CorePopovers } from '@services/popovers' ;
5859import { CoreLoadings } from '@services/loadings' ;
60+ import { CoreWSFile } from '@services/ws' ;
5961
6062/**
6163 * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
@@ -95,6 +97,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
9597 displaySubject = true ;
9698 optionsMenuEnabled = false ;
9799
100+ protected preparePostData ?: AddonModForumPrepareDraftAreaForPostWSResponse ;
101+
98102 constructor (
99103 protected elementRef : ElementRef ,
100104 ) { }
@@ -211,7 +215,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
211215 this . formData . isEditing = ! ! isEditing ;
212216 this . formData . subject = subject || this . defaultReplySubject || '' ;
213217 this . formData . message = message || null ;
214- this . formData . files = files || [ ] ;
218+ this . formData . files = ( files ?? [ ] ) . slice ( ) ; // Make a copy to avoid modifying the original array.
215219 this . formData . isprivatereply = ! ! isPrivate ;
216220 this . formData . id = postId ;
217221
@@ -226,6 +230,10 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
226230
227231 // Show advanced fields if any of them has not the default value.
228232 this . advanced = this . formData . files . length > 0 ;
233+
234+ if ( ! isEditing || ! postId || postId <= 0 ) {
235+ this . preparePostData = undefined ;
236+ }
229237 }
230238
231239 /**
@@ -314,6 +322,28 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
314322 // Ask confirm if there is unsaved data.
315323 try {
316324 await this . confirmDiscard ( ) ;
325+ } catch {
326+ // Cancelled.
327+ return ;
328+ }
329+
330+ const modal = await CoreLoadings . show ( ) ;
331+
332+ try {
333+ let message = this . post . message ;
334+
335+ if ( this . post . id > 0 ) {
336+ // Call prepare post for edition to retrieve the message without any added content (like filters and plagiarism).
337+ this . preparePostData = await AddonModForum . preparePostForEdition ( this . post . id , 'post' ) ;
338+
339+ const { text } = CoreFileHelper . replaceDraftfileUrls (
340+ CoreSites . getRequiredCurrentSite ( ) . getURL ( ) ,
341+ this . preparePostData . messagetext ,
342+ this . post . messageinlinefiles ?. length ? this . post . messageinlinefiles : ( this . preparePostData . files ?? [ ] ) ,
343+ ) ;
344+
345+ message = text ;
346+ }
317347
318348 this . formData . syncId = AddonModForumSync . getDiscussionSyncId ( this . discussionId ) ;
319349 CoreSync . blockOperation ( ADDON_MOD_FORUM_COMPONENT , this . formData . syncId ) ;
@@ -322,7 +352,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
322352 this . post . parentid ,
323353 true ,
324354 this . post . subject ,
325- this . post . message ,
355+ message ,
326356 this . post . attachments ,
327357 this . post . isprivatereply ,
328358 this . post . id > 0 ? this . post . id : undefined ,
@@ -331,8 +361,10 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
331361 this . scrollToForm ( ) ;
332362
333363 this . analyticsLogEvent ( 'mod_forum_update_discussion_post' , `/mod/forum/post.php?edit=${ this . post . id } ` ) ;
334- } catch {
335- // Cancelled.
364+ } catch ( error ) {
365+ CoreDomUtils . showErrorModalDefault ( error , 'addon.mod_forum.errorgetpost' , true ) ;
366+ } finally {
367+ modal . dismiss ( ) ;
336368 }
337369 }
338370
@@ -361,83 +393,76 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
361393 return ;
362394 }
363395
364- let saveOffline = false ;
365396 let message = this . formData . message ;
366397 const subject = this . formData . subject ;
367- const replyingTo = this . formData . replyingTo ! ;
398+ const replyingTo = this . formData . replyingTo ?? 0 ;
368399 const files = this . formData . files || [ ] ;
369400 const isEditOnline = this . formData . id && this . formData . id > 0 ;
370401 const modal = await CoreLoadings . show ( 'core.sending' , true ) ;
371402
403+ if ( isEditOnline && this . preparePostData ) {
404+ // Restore the draft file URLs, otherwise the treated URLs would be saved in the content, which can cause problems.
405+ message = CoreFileHelper . restoreDraftfileUrls (
406+ CoreSites . getRequiredCurrentSite ( ) . getURL ( ) ,
407+ message ,
408+ this . preparePostData . messagetext ,
409+ this . post . messageinlinefiles ?. length ? this . post . messageinlinefiles : ( this . preparePostData . files ?? [ ] ) ,
410+ ) ;
411+ }
412+
372413 // Add some HTML to the message if needed.
373414 message = CoreText . formatHtmlLines ( message ) ;
374415
375- // Upload attachments first if any.
376- let attachments ;
377-
378416 try {
379- if ( files . length ) {
380- try {
381- attachments = await AddonModForumHelper . uploadOrStoreReplyFiles (
382- this . forum . id ,
383- isEditOnline ? this . formData . id ! : replyingTo ,
384- files ,
385- false ,
386- ) ;
387- } catch ( error ) {
388- // Cannot upload them in online, save them in offline.
389- if ( ! this . forum . id || isEditOnline || CoreUtils . isWebServiceError ( error ) ) {
390- // Cannot store them in offline. Reject.
391- throw error ;
392- }
393-
394- saveOffline = true ;
395- attachments = await AddonModForumHelper . uploadOrStoreReplyFiles ( this . forum . id , replyingTo , files , true ) ;
396- }
397- }
398-
399417 let sent = false ;
400418
401- if ( isEditOnline ) {
402- sent = await AddonModForum . updatePost ( this . formData . id ! , subject , message , {
419+ if ( this . formData . id && this . formData . id > 0 ) {
420+ const attachments = await this . uploadAttachmentsForEditOnline ( this . formData . id ) ;
421+
422+ sent = await AddonModForum . updatePost ( this . formData . id , subject , message , {
403423 attachmentsid : attachments ,
424+ inlineattachmentsid : this . preparePostData ?. draftitemid ,
404425 } ) ;
405- } else if ( saveOffline ) {
406- // Save post in offline.
407- await AddonModForumOffline . replyPost (
408- replyingTo ,
409- this . discussionId ,
410- this . forum . id ,
411- this . forum . name ,
412- this . courseId ,
413- subject ,
414- message ,
415- {
416- attachmentsid : attachments ,
417- private : ! ! this . formData . isprivatereply ,
418- } ,
419- ) ;
420-
421- // Set sent to false since it wasn't sent to server.
422- sent = false ;
423426 } else {
424- // Try to send it to server.
425- // Don't allow offline if there are attachments since they were uploaded fine.
426- sent = await AddonModForum . replyPost (
427- replyingTo ,
428- this . discussionId ,
429- this . forum . id ,
430- this . forum . name ,
431- this . courseId ,
432- subject ,
433- message ,
434- {
435- attachmentsid : attachments ,
436- private : ! ! this . formData . isprivatereply ,
437- } ,
438- undefined ,
439- ! files . length ,
440- ) ;
427+ const { attachments, saveOffline } = await this . uploadAttachmentsForReply ( replyingTo ) ;
428+
429+ if ( saveOffline ) {
430+ // Save post in offline.
431+ await AddonModForumOffline . replyPost (
432+ replyingTo ,
433+ this . discussionId ,
434+ this . forum . id ,
435+ this . forum . name ,
436+ this . courseId ,
437+ subject ,
438+ message ,
439+ {
440+ attachmentsid : attachments ,
441+ private : ! ! this . formData . isprivatereply ,
442+ } ,
443+ ) ;
444+
445+ // Set sent to false since it wasn't sent to server.
446+ sent = false ;
447+ } else {
448+ // Try to send it to server.
449+ // Don't allow offline if there are attachments since they were uploaded fine.
450+ sent = await AddonModForum . replyPost (
451+ replyingTo ,
452+ this . discussionId ,
453+ this . forum . id ,
454+ this . forum . name ,
455+ this . courseId ,
456+ subject ,
457+ message ,
458+ {
459+ attachmentsid : attachments ,
460+ private : ! ! this . formData . isprivatereply ,
461+ } ,
462+ undefined ,
463+ ! files . length ,
464+ ) ;
465+ }
441466 }
442467
443468 if ( sent && this . forum . id ) {
@@ -464,6 +489,81 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
464489 }
465490 }
466491
492+ /**
493+ * Upload attachments when editing an online post.
494+ *
495+ * @param postId Post ID being edited.
496+ * @returns Draft area id (if any attachment has changed).
497+ */
498+ protected async uploadAttachmentsForEditOnline ( postId : number ) : Promise < number | undefined > {
499+ const files = this . formData . files || [ ] ;
500+ const previousAttachments = ( this . post . attachments ?? [ ] ) as CoreWSFile [ ] ;
501+
502+ if ( ! CoreFileUploader . areFileListDifferent ( files , previousAttachments ) ) {
503+ return ;
504+ }
505+
506+ // Use prepare post for edition to avoid re-uploading all files.
507+ let filesToKeep = files . filter ( ( file ) : file is CoreWSFile => ! CoreUtils . isFileEntry ( file ) ) ;
508+ let removedFiles : { filepath : string ; filename : string } [ ] | undefined ;
509+
510+ if ( previousAttachments . length && ! filesToKeep . length ) {
511+ // Post had attachments but they were all removed. We cannot use the filesToKeep option because it doesn't allow
512+ // removing all files. In this case we'll just keep 1 file and remove it later.
513+ filesToKeep = [ previousAttachments [ 0 ] ] ;
514+ removedFiles = [ {
515+ filename : previousAttachments [ 0 ] . filename ?? '' ,
516+ filepath : previousAttachments [ 0 ] . filepath ?? '' ,
517+ } ] ;
518+ }
519+
520+ const preparePostData = await AddonModForum . preparePostForEdition ( postId , 'attachment' , { filesToKeep } ) ;
521+
522+ if ( removedFiles ?. length ) {
523+ await CoreFileUploader . deleteDraftFiles ( preparePostData . draftitemid , removedFiles ) ;
524+ }
525+
526+ await CoreFileUploader . uploadFiles ( preparePostData . draftitemid , files ) ;
527+
528+ return preparePostData . draftitemid ;
529+ }
530+
531+ /**
532+ * Upload attachments for a reply that isn't an online post being edited.
533+ *
534+ * @param replyingTo Replying to post ID.
535+ * @returns Draft area id (if any attachment was uploaded) and whether data should be saved offline.
536+ */
537+ async uploadAttachmentsForReply (
538+ replyingTo : number ,
539+ ) : Promise < { attachments : CoreFileUploaderStoreFilesResult | number | undefined ; saveOffline : boolean } > {
540+ const files = this . formData . files || [ ] ;
541+ if ( ! files . length ) {
542+ return { attachments : undefined , saveOffline : false } ;
543+ }
544+
545+ try {
546+ const attachments = await AddonModForumHelper . uploadOrStoreReplyFiles (
547+ this . forum . id ,
548+ replyingTo ,
549+ files ,
550+ false ,
551+ ) ;
552+
553+ return { attachments, saveOffline : false } ;
554+ } catch ( error ) {
555+ // Cannot upload them in online, save them in offline.
556+ if ( ! this . forum . id || CoreUtils . isWebServiceError ( error ) ) {
557+ // Cannot store them in offline. Reject.
558+ throw error ;
559+ }
560+
561+ const attachments = await AddonModForumHelper . uploadOrStoreReplyFiles ( this . forum . id , replyingTo , files , true ) ;
562+
563+ return { attachments, saveOffline : true } ;
564+ }
565+ }
566+
467567 /**
468568 * Cancel reply.
469569 */
0 commit comments